{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE RecordWildCards #-}
module Data.Morpheus.Execution.Document.Convert
( toTHDefinitions
)
where
import Data.Semigroup ( (<>) )
import Data.Text (unpack)
import Language.Haskell.TH
import Data.Morpheus.Types.Internal.TH (infoTyVars)
import Data.Morpheus.Execution.Internal.Utils
( capital )
import Data.Morpheus.Types.Internal.AST
( FieldDefinition(..)
, TypeContent(..)
, TypeDefinition(..)
, TypeRef(..)
, DataEnumValue(..)
, ConsD(..)
, GQLTypeD(..)
, TypeD(..)
, Key
, InputFieldsDefinition(..)
, ArgumentsDefinition(..)
, hasArguments
, lookupWith
, toHSFieldDefinition
, hsTypeName
, kindOf
)
import Data.Morpheus.Types.Internal.Operation
(Listable(..))
m_ :: Key
m_ = "m"
getTypeArgs :: Key -> [TypeDefinition] -> Q (Maybe Key)
getTypeArgs "__TypeKind" _ = pure Nothing
getTypeArgs "Boolean" _ = pure Nothing
getTypeArgs "String" _ = pure Nothing
getTypeArgs "Int" _ = pure Nothing
getTypeArgs "Float" _ = pure Nothing
getTypeArgs key lib = case typeContent <$> lookupWith typeName key lib of
Just x -> pure (kindToTyArgs x)
Nothing -> getTyArgs <$> reify (mkName $ unpack key)
getTyArgs :: Info -> Maybe Key
getTyArgs x
| null (infoTyVars x) = Nothing
| otherwise = Just m_
kindToTyArgs :: TypeContent -> Maybe Key
kindToTyArgs DataObject{} = Just m_
kindToTyArgs DataUnion{} = Just m_
kindToTyArgs _ = Nothing
toTHDefinitions :: Bool -> [TypeDefinition] -> Q [GQLTypeD]
toTHDefinitions namespace lib = traverse renderTHType lib
where
renderTHType :: TypeDefinition -> Q GQLTypeD
renderTHType x = generateType x
where
genArgsTypeName :: Key -> Key
genArgsTypeName fieldName | namespace = hsTypeName (typeName x) <> argTName
| otherwise = argTName
where argTName = capital fieldName <> "Args"
genResField :: FieldDefinition -> Q FieldDefinition
genResField field@FieldDefinition { fieldName, fieldArgs, fieldType = typeRef@TypeRef { typeConName } }
= do
typeArgs <- getTypeArgs typeConName lib
pure $ field
{ fieldType = typeRef { typeConName = hsTypeName typeConName, typeArgs }
, fieldArgs = fieldArguments
}
where
fieldArguments
| hasArguments fieldArgs = fieldArgs { argumentsTypename = Just $ genArgsTypeName fieldName }
| otherwise = fieldArgs
generateType :: TypeDefinition -> Q GQLTypeD
generateType typeOriginal@TypeDefinition { typeName, typeContent, typeMeta } = genType
typeContent
where
buildType :: [ConsD] -> TypeD
buildType tCons = TypeD
{ tName = hsTypeName typeName
, tMeta = typeMeta
, tNamespace = []
, tCons
}
buildObjectCons :: [FieldDefinition] -> [ConsD]
buildObjectCons cFields =
[ ConsD
{ cName = hsTypeName typeName
, cFields
}
]
typeKindD = kindOf typeOriginal
genType :: TypeContent -> Q GQLTypeD
genType (DataEnum tags) = pure GQLTypeD
{ typeD = TypeD { tName = hsTypeName typeName
, tNamespace = []
, tCons = map enumOption tags
, tMeta = typeMeta
}
, typeArgD = []
, ..
}
where
enumOption DataEnumValue { enumName } =
ConsD { cName = hsTypeName enumName, cFields = [] }
genType DataScalar {} = fail "Scalar Types should defined By Native Haskell Types"
genType DataInputUnion {} = fail "Input Unions not Supported"
genType DataInterface {} = fail "interfaces must be eliminated in Validation"
genType (DataInputObject fields) = pure GQLTypeD
{ typeD = buildType $ buildObjectCons $ genInputFields fields
, typeArgD = []
, ..
}
genType DataObject {objectFields} = do
typeArgD <- concat <$> traverse (genArgumentType genArgsTypeName) (toList objectFields)
objCons <- buildObjectCons <$> traverse genResField (toList objectFields)
pure GQLTypeD
{ typeD = buildType objCons
, typeArgD
, ..
}
genType (DataUnion members) =
pure GQLTypeD
{ typeD = buildType (map unionCon members)
, typeArgD = []
, ..
}
where
unionCon memberName = ConsD
{ cName
, cFields = [ FieldDefinition
{ fieldName = "un" <> cName
, fieldType = TypeRef { typeConName = utName
, typeArgs = Just m_
, typeWrappers = []
}
, fieldMeta = Nothing
, fieldArgs = NoArguments
}
]
}
where
cName = hsTypeName typeName <> utName
utName = hsTypeName memberName
genArgumentType :: (Key -> Key) -> FieldDefinition -> Q [TypeD]
genArgumentType _ FieldDefinition { fieldArgs = NoArguments } = pure []
genArgumentType namespaceWith FieldDefinition { fieldName, fieldArgs } = pure
[ TypeD
{ tName
, tNamespace = []
, tCons = [ ConsD { cName = hsTypeName tName
, cFields = genArguments fieldArgs
}
]
, tMeta = Nothing
}
]
where tName = namespaceWith (hsTypeName fieldName)
genArguments :: ArgumentsDefinition -> [FieldDefinition]
genArguments = genInputFields . InputFieldsDefinition . arguments
genInputFields :: InputFieldsDefinition -> [FieldDefinition]
genInputFields = map toHSFieldDefinition . toList