{-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE TypeFamilies #-} module Data.Morpheus.Validation.Document.Interface ( validateImplements, ) where import Control.Monad.Except (throwError) import Data.Morpheus.Error.Document.Interface ( ImplementsError (..), partialImplements, ) import Data.Morpheus.Internal.Utils ( KeyOf (..), empty, selectOr, ) import Data.Morpheus.Types.Internal.AST ( ArgumentDefinition (..), ArgumentsDefinition, CONST, FieldContent (..), FieldDefinition (..), FieldsDefinition, OUT, Subtyping (..), TRUE, TypeName, TypeRef (..), ) import Data.Morpheus.Types.Internal.Validation ( ValidatorContext (localContext), selectType, ) import Data.Morpheus.Types.Internal.Validation.SchemaValidator ( Field (..), ON_INTERFACE, ON_TYPE, PLACE, SchemaValidator, TypeEntity (..), TypeSystemContext (..), constraintInterface, inArgument, inField, inInterface, ) import Relude hiding (empty, local) validateImplements :: [TypeName] -> FieldsDefinition OUT CONST -> SchemaValidator (TypeEntity ON_TYPE) [TypeName] validateImplements interfaceNames objectFields = traverse (selectType >=> constraintInterface >=> hasCompatibleFields) interfaceNames $> interfaceNames where hasCompatibleFields :: (TypeName, FieldsDefinition OUT CONST) -> SchemaValidator (TypeEntity ON_TYPE) () hasCompatibleFields (typeName, fields) = inInterface typeName $ isCompatibleTo objectFields fields class StructuralCompatibility a where type Context a :: PLACE -> Type type Context a = Field -- Object (which implements interface) -> Interface -> Validation isCompatibleTo :: a -> a -> SchemaValidator ((Context a) ON_INTERFACE) () isCompatibleBy :: (t -> a) -> t -> t -> SchemaValidator ((Context a) ON_INTERFACE) () isCompatibleBy f a b = f a `isCompatibleTo` f b instance StructuralCompatibility (FieldsDefinition OUT s) where type Context (FieldsDefinition OUT s) = TypeEntity isCompatibleTo objFields = traverse_ checkInterfaceField where checkInterfaceField interfaceField@FieldDefinition {fieldName} = inField fieldName $ selectOr err (`isCompatibleTo` interfaceField) fieldName objFields where err = failImplements Missing instance StructuralCompatibility (FieldDefinition OUT s) where f1 `isCompatibleTo` f2 = isCompatibleBy fieldType f1 f2 *> isCompatibleBy (fieldArgs . fieldContent) f1 f2 fieldArgs :: Maybe (FieldContent TRUE OUT s) -> ArgumentsDefinition s fieldArgs (Just (FieldArgs args)) = args fieldArgs _ = empty instance StructuralCompatibility (ArgumentsDefinition s) where subArguments `isCompatibleTo` arguments = traverse_ hasCompatibleSubArgument arguments where hasCompatibleSubArgument argument = inArgument (keyOf argument) $ selectOr (failImplements Missing) (`isCompatibleTo` argument) (keyOf argument) subArguments instance StructuralCompatibility (ArgumentDefinition s) where isCompatibleTo = isCompatibleBy (fieldType . argument) instance StructuralCompatibility TypeRef where t1 `isCompatibleTo` t2 | t1 `isSubtype` t2 = pure () | otherwise = failImplements UnexpectedType {expectedType = t2, foundType = t1} failImplements :: ImplementsError -> SchemaValidator (Field ON_INTERFACE) a failImplements err = do x <- asks (local . localContext) throwError $ partialImplements x err