morpheus-graphql-client-0.28.2: src/Data/Morpheus/Client/CodeGen/Interpreting/Local.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Data.Morpheus.Client.CodeGen.Interpreting.Local
( toLocalDefinitions,
)
where
import Data.Morpheus.Client.CodeGen.AST
( ClientDeclaration (..),
ClientPreDeclaration (..),
RequestTypeDefinition (..),
UnionPat (..),
)
import Data.Morpheus.Client.CodeGen.Interpreting.Arguments (genArguments)
import Data.Morpheus.Client.CodeGen.Interpreting.Core
( LocalContext (..),
LocalM,
clientConfig,
defaultDerivations,
deprecationWarning,
existFragment,
getNameByPath,
getType,
lookupField,
lookupType,
registerFragment,
removeDuplicates,
typeFrom,
warning,
)
import Data.Morpheus.Client.CodeGen.Interpreting.PreDeclarations
( mapPreDeclarations,
)
import Data.Morpheus.CodeGen.Internal.AST
( CodeGenConstructor (..),
CodeGenField (..),
CodeGenType (..),
CodeGenTypeName (..),
FIELD_TYPE_WRAPPER (..),
fromTypeName,
getFullName,
)
import Data.Morpheus.CodeGen.Utils
( Flags,
langExtension,
requireExternal,
runCodeGenT,
)
import Data.Morpheus.Core (validateRequest)
import Data.Morpheus.Error (deprecatedField)
import Data.Morpheus.Internal.Ext
( GQLResult,
)
import Data.Morpheus.Internal.Utils
( keyOf,
member,
)
import Data.Morpheus.Types.Internal.AST
( ANY,
ExecutableDocument (..),
FieldDefinition (..),
FieldName,
FragmentName,
Operation (..),
Position (..),
PropName (..),
Schema (..),
Selection (..),
SelectionContent (..),
SelectionSet,
TypeDefinition (..),
TypeKind (..),
TypeName,
TypeRef (..),
UnionTag (..),
VALID,
at,
getOperationDataType,
getOperationName,
isNullable,
kindOf,
msg,
toAny,
unpackName,
withPath,
)
import qualified Data.Set as S
import qualified Data.Text as T
import Relude hiding (empty, show)
toLocalDefinitions :: (Text, ExecutableDocument) -> Schema VALID -> GQLResult ([ClientDeclaration], Flags)
toLocalDefinitions (query, request) ctxSchema = do
validOperation <- validateRequest clientConfig ctxSchema request
let context =
LocalContext
{ ctxSchema,
ctxVariables = operationArguments $ operation request,
ctxPosition = Nothing,
ctxFragments = mempty
}
(t, flags) <- runCodeGenT (genLocalDeclarations query validOperation) context
types <- removeDuplicates <$> traverse mapPreDeclarations t
pure (types, flags)
genLocalDeclarations :: Text -> Operation VALID -> LocalM [ClientPreDeclaration]
genLocalDeclarations query op@Operation {operationName, operationSelection, operationType} = do
LocalContext {ctxSchema, ctxVariables} <- asks id
datatype <- getOperationDataType op ctxSchema
let operationTypeName = getOperationName operationName
let (requestArgs, argTypes) = genArguments operationTypeName ctxVariables
(rootTypeName, localTypes) <- genLocalTypes [coerce operationTypeName] operationTypeName (toAny datatype) operationSelection
pure
( RequestTypeClass
RequestTypeDefinition
{ requestArgs = requestArgs,
requestName = typename rootTypeName,
requestType = operationType,
requestQuery = T.unpack query
}
: localTypes <> argTypes
)
genLocalTypes ::
[FieldName] ->
TypeName ->
TypeDefinition ANY VALID ->
SelectionSet VALID ->
LocalM (CodeGenTypeName, [ClientPreDeclaration])
genLocalTypes namespace tName = genObjectTypeWithFragment namespace (getNameByPath namespace tName)
subTypesBySelection ::
TypeName ->
[FieldName] ->
TypeDefinition ANY VALID ->
Selection VALID ->
LocalM (CodeGenTypeName, [ClientPreDeclaration])
subTypesBySelection name _ _ Selection {selectionContent = SelectionField} = do
kind <- fmap kindOf <$> lookupType name
if null kind || kind == Just KIND_SCALAR
then requireExternal (unpackName name) $> (fromTypeName name, [])
else pure (fromTypeName name, [])
subTypesBySelection _ path dType Selection {selectionContent = SelectionSet selectionSet} =
genLocalTypes path (getFullName $ typeFrom [] dType) dType selectionSet
subTypesBySelection _ namespace dType Selection {selectionPosition, selectionContent = UnionSelection interface unionSelections} =
do
let variants = toList unionSelections
traverse_ (checkTypename selectionPosition namespace interface) variants
let cgTypeName = typeFrom namespace dType
(cons, subTypes) <- unzip <$> traverse (getVariant namespace) variants
(fallbackCons, fallBackTypes) <- maybe (getEmptyFallback cgTypeName) (getVariant namespace . UnionTag (typeName dType)) interface
let typeDef = CodeGenType {cgTypeName, cgConstructors = map buildVariantConstructor (cons <> [fallbackCons]), cgDerivations = defaultDerivations}
langExtension "LambdaCase"
pure (cgTypeName, [ClientType typeDef, FromJSONUnionClass cgTypeName (map tagConstructor cons <> [(UVar "_fallback", mapFallback fallbackCons)])] <> concat subTypes <> fallBackTypes)
where
tagConstructor (name, x) = (UString $ typename name, (name, fmap (const "v") x))
mapFallback (x, y) = (x, fmap (const "v") y)
checkTypename :: Position -> [FieldName] -> Maybe (SelectionSet VALID) -> UnionTag -> LocalM ()
checkTypename pos path iFace UnionTag {..}
| any (member "__typename") (unionTagSelection : toList iFace) = pure ()
| otherwise =
warning
$ withPath
("missing \"__typename\" for selection " <> msg unionTagName <> ". this can lead to undesired behavior at runtime!")
(map (PropName . unpackName) path)
`at` pos
type Variant = (CodeGenTypeName, Maybe TypeName)
getEmptyFallback :: CodeGenTypeName -> LocalM (Variant, [ClientPreDeclaration])
getEmptyFallback name = pure ((name, Nothing), [])
buildVariantConstructor :: Variant -> CodeGenConstructor
buildVariantConstructor (conName, ref) =
CodeGenConstructor
{ constructorName = conName,
constructorFields =
( \fieldType ->
CodeGenField
{ fieldName = "_",
fieldType,
wrappers = [],
fieldIsNullable = False
}
)
<$> toList ref
}
getVariant :: [FieldName] -> UnionTag -> LocalM (Variant, [ClientPreDeclaration])
getVariant path (UnionTag selectedTyName selectionVariant) = do
-- traceShow (map getSelectionOrigins variants)
conDatatype <- getType selectedTyName
let name = CodeGenTypeName path [] selectedTyName
(n, types) <- genObjectTypeWithFragment path name conDatatype selectionVariant
pure
( ( CodeGenTypeName (path <> ["variant"]) [] selectedTyName,
Just (getFullName n)
),
types
)
getFragmentOrigin :: SelectionSet VALID -> Maybe FragmentName
getFragmentOrigin x = case toList (S.fromList (map selectionOrigin $ toList x)) of
[Just name] -> Just name
_ -> Nothing
genObjectTypeWithFragment ::
[FieldName] ->
CodeGenTypeName ->
TypeDefinition ANY VALID ->
SelectionSet VALID ->
LocalM (CodeGenTypeName, [ClientPreDeclaration])
genObjectTypeWithFragment namespace cgTypeName dataType recordSelSet = do
case getFragmentOrigin recordSelSet of
Just name -> do
exists <- existFragment name
let tName = CodeGenTypeName [] [] ("Fragment" <> coerce name)
if exists
then pure (tName, [])
else registerFragment name (genObjectType namespace tName dataType recordSelSet)
Nothing -> genObjectType namespace cgTypeName dataType recordSelSet
genObjectType ::
[FieldName] ->
CodeGenTypeName ->
TypeDefinition ANY VALID ->
SelectionSet VALID ->
LocalM (CodeGenTypeName, [ClientPreDeclaration])
genObjectType namespace cgTypeName datatype selSet = do
(fields, subTypes) <- unzip <$> traverse (genField namespace datatype) (toList selSet)
let constructor = CodeGenConstructor {constructorName = cgTypeName, constructorFields = fields}
let definition = CodeGenType {cgTypeName, cgConstructors = [constructor], cgDerivations = defaultDerivations}
pure (cgTypeName, [ClientType definition, FromJSONObjectClass cgTypeName constructor] <> concat subTypes)
genField :: [FieldName] -> TypeDefinition ANY VALID -> Selection VALID -> LocalM (CodeGenField, [ClientPreDeclaration])
genField path datatype sel = do
let fieldName = keyOf sel
let fieldPath = path <> [fieldName]
(fieldDataType, TypeRef {..}) <- getFieldType fieldPath datatype sel
(fieldTypeName, subTypes) <- subTypesBySelection typeConName fieldPath fieldDataType sel
pure
( CodeGenField
{ fieldName,
fieldType = getFullName fieldTypeName,
wrappers = [GQL_WRAPPER typeWrappers],
fieldIsNullable = isNullable typeWrappers
},
subTypes
)
getFieldType ::
[FieldName] ->
TypeDefinition ANY VALID ->
Selection VALID ->
LocalM (TypeDefinition ANY VALID, TypeRef)
getFieldType
path
TypeDefinition {typeContent, typeName}
Selection
{ selectionName,
selectionPosition
} = lookupField selectionName typeContent >>= processFieldDefinition
where
processFieldDefinition FieldDefinition {fieldType = TypeRef {..}, ..} = do
deprecationWarning fieldWarnings fieldDirectives
typeDef <- getType typeConName
pure (typeDef, TypeRef {typeConName = getFullName (typeFrom path typeDef), ..})
where
fieldWarnings reason =
( deprecatedField typeName selectionName reason
`at` selectionPosition
)
`withPath` map (PropName . unpackName) path