{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE FlexibleContexts #-} module Data.Morpheus.Rendering.RenderIntrospection ( render , createObjectType ) where import Data.Semigroup ( (<>) ) import Data.Text ( Text ) import Data.Maybe ( isJust ) -- Morpheus import Data.Morpheus.Schema.Schema import Data.Morpheus.Schema.TypeKind ( TypeKind(..) ) import Data.Morpheus.Types.Internal.AST ( DataField(..) , DataTyCon(..) , DataType(..) , DataTypeKind(..) , DataTypeLib , DataTypeWrapper(..) , DataUnion , Meta(..) , TypeAlias(..) , createInputUnionFields , fieldVisibility , kindOf , lookupDataType , toGQLWrapper , DataEnumValue(..) , lookupDeprecated , lookupDeprecatedReason , convertToJSONName ) import Data.Morpheus.Types.Internal.Resolving ( Failure(..) ) constRes :: Applicative m => a -> b -> m a constRes = const . pure type Result m a = DataTypeLib -> m a class RenderSchema a b where render :: (Monad m, Failure Text m) => (Text, a) -> DataTypeLib -> m (b m) instance RenderSchema DataType S__Type where render (key, DataScalar DataTyCon { typeMeta }) = constRes $ createLeafType SCALAR key typeMeta Nothing render (key, DataEnum DataTyCon { typeMeta, typeData }) = constRes $ createLeafType ENUM key typeMeta (Just $ map createEnumValue typeData) render (name, DataInputObject DataTyCon { typeData, typeMeta }) = renderInputObject where renderInputObject lib = do fields <- traverse (`renderinputValue` lib) typeData pure $ createInputObject name typeMeta fields render (name, DataObject object') = typeFromObject (name, object') where typeFromObject (key, DataTyCon { typeData, typeMeta }) lib = createObjectType key (typeMeta >>= metaDescription) <$> (Just <$> traverse (`render` lib) (filter fieldVisibility typeData)) render (name, DataUnion union) = constRes $ typeFromUnion (name, union) render (name, DataInputUnion inpUnion') = renderInputUnion (name, inpUnion') createEnumValue :: Monad m => DataEnumValue -> S__EnumValue m createEnumValue DataEnumValue { enumName, enumMeta } = S__EnumValue { s__EnumValueName = constRes enumName , s__EnumValueDescription = constRes (enumMeta >>= metaDescription) , s__EnumValueIsDeprecated = constRes (isJust deprecated) , s__EnumValueDeprecationReason = constRes (deprecated >>= lookupDeprecatedReason) } where deprecated = enumMeta >>= lookupDeprecated instance RenderSchema DataField S__Field where render (name, field@DataField { fieldType = TypeAlias { aliasTyCon }, fieldArgs, fieldMeta }) lib = do kind <- renderTypeKind <$> lookupKind aliasTyCon lib args <- traverse (`renderinputValue` lib) fieldArgs pure S__Field { s__FieldName = constRes (convertToJSONName name) , s__FieldDescription = constRes (fieldMeta >>= metaDescription) , s__FieldArgs = constRes args , s__FieldType' = constRes (wrap field $ createType kind aliasTyCon Nothing $ Just []) , s__FieldIsDeprecated = constRes (isJust deprecated) , s__FieldDeprecationReason = constRes (deprecated >>= lookupDeprecatedReason) } where deprecated = fieldMeta >>= lookupDeprecated renderTypeKind :: DataTypeKind -> TypeKind renderTypeKind KindScalar = SCALAR renderTypeKind (KindObject _) = OBJECT renderTypeKind KindUnion = UNION renderTypeKind KindInputUnion = INPUT_OBJECT renderTypeKind KindEnum = ENUM renderTypeKind KindInputObject = INPUT_OBJECT renderTypeKind KindList = LIST renderTypeKind KindNonNull = NON_NULL wrap :: Monad m => DataField -> S__Type m -> S__Type m wrap DataField { fieldType = TypeAlias { aliasWrappers } } typ = foldr wrapByTypeWrapper typ (toGQLWrapper aliasWrappers) wrapByTypeWrapper :: Monad m => DataTypeWrapper -> S__Type m -> S__Type m wrapByTypeWrapper ListType = wrapAs LIST wrapByTypeWrapper NonNullType = wrapAs NON_NULL lookupKind :: (Monad m, Failure Text m) => Text -> Result m DataTypeKind lookupKind name lib = case lookupDataType name lib of Nothing -> failure $ "Kind Not Found: " <> name Just value -> pure (kindOf value) renderinputValue :: (Monad m, Failure Text m) => (Text, DataField) -> Result m (S__InputValue m) renderinputValue (key, input) = fmap (createInputValueWith key (fieldMeta input)) . createInputObjectType input createInputObjectType :: (Monad m, Failure Text m) => DataField -> Result m (S__Type m) createInputObjectType field@DataField { fieldType = TypeAlias { aliasTyCon } } lib = do kind <- renderTypeKind <$> lookupKind aliasTyCon lib pure $ wrap field $ createType kind aliasTyCon Nothing $ Just [] renderInputUnion :: (Monad m, Failure Text m) => (Text, DataUnion) -> Result m (S__Type m) renderInputUnion (key, DataTyCon { typeData, typeMeta }) lib = createInputObject key typeMeta <$> traverse createField (createInputUnionFields key typeData) where createField (name, field) = createInputValueWith name Nothing <$> createInputObjectType field lib createLeafType :: Monad m => TypeKind -> Text -> Maybe Meta -> Maybe [S__EnumValue m] -> S__Type m createLeafType kind name meta enums = S__Type { s__TypeKind = constRes kind , s__TypeName = constRes $ Just name , s__TypeDescription = constRes (meta >>= metaDescription) , s__TypeFields = constRes Nothing , s__TypeOfType = constRes Nothing , s__TypeInterfaces = constRes Nothing , s__TypePossibleTypes = constRes Nothing , s__TypeEnumValues = constRes enums , s__TypeInputFields = constRes Nothing } typeFromUnion :: Monad m => (Text, DataUnion) -> S__Type m typeFromUnion (name, DataTyCon { typeData, typeMeta }) = S__Type { s__TypeKind = constRes UNION , s__TypeName = constRes $ Just name , s__TypeDescription = constRes (typeMeta >>= metaDescription) , s__TypeFields = constRes Nothing , s__TypeOfType = constRes Nothing , s__TypeInterfaces = constRes Nothing , s__TypePossibleTypes = constRes $ Just (map (\x -> createObjectType x Nothing $ Just []) typeData) , s__TypeEnumValues = constRes Nothing , s__TypeInputFields = constRes Nothing } createObjectType :: Monad m => Text -> Maybe Text -> Maybe [S__Field m] -> S__Type m createObjectType name description fields = S__Type { s__TypeKind = constRes OBJECT , s__TypeName = constRes $ Just name , s__TypeDescription = constRes description , s__TypeFields = constRes fields , s__TypeOfType = constRes Nothing , s__TypeInterfaces = constRes $ Just [] , s__TypePossibleTypes = constRes Nothing , s__TypeEnumValues = constRes Nothing , s__TypeInputFields = constRes Nothing } createInputObject :: Monad m => Text -> Maybe Meta -> [S__InputValue m] -> S__Type m createInputObject name meta fields = S__Type { s__TypeKind = constRes INPUT_OBJECT , s__TypeName = constRes $ Just name , s__TypeDescription = constRes (meta >>= metaDescription) , s__TypeFields = constRes Nothing , s__TypeOfType = constRes Nothing , s__TypeInterfaces = constRes Nothing , s__TypePossibleTypes = constRes Nothing , s__TypeEnumValues = constRes Nothing , s__TypeInputFields = constRes $ Just fields } createType :: Monad m => TypeKind -> Text -> Maybe Text -> Maybe [S__Field m] -> S__Type m createType kind name description fields = S__Type { s__TypeKind = constRes kind , s__TypeName = constRes $ Just name , s__TypeDescription = constRes description , s__TypeFields = constRes fields , s__TypeOfType = constRes Nothing , s__TypeInterfaces = constRes Nothing , s__TypePossibleTypes = constRes Nothing , s__TypeEnumValues = constRes $ Just [] , s__TypeInputFields = constRes Nothing } wrapAs :: Monad m => TypeKind -> S__Type m -> S__Type m wrapAs kind contentType = S__Type { s__TypeKind = constRes kind , s__TypeName = constRes Nothing , s__TypeDescription = constRes Nothing , s__TypeFields = constRes Nothing , s__TypeOfType = constRes $ Just contentType , s__TypeInterfaces = constRes Nothing , s__TypePossibleTypes = constRes Nothing , s__TypeEnumValues = constRes Nothing , s__TypeInputFields = constRes Nothing } createInputValueWith :: Monad m => Text -> Maybe Meta -> S__Type m -> S__InputValue m createInputValueWith name meta ivType = S__InputValue { s__InputValueName = constRes (convertToJSONName name) , s__InputValueDescription = constRes (meta >>= metaDescription) , s__InputValueType' = constRes ivType , s__InputValueDefaultValue = constRes Nothing }