{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
module Data.Morpheus.Server.Document.Convert
( toTHDefinitions,
)
where
import Data.Morpheus.Internal.TH
( infoTyVars,
mkTypeName,
)
import Data.Morpheus.Internal.Utils
( capitalTypeName,
elems,
singleton,
)
import Data.Morpheus.Types.Internal.AST
( ANY,
ArgumentsDefinition (..),
ConsD,
DataTypeKind (..),
FieldDefinition (..),
FieldName,
FieldsDefinition,
GQLTypeD (..),
OUT,
TRUE,
TypeContent (..),
TypeD (..),
TypeDefinition (..),
TypeName,
TypeRef (..),
argumentsToFields,
hasArguments,
hsTypeName,
kindOf,
lookupWith,
mkCons,
mkConsEnum,
toFieldName,
)
import Data.Semigroup ((<>))
import Language.Haskell.TH
m_ :: TypeName
m_ = "m"
getTypeArgs :: TypeName -> [TypeDefinition ANY] -> Q (Maybe TypeName)
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 (mkTypeName key)
getTyArgs :: Info -> Maybe TypeName
getTyArgs x
| null (infoTyVars x) = Nothing
| otherwise = Just m_
kindToTyArgs :: TypeContent TRUE ANY -> Maybe TypeName
kindToTyArgs DataObject {} = Just m_
kindToTyArgs DataUnion {} = Just m_
kindToTyArgs DataInterface {} = Just m_
kindToTyArgs _ = Nothing
toTHDefinitions :: Bool -> [TypeDefinition ANY] -> Q [GQLTypeD]
toTHDefinitions namespace lib = traverse renderTHType lib
where
renderTHType :: TypeDefinition ANY -> Q GQLTypeD
renderTHType x = generateType x
where
genArgsTypeName :: FieldName -> TypeName
genArgsTypeName fieldName
| namespace = hsTypeName (typeName x) <> argTName
| otherwise = argTName
where
argTName = capitalTypeName (fieldName <> "Args")
genResField :: FieldDefinition OUT -> Q (FieldDefinition OUT)
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 ANY -> Q GQLTypeD
generateType typeOriginal@TypeDefinition {typeName, typeContent, typeMeta} =
genType
typeContent
where
buildType :: [ConsD] -> TypeD
buildType tCons =
TypeD
{ tName = hsTypeName typeName,
tMeta = typeMeta,
tNamespace = [],
tCons,
tKind
}
buildObjectCons :: FieldsDefinition cat -> [ConsD]
buildObjectCons fields = [mkCons typeName fields]
tKind = kindOf typeOriginal
genType :: TypeContent TRUE ANY -> Q GQLTypeD
genType (DataEnum tags) =
pure
GQLTypeD
{ typeD =
TypeD
{ tName = hsTypeName typeName,
tNamespace = [],
tCons = map mkConsEnum tags,
tMeta = typeMeta,
tKind
},
typeArgD = [],
..
}
genType DataScalar {} = fail "Scalar Types should defined By Native Haskell Types"
genType DataInputUnion {} = fail "Input Unions not Supported"
genType DataInterface {interfaceFields} = do
typeArgD <- concat <$> traverse (genArgumentType genArgsTypeName) (elems interfaceFields)
objCons <- buildObjectCons <$> traverse genResField interfaceFields
pure
GQLTypeD
{ typeD = buildType objCons,
typeArgD,
..
}
genType (DataInputObject fields) =
pure
GQLTypeD
{ typeD = buildType $ buildObjectCons fields,
typeArgD = [],
..
}
genType DataObject {objectFields} = do
typeArgD <- concat <$> traverse (genArgumentType genArgsTypeName) (elems objectFields)
objCons <- buildObjectCons <$> traverse genResField objectFields
pure
GQLTypeD
{ typeD = buildType objCons,
typeArgD,
..
}
genType (DataUnion members) =
pure
GQLTypeD
{ typeD = buildType (map unionCon members),
typeArgD = [],
..
}
where
unionCon memberName =
mkCons
cName
( singleton
FieldDefinition
{ fieldName = "un" <> toFieldName cName,
fieldType =
TypeRef
{ typeConName = utName,
typeArgs = Just m_,
typeWrappers = []
},
fieldMeta = Nothing,
fieldArgs = NoArguments
}
)
where
cName = hsTypeName typeName <> utName
utName = hsTypeName memberName
genArgumentType :: (FieldName -> TypeName) -> FieldDefinition OUT -> Q [TypeD]
genArgumentType _ FieldDefinition {fieldArgs = NoArguments} = pure []
genArgumentType namespaceWith FieldDefinition {fieldName, fieldArgs} =
pure
[ TypeD
{ tName,
tNamespace = [],
tCons =
[ mkCons tName (argumentsToFields fieldArgs)
],
tMeta = Nothing,
tKind = KindInputObject
}
]
where
tName = hsTypeName (namespaceWith fieldName)