thrift-compiler-0.1.0.1: Thrift/Compiler/GenService.hs
{-
Copyright (c) Meta Platforms, Inc. and affiliates.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree.
-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE CPP #-}
module Thrift.Compiler.GenService
( genServiceDecls
, genServiceImports
, genServiceExports
) where
import Control.Monad
#if __GLASGOW_HASKELL__ <= 804
import Data.Monoid ((<>))
#endif
import Data.Maybe (isNothing)
import qualified Data.Set as Set
import Data.Some
import Data.Text (Text)
import qualified Data.Text as Text
import Language.Haskell.Exts.Syntax as HS
import Thrift.Compiler.GenStruct
import Thrift.Compiler.GenUtils
import Thrift.Compiler.Plugins.Haskell
import Thrift.Compiler.Types hiding (Decl(..))
-- | All things required to generate a Service.hs file
genServiceExports :: HS Service -> [HS.ExportSpec ()]
genServiceExports s =
(if isEmptyService s
then HS.EAbs () (NoNamespace ()) (unqualSym $ commandTypeName s)
else HS.EThingWith () (EWildcard () 0) (unqualSym $ commandTypeName s) [])
: map (HS.EVar () . UnQual () . Ident ())
["reqName'", "reqParser'", "respWriter'", "methodsInfo'"]
genServiceImports :: Text.Text -> HS Service -> Set.Set Import
genServiceImports this s@Service{..} =
foldr (Set.union . importsForFunc) baseImports (getServiceFunctions s)
where
importsForFunc Function{..} =
foldr (Set.union . getImports) (retImport funResolvedType) funArgs
where
retImport Nothing = Set.empty
retImport (Just (Some i)) = typeToImport i
getImports :: HS (Field u) -> Set.Set Import
getImports Field{..} = typeToImport fieldResolvedType
baseImports = Set.fromList
[ QImport "Prelude" "Prelude"
, QImport "Control.Exception" "Exception"
, QImport "Control.Monad.ST.Trans" "ST"
, QImport "Control.Monad.Trans.Class" "Trans"
, QImport "Data.ByteString.Builder" "Builder"
, QImport "Data.Default" "Default"
, QImport "Data.HashMap.Strict" "HashMap"
, QImport "Data.Map.Strict" "Map"
, QImport "Data.Int" "Int"
, QImport "Data.Proxy" "Proxy"
, QImport "Data.Text" "Text"
, QImport "Thrift.Binary.Parser" "Parser"
, QImport "Thrift.Protocol.ApplicationException.Types" "Thrift"
, QImport "Thrift.Processor" "Thrift"
, SymImport "Prelude"
[ "<$>", "<*>", "++", ".", "==" ]
, SymImport "Control.Applicative" [ "<*", "*>" ]
, SymImport "Data.Monoid" [ "<>" ]
] `Set.union`
(case resolvedName . fst . supResolvedName <$> serviceSuper of
Nothing -> Set.empty
Just (UName n) -> mkImport this n
Just (QName m n) -> mkImport m n)
mkImport m n =
Set.singleton $ QImport (Text.intercalate "." [m, n, "Service"]) n
genServiceDecls :: HS Service -> [HS.Decl ()]
genServiceDecls s =
[ genCommandDT s
, processorInstance
] ++ concat
[ genReqName s
, genReqParser s
, genRespWriter s
, genMethodsInfo s
]
where
processorInstance = HS.InstDecl () Nothing
(HS.IRule () Nothing Nothing $
HS.IHApp ()
(HS.IHCon () $ qualSym "Thrift" "Processor")
(HS.TyCon () $ unqualSym $ commandTypeName s)
)
(Just $ classFuns ++ additional)
classFuns = flip map [ "reqName" , "reqParser" , "respWriter" ] $ \fn ->
HS.InsDecl () $ HS.FunBind () [ HS.Match () (textToName fn) []
(HS.UnGuardedRhs () $ con (fn <> "'")) Nothing
]
additional =
[ HS.InsDecl () $ HS.FunBind ()
[ HS.Match () (textToName "methodsInfo") [HS.PWildCard ()]
(HS.UnGuardedRhs () $ var "methodsInfo'") Nothing
]
]
-- | Generates a GADT for all functions that the service can implement
-- If extending a service, adds a "Super<Name>" constructor to forward
-- requests along
genCommandDT :: HS Service -> HS.Decl ()
genCommandDT s@Service{..} = HS.GDataDecl () (HS.DataType ()) Nothing
(HS.DHApp ()
(HS.DHead () (textToName $ commandTypeName s))
(HS.UnkindedVar () (HS.Ident () "a")))
Nothing
genFunctions
mzero
where
genFunctions = map genDTFunction (getServiceFunctions s) ++
case fst . supResolvedName <$> serviceSuper of
Nothing -> []
Just Name{..} -> [genSuper $ localName resolvedName]
genSuper superName =
HS.GadtDecl () (textToName $ "Super" <> superName)
#if MIN_VERSION_haskell_src_exts(1,21,0)
Nothing Nothing
#endif
Nothing $
HS.TyFun ()
(qualType superName (superName <> "Command") `appT` simpleType "a")
(simpleType (commandTypeName s) `appT` tvar "a")
genDTFunction Function{..} =
HS.GadtDecl () (textToName $ toCamel funName)
#if MIN_VERSION_haskell_src_exts(1,21,0)
Nothing Nothing
#endif
Nothing (mkArgs funArgs)
where
mkArgs = foldr
(\Field{..} ->
HS.TyFun () $ genType $ qualifyType "Types" fieldResolvedType)
(simpleType (commandTypeName s) `appT`
maybe (HS.unit_tycon ())
(`withSome` (genType . qualifyType "Types")) funResolvedType)
-- | Generates a function that returns a Text name for the given function
genReqName :: HS Service -> [HS.Decl ()]
genReqName s@Service{..} =
[ HS.TypeSig () [textToName "reqName'"] $ HS.TyFun ()
(HS.TyApp () (simpleType $ commandTypeName s) (simpleType "a"))
(qualType "Text" "Text")
] ++ map genFunction theseFunctions ++
case fst . supResolvedName <$> serviceSuper of
Nothing -> noParentBody (null theseFunctions)
Just Name{..} -> [genSuper $ localName resolvedName]
where
theseFunctions = getServiceFunctions s
genSuper superName = HS.FunBind ()
[ HS.Match () (textToName "reqName'")
[HS.PParen () $ HS.PApp ()
(HS.UnQual () $ textToName $ "Super" <> superName) [pvar "x"]]
(HS.UnGuardedRhs () $ qvar superName "reqName'" `app` var "x")
Nothing
]
genFunction Function{..} = HS.FunBind ()
[ HS.Match () (textToName "reqName'")
[HS.PApp () (unqualSym $ toCamel funName) $
map (\Field{..} -> pvar ("__field__" <> fieldName)) funArgs]
(HS.UnGuardedRhs () $ stringLit funName)
Nothing
]
noParentBody False = []
noParentBody True = [ HS.FunBind ()
[ HS.Match () (textToName "reqName'")
[ HS.PWildCard () ]
(HS.UnGuardedRhs () (stringLit "unknown function"))
Nothing
]]
-- | Generates a function that parses the input message as appropriate
genReqParser :: HS Service -> [HS.Decl ()]
genReqParser s@Service{..} =
[ HS.TypeSig () [textToName "reqParser'"] $
HS.TyForall () Nothing
#if MIN_VERSION_haskell_src_exts(1,22,0)
(Just $ HS.CxSingle () $ HS.TypeA () (HS.TyApp () (HS.TyCon () (qualSym "Thrift" "Protocol"))
(tvar "p" ))) $
#else
(Just $ HS.CxSingle () $ HS.ClassA () (qualSym "Thrift" "Protocol")
[ tvar "p" ]) $
#endif
HS.TyFun () (qualType "Proxy" "Proxy" `appT` tvar "p") $
HS.TyFun () (qualType "Text" "Text") $
HS.TyApp () (qualType "Parser" "Parser") $ HS.TyParen () $
qualType "Thrift" "Some" `appT` simpleType (commandTypeName s)
] ++
map genFunction (getServiceFunctions s) ++
genSuper serviceSuper
where
genSuper Nothing = [ HS.FunBind ()
[ HS.Match () (textToName "reqParser'")
[ HS.PWildCard (), pvar "funName"]
(HS.UnGuardedRhs () (qvar "Prelude" "errorWithoutStackTrace" `app` HS.Paren ()
(infixApp "++" (stringLit "unknown function call: ")
(qvar "Text" "unpack" `app` var "funName"))))
Nothing
]]
genSuper (Just Super{..}) =
let n = localName $ resolvedName $ fst supResolvedName in
[ HS.FunBind ()
[ HS.Match () (textToName "reqParser'")
[ pvar "_proxy", pvar "funName" ]
(HS.UnGuardedRhs () $ HS.Do ()
[ HS.Generator () (HS.PApp () (qualSym "Thrift" "Some") [ pvar "x" ])
(qvar (toCamel n) "reqParser'" `app`
var "_proxy" `app`
var "funName")
, HS.Qualifier ()
(qvar "Prelude" "return" `app` HS.Paren ()
(qcon "Thrift" "Some" `app`
HS.Paren () (
con ("Super" <> n) `app`
var "x"
)))
])
Nothing
]]
genFunction Function{..} = HS.FunBind ()
[ HS.Match () (textToName "reqParser'")
[ pvar "_proxy", stringP funName ]
(genFieldParser (map (qualifyField "Types") funArgs)
(toCamel funName)
(app (qcon "Thrift" "Some")))
Nothing
]
-- | Generates a function that builds the appropriate response type
genRespWriter :: HS Service -> [HS.Decl ()]
genRespWriter s@Service{..} =
[ HS.TypeSig () [textToName "respWriter'"] $
HS.TyForall () Nothing
#if MIN_VERSION_haskell_src_exts(1,22,0)
(Just $ HS.CxSingle () $ HS.TypeA () (HS.TyApp () (HS.TyCon () (qualSym "Thrift" "Protocol"))
(tvar "p" ))) $
#else
(Just $ HS.CxSingle () $ HS.ClassA () (qualSym "Thrift" "Protocol")
[ tvar "p" ]) $
#endif
HS.TyFun () (qualType "Proxy" "Proxy" `appT` tvar "p") $
HS.TyFun () (qualType "Int" "Int32") $
HS.TyFun () (simpleType (commandTypeName s) `appT` tvar "a") $
HS.TyFun ()
(qualType "Prelude" "Either" `appT`
qualType "Exception" "SomeException" `appT`
tvar "a") $
HS.TyTuple () Boxed
[ qualType "Builder" "Builder"
, qualType "Prelude" "Maybe" `appT`
HS.TyTuple() Boxed
[ qualType "Exception" "SomeException"
, qualType "Thrift" "Blame"
]
]
] ++
map genFunction theseFunctions ++
genSuper serviceSuper (null theseFunctions)
where
theseFunctions = getServiceFunctions s
genSuper Nothing True =
[ HS.FunBind ()
[ HS.Match () (textToName "respWriter'")
[ HS.PWildCard () ]
(HS.UnGuardedRhs () (qvar "Prelude" "errorWithoutStackTrace" `app` HS.Paren ()
(stringLit "unknown function")))
Nothing
]
]
genSuper Nothing False = []
genSuper (Just Super{..}) _ =
let n = localName $ resolvedName $ fst supResolvedName
in
[ HS.FunBind ()
[ HS.Match () (textToName "respWriter'")
[ pvar "_proxy"
, pvar "_seqNum"
, HS.PParen () $ HS.PApp ()
(HS.UnQual () $ textToName $ "Super" <> n) [pvar "_x"]
, pvar "_r" ]
(HS.UnGuardedRhs () $
qvar n "respWriter'" `app`
var "_proxy" `app`
var "_seqNum" `app`
var "_x" `app`
var "_r"
)
Nothing
]]
genFunction :: HS Function -> Decl ()
genFunction Function{..} = HS.FunBind ()
[ HS.Match () (textToName "respWriter'")
[ pvar "_proxy"
, pvar "_seqNum"
, HS.PRec () (unqualSym $ toCamel funName) []
, pvar "_r" ]
(HS.UnGuardedRhs () $
Tuple () Boxed
[ infixApp "<>"
(infixApp "<>"
(protocolFun "genMsgBegin" `app`
stringLit funName `app`
var "_msgType" `app`
var "_seqNum")
(var "_msgBody"))
(protocolFun "genMsgEnd")
, var "_msgException" ])
(Just $ BDecls ()
[ PatBind () (PTuple () Boxed
[ pvar "_msgType"
, pvar "_msgBody"
, pvar "_msgException" ])
(UnGuardedRhs () $ Case () (var "_r")
[ Alt () (PApp () (qualSym "Prelude" "Left") [ pvar "_ex" ])
(GuardedRhss () $
[ GuardedRhs ()
[ Generator ()
(PApp () (qualSym "Prelude" "Just")
[ PAsPat () (textToName "_e") $
PRec () (qualSym "Thrift" "ApplicationException") []
]) $
qvar "Exception" "fromException" `app` var "_ex"
] $
genRespTup genEXCEPTION
(protocolFun "buildStruct" `app` var "_e")
(Just (var "_ex", "ServerError"))
] ++
map genExceptionCase funExceptions ++
[ GuardedRhs () [ Qualifier () $ qvar "Prelude" "otherwise" ] $
Let ()
(BDecls ()
[ PatBind ()
(pvar "_e")
(UnGuardedRhs () $
qcon "Thrift" "ApplicationException" `app`
(qvar "Text" "pack" `app`
(qvar "Prelude" "show" `app` var "_ex")) `app`
qcon "Thrift"
"ApplicationExceptionType_InternalError")
Nothing ]) $
genRespTup genEXCEPTION
(protocolFun "buildStruct" `app` var "_e")
(Just
( qvar "Exception" "toException" `app` var "_e"
, "ServerError" ))
])
Nothing
, Alt () (PApp () (qualSym "Prelude" "Right") [ pvar "_result" ])
(UnGuardedRhs () $ genRespTup genREPLY
(protocolFun "genStruct" `app`
HS.List ()
(genResp $ qualifyResolvedType "Types" funResolvedType))
Nothing)
Nothing
])
Nothing
])
]
where
genExceptionCase :: HS (Field 'ThrowsField) -> GuardedRhs ()
genExceptionCase Field{..} =
case fieldTag of
THROWS_RESOLVED -> GuardedRhs ()
[ Generator ()
(PApp () (qualSym "Prelude" "Just")
[ PAsPat () (textToName "_e") $
PRec () (genConstructor (Just "Types") fieldResolvedType) []
])
(qvar "Exception" "fromException" `app` var "_ex")
] $
genRespTup genREPLY
(protocolFun "genStruct" `app`
HS.List ()
[ genFieldBase fieldResolvedType fieldName fieldId
(intLit (0 :: Int))
( var "_e")
])
(Just (var "_ex", "ClientError"))
genRespTup int body exc = Tuple () Boxed
[ intLit int
, body
, case exc of
Just (e,blame) -> qcon "Prelude" "Just" `app`
Tuple () Boxed [ e, qcon "Thrift" blame ]
Nothing -> qcon "Prelude" "Nothing"
]
genResp Nothing = []
genResp (Just (Some t)) =
[ genFieldBase t "" 0 (intLit (0 :: Int)) (var "_result") ]
genMethodsInfo :: HS Service -> [HS.Decl ()]
genMethodsInfo service =
[ signature
, HS.FunBind ()
[ HS.Match () (textToName methodsInfo') []
(HS.UnGuardedRhs () infos) Nothing
]
]
where
signature = HS.TypeSig () [textToName methodsInfo'] $
qualType "Map" "Map"
`appT` qualType "Text" "Text"
`appT` HS.TyCon () (qualSym "Thrift" "MethodInfo")
methodsInfo' = "methodsInfo'"
superMethodsInfo :: Maybe (HS.Exp ())
superMethodsInfo = do
(Name{..}, _) <- supResolvedName <$> serviceSuper service
return $ qvar (localName resolvedName) methodsInfo'
instanceMethodsInfo = qvar "Map" "fromList" `app`
listE (genInfoTuple <$> getServiceFunctions service)
infos = case superMethodsInfo of
Nothing -> instanceMethodsInfo
Just s -> qvar "Map" "union" `app` instanceMethodsInfo `app` s
genInfoTuple f@Function{..} =
Tuple () Boxed
[ stringLit funName
, genOneMethodInfo f
]
genOneMethodInfo :: HS Function -> HS.Exp ()
genOneMethodInfo Function{..} =
qcon "Thrift" "MethodInfo"
`app` qcon "Thrift" (Text.pack $ show funPriority)
`app` qcon "Prelude" (Text.pack $ show funIsOneWay)
commandTypeName :: HS Service -> Text
commandTypeName Service{..} = serviceResolvedName <> "Command"
isEmptyService :: HS Service -> Bool
isEmptyService s@Service{..} = null (getServiceFunctions s) && isNothing serviceSuper