{-# LANGUAGE GADTs #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeOperators #-} module Data.Morpheus.Execution.Client.Selection ( operationTypes ) where import Data.Semigroup ( (<>) ) import Data.Text ( Text , pack ) -- -- MORPHEUS import Data.Morpheus.Error.Client.Client ( deprecatedField ) import Data.Morpheus.Error.Utils ( globalErrorMessage ) import Data.Morpheus.Execution.Internal.Utils ( nameSpaceType ) import Data.Morpheus.Types.Internal.AST ( Operation(..) , Key , Name , RAW , VALID , Variable(..) , VariableDefinitions , Selection(..) , SelectionContent(..) , SelectionSet , Ref(..) , FieldDefinition(..) , TypeContent(..) , TypeDefinition(..) , DataTypeKind(..) , Schema(..) , TypeRef(..) , DataEnumValue(..) , ConsD(..) , ClientType(..) , TypeD(..) , ArgumentsDefinition(..) , getOperationName , getOperationDataType , lookupDeprecated , lookupDeprecatedReason , typeFromScalar , removeDuplicates , Position , GQLErrors , UnionTag(..) ) import Data.Morpheus.Types.Internal.Operation ( Listable(..) , selectBy , keyOf ) import Data.Morpheus.Types.Internal.Resolving ( Eventless , Failure(..) , Result(..) , LibUpdater , resolveUpdates ) compileError :: Text -> GQLErrors compileError x = globalErrorMessage $ "Unhandled Compile Time Error: \"" <> x <> "\" ;" operationTypes :: Schema -> VariableDefinitions RAW -> Operation VALID -> Eventless (Maybe TypeD, [ClientType]) operationTypes lib variables = genOperation where genOperation operation@Operation { operationName, operationSelection } = do datatype <- getOperationDataType operation lib (queryTypes, enums) <- genRecordType [] (getOperationName operationName) datatype operationSelection inputTypeRequests <- resolveUpdates [] $ map (scanInputTypes lib . typeConName . variableType) (toList variables) inputTypesAndEnums <- buildListedTypes (inputTypeRequests <> enums) pure ( rootArguments (getOperationName operationName <> "Args") , queryTypes <> inputTypesAndEnums ) ------------------------------------------------------------------------- buildListedTypes = fmap concat . traverse (buildInputType lib) . removeDuplicates ------------------------------------------------------------------------- -- generates argument types for Operation Head rootArguments :: Text -> Maybe TypeD rootArguments argsName | null variables = Nothing | otherwise = Just rootArgumentsType where rootArgumentsType :: TypeD rootArgumentsType = TypeD { tName = argsName , tNamespace = [] , tCons = [ConsD { cName = argsName, cFields = map fieldD (toList variables) }] , tMeta = Nothing } where fieldD :: Variable RAW -> FieldDefinition fieldD Variable { variableName, variableType } = FieldDefinition { fieldName = variableName , fieldArgs = NoArguments , fieldType = variableType , fieldMeta = Nothing } --------------------------------------------------------- -- generates selection Object Types genRecordType :: [Name] -> Name -> TypeDefinition -> SelectionSet VALID -> Eventless ([ClientType], [Name]) genRecordType path tName dataType recordSelSet = do (con, subTypes, requests) <- genConsD tName dataType recordSelSet pure ( ClientType { clientType = TypeD { tName , tNamespace = path , tCons = [con] , tMeta = Nothing } , clientKind = KindObject Nothing } : subTypes , requests ) where genConsD :: Name -> TypeDefinition -> SelectionSet VALID -> Eventless (ConsD, [ClientType], [Text]) genConsD cName datatype selSet = do (cFields, subTypes, requests) <- unzip3 <$> traverse genField (toList selSet) pure (ConsD { cName, cFields }, concat subTypes, concat requests) where genField :: Selection VALID -> Eventless (FieldDefinition, [ClientType], [Text]) genField sel@Selection { selectionName , selectionPosition } = do (fieldDataType, fieldType) <- lookupFieldType lib fieldPath datatype selectionPosition selectionName (subTypes, requests) <- subTypesBySelection fieldDataType sel pure ( FieldDefinition { fieldName , fieldType , fieldArgs = NoArguments , fieldMeta = Nothing } , subTypes , requests ) where fieldPath = path <> [fieldName] ------------------------------- fieldName = keyOf sel ------------------------------------------ subTypesBySelection :: TypeDefinition -> Selection VALID -> Eventless ([ClientType], [Text]) subTypesBySelection dType Selection { selectionContent = SelectionField } = leafType dType --withLeaf buildLeaf dType subTypesBySelection dType Selection { selectionContent = SelectionSet selectionSet } = genRecordType fieldPath (typeFrom [] dType) dType selectionSet ---- UNION subTypesBySelection dType Selection { selectionContent = UnionSelection unionSelections } = do (tCons, subTypes, requests) <- unzip3 <$> traverse getUnionType (toList unionSelections) pure ( ClientType { clientType = TypeD { tNamespace = fieldPath , tName = typeFrom [] dType , tCons , tMeta = Nothing } , clientKind = KindUnion } : concat subTypes , concat requests ) where getUnionType (UnionTag selectedTyName selectionVariant) = do conDatatype <- getType lib selectedTyName genConsD selectedTyName conDatatype selectionVariant scanInputTypes :: Schema -> Key -> LibUpdater [Key] scanInputTypes lib name collected | name `elem` collected = pure collected | otherwise = getType lib name >>= scanInpType where scanInpType TypeDefinition { typeContent, typeName } = scanType typeContent where scanType (DataInputObject fields) = resolveUpdates (name : collected) (map toInputTypeD $ toList fields) where toInputTypeD :: FieldDefinition -> LibUpdater [Key] toInputTypeD FieldDefinition { fieldType = TypeRef { typeConName } } = scanInputTypes lib typeConName scanType (DataEnum _) = pure (collected <> [typeName]) scanType _ = pure collected buildInputType :: Schema -> Text -> Eventless [ClientType] buildInputType lib name = getType lib name >>= generateTypes where generateTypes TypeDefinition { typeName, typeContent } = subTypes typeContent where subTypes (DataInputObject inputFields) = do fields <- traverse toFieldD (toList inputFields) pure [ ClientType { clientType = TypeD { tName = typeName , tNamespace = [] , tCons = [ ConsD { cName = typeName , cFields = fields } ] , tMeta = Nothing } , clientKind = KindInputObject } ] where toFieldD :: FieldDefinition -> Eventless FieldDefinition toFieldD field@FieldDefinition { fieldType } = do typeConName <- typeFrom [] <$> getType lib (typeConName fieldType) pure $ field { fieldType = fieldType { typeConName } } subTypes (DataEnum enumTags) = pure [ ClientType { clientType = TypeD { tName = typeName , tNamespace = [] , tCons = map enumOption enumTags , tMeta = Nothing } , clientKind = KindEnum } ] where enumOption DataEnumValue { enumName } = ConsD { cName = enumName, cFields = [] } subTypes _ = pure [] lookupFieldType :: Schema -> [Key] -> TypeDefinition -> Position -> Text -> Eventless (TypeDefinition, TypeRef) lookupFieldType lib path TypeDefinition { typeContent = DataObject { objectFields }, typeName } refPosition key = selectBy selError key objectFields >>= processDeprecation where selError = compileError $ "cant find field \"" <> pack (show objectFields) <> "\"" processDeprecation FieldDefinition { fieldType = alias@TypeRef { typeConName }, fieldMeta } = checkDeprecated >> (trans <$> getType lib typeConName) where trans x = (x, alias { typeConName = typeFrom path x, typeArgs = Nothing }) ------------------------------------------------------------------ checkDeprecated :: Eventless () checkDeprecated = case fieldMeta >>= lookupDeprecated of Just deprecation -> Success { result = (), warnings, events = [] } where warnings = deprecatedField typeName Ref { refName = key, refPosition } (lookupDeprecatedReason deprecation) Nothing -> pure () lookupFieldType _ _ dt _ _ = failure (compileError $ "Type should be output Object \"" <> pack (show dt)) leafType :: TypeDefinition -> Eventless ([ClientType], [Text]) leafType TypeDefinition { typeName, typeContent } = fromKind typeContent where fromKind :: TypeContent -> Eventless ([ClientType], [Text]) fromKind DataEnum{} = pure ([], [typeName]) fromKind DataScalar{} = pure ([], []) fromKind _ = failure $ compileError "Invalid schema Expected scalar" getType :: Schema -> Text -> Eventless TypeDefinition getType lib typename = selectBy (compileError typename) typename lib typeFrom :: [Name] -> TypeDefinition -> Name typeFrom path TypeDefinition { typeName, typeContent } = __typeFrom typeContent where __typeFrom DataScalar{} = typeFromScalar typeName __typeFrom DataObject{} = nameSpaceType path typeName __typeFrom DataUnion{} = nameSpaceType path typeName __typeFrom _ = typeName