compiler/Thrift/Compiler/GenService.hs (406 lines of code) (raw):

-- Copyright (c) Facebook, Inc. and its affiliates. {-# LANGUAGE TypeOperators #-} {-# LANGUAGE CPP #-} module Thrift.Compiler.GenService ( genServiceDecls , genServiceImports , genServiceExports ) where #if __GLASGOW_HASKELL__ > 804 #define This Some #define THIS "Some" #else #define THIS "This" #endif 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@Service{..} = (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'", "onewayFunctions'"] genServiceImports :: Text.Text -> HS Service -> Set.Set Import genServiceImports this Service{..} = foldr (Set.union . importsForFunc) baseImports serviceFunctions where importsForFunc Function{..} = foldr (Set.union . getImports) (retImport funResolvedType) funArgs where retImport Nothing = Set.empty retImport (Just (This 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.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@Service{..} = [ genCommandDT s , processorInstance ] ++ concat [ genReqName s , genReqParser s , genRespWriter s , genOneWays s ] where processorInstance = HS.InstDecl () Nothing (HS.IRule () Nothing Nothing $ HS.IHApp () (HS.IHCon () $ qualSym "Thrift" "Processor") (HS.TyCon () $ unqualSym $ commandTypeName s) ) (Just $ (flip map [ "reqName" , "reqParser" , "respWriter" ] $ \fn -> HS.InsDecl () $ HS.FunBind () [ HS.Match () (textToName fn) [] (HS.UnGuardedRhs () $ con (fn <> "'")) Nothing ]) ++ [ HS.InsDecl () $ HS.FunBind () [ HS.Match () (textToName "onewayFns") [HS.PWildCard ()] (HS.UnGuardedRhs () $ var "onewayFunctions'") 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 serviceFunctions ++ 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 serviceFunctions ++ case fst . supResolvedName <$> serviceSuper of Nothing -> noParentBody (null serviceFunctions) Just Name{..} -> [genSuper $ localName resolvedName] where 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 serviceFunctions ++ 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" THIS) [ pvar "x" ]) (qvar (toCamel n) "reqParser'" `app` var "_proxy" `app` var "funName") , HS.Qualifier () (qvar "Prelude" "return" `app` HS.Paren () (qcon "Thrift" THIS `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" THIS))) 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 serviceFunctions ++ genSuper serviceSuper (null serviceFunctions) where 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 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 (This t)) = [ genFieldBase t "" 0 (intLit (0 :: Int)) (var "_result") ] genOneWays :: HS Service -> [HS.Decl ()] genOneWays Service{..} = [ HS.TypeSig () [textToName "onewayFunctions'"] $ HS.TyList () (qualType "Text" "Text") , HS.FunBind () [ HS.Match () (textToName "onewayFunctions'") [] (HS.UnGuardedRhs () $ genList $ (fst . supResolvedName) <$> serviceSuper) Nothing ] ] where genList Nothing = onewayFns genList (Just Name{..}) = infixApp "++" onewayFns (qvar (localName resolvedName) "onewayFunctions'") onewayFns = HS.List () $ map (stringLit . funName) $ filter funIsOneWay serviceFunctions commandTypeName :: HS Service -> Text commandTypeName Service{..} = serviceResolvedName <> "Command" isEmptyService :: HS Service -> Bool isEmptyService Service{..} = null serviceFunctions && isNothing serviceSuper