morpheus-graphql-code-gen-0.27.0: src/Data/Morpheus/CodeGen/Server/Interpreting/Transform.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskellQuotes #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Data.Morpheus.CodeGen.Server.Interpreting.Transform
( parseServerTypeDefinitions,
)
where
import Data.ByteString.Lazy.Char8 (ByteString)
import Data.Morpheus.CodeGen.Internal.AST
( AssociatedType (..),
CodeGenConstructor (..),
CodeGenField (..),
CodeGenType (..),
CodeGenTypeName (CodeGenTypeName, typeParameters),
MethodArgument (..),
TypeClassInstance (..),
fromTypeName,
getFullName,
)
import Data.Morpheus.CodeGen.Server.Internal.AST
( CodeGenConfig (..),
DerivingClass (..),
FIELD_TYPE_WRAPPER (..),
GQLTypeDefinition (..),
InterfaceDefinition (..),
Kind (..),
ServerDeclaration (..),
ServerMethod (..),
)
import Data.Morpheus.CodeGen.Server.Interpreting.Directive
( getDefaultValueDir,
getDirectives,
getNamespaceDirs,
getRenameDir,
)
import Data.Morpheus.CodeGen.Server.Interpreting.Utils
( CodeGenM,
CodeGenMonad (printWarnings),
ServerCodeGenContext (..),
checkTypeExistence,
getEnumName,
getFieldName,
getFieldTypeName,
inType,
isParamResolverType,
isSubscription,
)
import Data.Morpheus.CodeGen.TH (ToName (..))
import Data.Morpheus.CodeGen.Utils
( Flag (..),
Flags,
camelCaseTypeName,
langExtension,
runCodeGenT,
toHaskellTypeName,
)
import Data.Morpheus.Core (parseDefinitions)
import Data.Morpheus.Error (renderGQLErrors)
import Data.Morpheus.Internal.Ext (Result (..))
import Data.Morpheus.Server.Types (Arg, DIRECTIVE_LOCATIONS, GQLDirective, GQLType (..), SubscriptionField)
import Data.Morpheus.Types.Internal.AST
( ANY,
ArgumentDefinition (..),
CONST,
DataEnumValue (..),
DirectiveDefinition (..),
FieldContent (..),
FieldDefinition (..),
FieldName,
FieldsDefinition,
IN,
OUT,
RawTypeDefinition (..),
TRUE,
TypeContent (..),
TypeDefinition (..),
TypeKind (..),
TypeName,
TypeRef (..),
UnionMember (..),
isNullable,
isPossibleInterfaceType,
isResolverType,
kindOf,
packName,
unpackName,
)
import Relude hiding (ByteString, get)
parseServerTypeDefinitions :: CodeGenMonad m => CodeGenConfig -> ByteString -> m ([ServerDeclaration], Flags)
parseServerTypeDefinitions ctx txt =
case parseDefinitions txt of
Failure errors -> fail (renderGQLErrors errors)
Success {result, warnings} -> printWarnings warnings >> toTHDefinitions (namespace ctx) result
getExternals :: [ServerDeclaration] -> Flags
getExternals xs =
[FlagExternal scalarTypeName | ScalarType {scalarTypeName} <- xs]
<> [FlagExternal (unpackName $ getFullName $ typeClassTarget v) | GQLTypeInstance Scalar v <- xs]
toTHDefinitions ::
CodeGenMonad m =>
Bool ->
[RawTypeDefinition] ->
m ([ServerDeclaration], Flags)
toTHDefinitions namespace defs = do
(types, flags) <- bimap concat concat . unzip <$> traverse generateTypes defs
pure (types, flags <> getExternals types)
where
typeDefinitions = [td | RawTypeDefinition td <- defs]
directiveDefinitions = [td | RawDirectiveDefinition td <- defs]
generateTypes :: CodeGenMonad m => RawTypeDefinition -> m ([ServerDeclaration], Flags)
generateTypes (RawTypeDefinition typeDef) =
runCodeGenT
(genTypeDefinition typeDef)
ServerCodeGenContext
{ toArgsTypeName = mkArgsTypeName namespace (typeName typeDef),
typeDefinitions,
directiveDefinitions,
currentTypeName = Just (packName $ toHaskellTypeName $ typeName typeDef),
currentKind = Just (kindOf typeDef),
hasNamespace = namespace
}
generateTypes (RawDirectiveDefinition DirectiveDefinition {..}) =
runCodeGenT
( do
fields <- traverse renderDataField (argument <$> toList directiveDefinitionArgs)
let typename = coerce directiveDefinitionName
namespaceDirs <- getNamespaceDirs (unpackName typename)
let cgTypeName = fromTypeName typename
pure
[ DataType
CodeGenType
{ cgTypeName,
cgConstructors = [CodeGenConstructor (fromTypeName typename) fields],
cgDerivations = [SHOW, GENERIC]
},
GQLDirectiveInstance
TypeClassInstance
{ typeClassName = ''GQLDirective,
typeClassContext = [],
typeClassTarget = cgTypeName,
assoc = [(''DIRECTIVE_LOCATIONS, AssociatedLocations directiveDefinitionLocations)],
typeClassMethods = []
},
gqlTypeToInstance
GQLTypeDefinition
{ gqlTarget = cgTypeName,
gqlKind = Type,
gqlTypeDirectiveUses = namespaceDirs
}
]
)
ServerCodeGenContext
{ toArgsTypeName = coerce,
typeDefinitions,
currentTypeName = Just (coerce directiveDefinitionName),
directiveDefinitions,
currentKind = Nothing,
hasNamespace = namespace
}
generateTypes _ = pure (mempty, mempty)
mkInterfaceName :: TypeName -> TypeName
mkInterfaceName = ("Interface" <>)
mkPossibleTypesName :: TypeName -> TypeName
mkPossibleTypesName = ("PossibleTypes" <>)
genTypeDefinition ::
CodeGenM m =>
TypeDefinition ANY CONST ->
m [ServerDeclaration]
genTypeDefinition
typeDef@TypeDefinition {typeName = originalTypeName, typeContent} =
case tKind of
KIND_SCALAR -> do
scalarGQLType <- deriveGQL
pure [ScalarType (toHaskellTypeName typeName), scalarGQLType]
_ -> genTypeContent originalTypeName typeContent >>= withType
where
typeName
| tKind == KIND_INTERFACE = mkInterfaceName originalTypeName
| otherwise = originalTypeName
tKind = kindOf typeDef
hsTypeName = packName $ toHaskellTypeName typeName
cgTypeName = CodeGenTypeName [] ["m" | isResolverType tKind] hsTypeName
deriveGQL = do
defaultValueDirs <- concat <$> traverse getDefaultValueDir (getInputFields typeDef)
namespaceDirs <- getNamespaceDirs (unpackName hsTypeName)
dirs <- getDirectives typeDef
renameDir <- getRenameDir originalTypeName hsTypeName
pure $
gqlTypeToInstance
GQLTypeDefinition
{ gqlTarget = cgTypeName,
gqlTypeDirectiveUses = renameDir <> namespaceDirs <> dirs <> defaultValueDirs,
gqlKind = derivingKind tKind
}
cgDerivations = derivesClasses (isResolverType tKind)
-------------------------
withType (ConsIN cgConstructors) = do
gqlType <- deriveGQL
pure [DataType CodeGenType {..}, gqlType]
withType (ConsOUT others cgConstructors) = do
gqlType <- deriveGQL
pure (DataType CodeGenType {..} : gqlType : others)
derivingKind :: TypeKind -> Kind
derivingKind KIND_SCALAR = Scalar
derivingKind _ = Type
derivesClasses :: Bool -> [DerivingClass]
derivesClasses isResolver = GENERIC : [SHOW | not isResolver]
mkObjectCons :: TypeName -> [CodeGenField] -> [CodeGenConstructor]
mkObjectCons name = pure . CodeGenConstructor (fromTypeName name)
mkArgsTypeName :: Bool -> TypeName -> FieldName -> TypeName
mkArgsTypeName namespace typeName fieldName
| namespace = typeName <> argTName
| otherwise = argTName
where
argTName = camelCaseTypeName [fieldName] "Args"
mkObjectField ::
CodeGenM m =>
FieldDefinition OUT CONST ->
m CodeGenField
mkObjectField
FieldDefinition
{ fieldName = fName,
fieldContent,
fieldType = TypeRef {typeConName, typeWrappers}
} = do
isParametrized <- isParamResolverType typeConName
genName <- asks toArgsTypeName
kind <- asks currentKind
fieldName <- getFieldName fName
args <- mkFieldArguments fName genName (toArgList fieldContent)
fieldType <- getFieldTypeName typeConName
pure
CodeGenField
{ fieldType,
fieldIsNullable = isNullable typeWrappers,
wrappers =
args
<> [SUBSCRIPTION ''SubscriptionField | fmap isSubscription kind == Just True]
<> [MONAD]
<> [GQL_WRAPPER typeWrappers]
<> [PARAMETRIZED | isParametrized],
..
}
mkFieldArguments :: CodeGenM m => FieldName -> (FieldName -> TypeName) -> [ArgumentDefinition s] -> m [FIELD_TYPE_WRAPPER]
mkFieldArguments _ _ [] = pure []
mkFieldArguments
_
_
[ ArgumentDefinition FieldDefinition {fieldName, fieldType}
] =
checkTypeExistence (typeConName fieldType)
>> langExtension "DataKinds" $> [TAGGED_ARG ''Arg fieldName fieldType]
mkFieldArguments fName genName _ = pure [ARG (genName fName)]
toArgList :: Maybe (FieldContent bool cat s) -> [ArgumentDefinition s]
toArgList (Just (FieldArgs args)) = toList args
toArgList _ = []
data BuildPlan
= ConsIN [CodeGenConstructor]
| ConsOUT [ServerDeclaration] [CodeGenConstructor]
gqlTypeToInstance :: GQLTypeDefinition -> ServerDeclaration
gqlTypeToInstance GQLTypeDefinition {..} =
GQLTypeInstance
gqlKind
TypeClassInstance
{ typeClassName = ''GQLType,
typeClassContext = map ((''Typeable,) . toName) (typeParameters gqlTarget),
typeClassTarget = gqlTarget,
assoc = [(''KIND, AssociatedTypeName (toName gqlKind))],
typeClassMethods =
[ ('directives, ProxyArgument, ServerMethodDirectives gqlTypeDirectiveUses)
| not (null gqlTypeDirectiveUses)
]
}
genInterfaceUnion :: CodeGenM m => TypeName -> m [ServerDeclaration]
genInterfaceUnion interfaceName =
asks typeDefinitions >>= mkInterface . map typeName . mapMaybe (isPossibleInterfaceType interfaceName)
where
mkInterface [] = pure []
mkInterface [possibleTypeName] = pure [mkGuardWithPossibleType possibleTypeName]
mkInterface members = do
cgConstructors <- traverse (mkUnionFieldDefinition tName) members
pure
[ mkGuardWithPossibleType tName,
DataType
CodeGenType
{ cgTypeName = possTypeName,
cgConstructors,
cgDerivations = derivesClasses True
},
gqlTypeToInstance
GQLTypeDefinition
{ gqlTarget = possTypeName,
gqlKind = Type,
gqlTypeDirectiveUses = empty
}
]
where
possTypeName = CodeGenTypeName [] ["m"] (packName $ toHaskellTypeName tName)
mkGuardWithPossibleType = InterfaceType . InterfaceDefinition interfaceName (mkInterfaceName interfaceName)
tName = mkPossibleTypesName interfaceName
mkConsEnum :: CodeGenM m => DataEnumValue CONST -> m CodeGenConstructor
mkConsEnum DataEnumValue {enumName} = do
constructorName <- getEnumName enumName
pure CodeGenConstructor {constructorName, constructorFields = []}
renderDataField :: CodeGenM m => FieldDefinition c CONST -> m CodeGenField
renderDataField FieldDefinition {fieldType = TypeRef {typeConName, typeWrappers}, fieldName = fName} = do
fieldName <- getFieldName fName
let wrappers = [GQL_WRAPPER typeWrappers]
fieldType <- getFieldTypeName typeConName
let fieldIsNullable = isNullable typeWrappers
pure CodeGenField {..}
genTypeContent :: CodeGenM m => TypeName -> TypeContent TRUE ANY CONST -> m BuildPlan
genTypeContent _ DataScalar {} = pure (ConsIN [])
genTypeContent _ (DataEnum tags) = ConsIN <$> traverse mkConsEnum tags
genTypeContent typeName (DataInputObject fields) =
ConsIN . mkObjectCons typeName <$> traverse renderDataField (toList fields)
genTypeContent _ DataInputUnion {} = fail "Input Unions not Supported"
genTypeContent typeName DataInterface {interfaceFields} =
ConsOUT
<$> ((<>) <$> genArgumentTypes interfaceFields <*> genInterfaceUnion typeName)
<*> ( do
let interfaceName = mkInterfaceName typeName
inType
(Just interfaceName)
( mkObjectCons interfaceName
<$> traverse mkObjectField (toList interfaceFields)
)
)
genTypeContent typeName DataObject {objectFields} =
ConsOUT
<$> genArgumentTypes objectFields
<*> ( mkObjectCons typeName
<$> traverse mkObjectField (toList objectFields)
)
genTypeContent typeName (DataUnion members) = do
ConsOUT [] <$> traverse unionCon (toList members)
where
unionCon UnionMember {memberName} = mkUnionFieldDefinition typeName memberName
mkUnionFieldDefinition :: CodeGenM m => TypeName -> TypeName -> m CodeGenConstructor
mkUnionFieldDefinition typeName memberName = do
fieldType <- getFieldTypeName memberName
pure $
CodeGenConstructor
{ constructorName,
constructorFields =
[ CodeGenField
{ fieldName = "_",
fieldType,
wrappers = [MONAD, PARAMETRIZED],
fieldIsNullable = False
}
]
}
where
constructorName = CodeGenTypeName [coerce typeName] [] memberName
genArgumentTypes :: CodeGenM m => FieldsDefinition OUT CONST -> m [ServerDeclaration]
genArgumentTypes = fmap concat . traverse genArgumentType . toList
genArgumentType :: CodeGenM m => FieldDefinition OUT CONST -> m [ServerDeclaration]
genArgumentType
FieldDefinition
{ fieldName,
fieldContent = Just (FieldArgs arguments)
}
| length arguments > 1 = do
tName <- (fieldName &) <$> asks toArgsTypeName
inType (Just tName) $ do
let argumentFields = argument <$> toList arguments
fields <- traverse renderDataField argumentFields
let typename = toHaskellTypeName tName
namespaceDirs <- getNamespaceDirs typename
dirs <- concat <$> traverse getDirectives argumentFields
let cgTypeName = fromTypeName (packName typename)
defaultValueDirs <- concat <$> traverse getDefaultValueDir argumentFields
pure
[ DataType
CodeGenType
{ cgTypeName,
cgConstructors = mkObjectCons tName fields,
cgDerivations = derivesClasses False
},
gqlTypeToInstance
GQLTypeDefinition
{ gqlTarget = cgTypeName,
gqlKind = Type,
gqlTypeDirectiveUses = namespaceDirs <> dirs <> defaultValueDirs
}
]
genArgumentType _ = pure []
getInputFields :: TypeDefinition c s -> [FieldDefinition IN s]
getInputFields TypeDefinition {typeContent = DataInputObject {inputObjectFields}} = toList inputObjectFields
getInputFields _ = []