Safe Haskell | None |
---|---|
Language | Haskell2010 |
Remote Schema Permissions Validation
This module parses the GraphQL IDL (Schema Document) that's provided by the user for configuring permissions for remote schemas to a schema introspection object, which is then used to construct the remote schema for the particular role.
This module does two things essentially:
- Checks if the given schema document is a subset of the upstream remote schema document. This is done by checking if all the objects, interfaces, unions, enums, scalars and input objects provided in the schema document exist in the upstream remote schema too. We validate the fields, directives and arguments too, wherever applicable.
- Parse the
preset
directives (if any) on input object fields or argument fields. Apreset
directive is used to specify any preset argument on a field, it can be either a static value or session variable value. There is some validation done on preset directives. For example: - Preset directives can only be specified atARGUMENT_DEFINITION
orINPUT_FIELD_DEFINITION
- A field expecting object cannot have a scalar/enum preset directive and vice versa.
If a preset directive value is a session variable (like `x-hasura-*`), then it's
considered to be a session variable value. In the case, the user wants to treat the
session variable value literally, they can add the static
key to the preset directive
to indicate that the value provided should be considered literally. For example:
`user(id: Int @preset(value: "x-hasura-user-id", static: true))
In this case `x-hasura-user-id` will be considered literally.
For validation, we use the MonadValidate
monad transformer to collect as many errors
as possible and then report all those errors at one go to the user.
Synopsis
- data FieldDefinitionType
- data ArgumentDefinitionType
- data PresetInputTypeInfo
- = PresetScalar Name
- | PresetEnum Name [EnumValue]
- | PresetInputObject [InputValueDefinition]
- data GraphQLType
- data RoleBasedSchemaValidationError
- = NonMatchingType Name GraphQLType GType GType
- | TypeDoesNotExist GraphQLType Name
- | NonMatchingDefaultValue Name Name (Maybe (Value Void)) (Maybe (Value Void))
- | NonExistingInputArgument Name Name
- | MissingNonNullableArguments Name (NonEmpty Name)
- | NonExistingDirectiveArgument Name GraphQLType Name (NonEmpty Name)
- | NonExistingField (FieldDefinitionType, Name) Name
- | NonExistingUnionMemberTypes Name (NonEmpty Name)
- | CustomInterfacesNotAllowed Name (NonEmpty Name)
- | ObjectImplementsNonExistingInterfaces Name (NonEmpty Name)
- | NonExistingEnumValues Name (NonEmpty Name)
- | MultipleSchemaDefinitionsFound
- | MissingQueryRoot
- | DuplicateTypeNames (NonEmpty Name)
- | DuplicateDirectives (GraphQLType, Name) (NonEmpty Name)
- | DuplicateFields (FieldDefinitionType, Name) (NonEmpty Name)
- | DuplicateArguments Name (NonEmpty Name)
- | DuplicateEnumValues Name (NonEmpty Name)
- | InvalidPresetDirectiveLocation
- | MultiplePresetDirectives (GraphQLType, Name)
- | NoPresetArgumentFound
- | InvalidPresetArgument Name
- | ExpectedInputTypeButGotOutputType Name
- | EnumValueNotFound Name Name
- | ExpectedEnumValue Name (Value Void)
- | KeyDoesNotExistInInputObject Name Name
- | ExpectedInputObject Name (Value Void)
- | ExpectedScalarValue Name (Value Void)
- | DisallowSessionVarForListType Name
- | InvalidStaticValue
- | UnexpectedNonMatchingNames Name Name GraphQLType
- showRoleBasedSchemaValidationError :: RoleBasedSchemaValidationError -> Text
- lookupInputType :: SchemaDocument -> Name -> Maybe PresetInputTypeInfo
- parsePresetValue :: forall m. (MonadValidate [RoleBasedSchemaValidationError] m, MonadReader SchemaDocument m) => GType -> Name -> Bool -> Value Void -> m (Value RemoteSchemaVariable)
- parsePresetDirective :: forall m. (MonadValidate [RoleBasedSchemaValidationError] m, MonadReader SchemaDocument m) => GType -> Name -> Directive Void -> m (Value RemoteSchemaVariable)
- validateDirective :: MonadValidate [RoleBasedSchemaValidationError] m => Directive a -> Directive a -> (GraphQLType, Name) -> m ()
- validateDirectives :: MonadValidate [RoleBasedSchemaValidationError] m => [Directive a] -> [Directive a] -> TypeSystemDirectiveLocation -> (GraphQLType, Name) -> m (Maybe (Directive a))
- validateEnumTypeDefinition :: MonadValidate [RoleBasedSchemaValidationError] m => EnumTypeDefinition -> EnumTypeDefinition -> m EnumTypeDefinition
- validateInputValueDefinition :: (MonadValidate [RoleBasedSchemaValidationError] m, MonadReader SchemaDocument m) => InputValueDefinition -> InputValueDefinition -> Name -> m RemoteSchemaInputValueDefinition
- validateArguments :: (MonadValidate [RoleBasedSchemaValidationError] m, MonadReader SchemaDocument m) => ArgumentsDefinition InputValueDefinition -> ArgumentsDefinition RemoteSchemaInputValueDefinition -> Name -> m [RemoteSchemaInputValueDefinition]
- validateInputObjectTypeDefinition :: (MonadValidate [RoleBasedSchemaValidationError] m, MonadReader SchemaDocument m) => InputObjectTypeDefinition InputValueDefinition -> InputObjectTypeDefinition RemoteSchemaInputValueDefinition -> m (InputObjectTypeDefinition RemoteSchemaInputValueDefinition)
- validateFieldDefinition :: (MonadValidate [RoleBasedSchemaValidationError] m, MonadReader SchemaDocument m) => FieldDefinition InputValueDefinition -> FieldDefinition RemoteSchemaInputValueDefinition -> (FieldDefinitionType, Name) -> m (FieldDefinition RemoteSchemaInputValueDefinition)
- validateFieldDefinitions :: (MonadValidate [RoleBasedSchemaValidationError] m, MonadReader SchemaDocument m) => [FieldDefinition InputValueDefinition] -> [FieldDefinition RemoteSchemaInputValueDefinition] -> (FieldDefinitionType, Name) -> m [FieldDefinition RemoteSchemaInputValueDefinition]
- validateInterfaceDefinition :: (MonadValidate [RoleBasedSchemaValidationError] m, MonadReader SchemaDocument m) => InterfaceTypeDefinition () InputValueDefinition -> InterfaceTypeDefinition [Name] RemoteSchemaInputValueDefinition -> m (InterfaceTypeDefinition () RemoteSchemaInputValueDefinition)
- validateScalarDefinition :: MonadValidate [RoleBasedSchemaValidationError] m => ScalarTypeDefinition -> ScalarTypeDefinition -> m ScalarTypeDefinition
- validateUnionDefinition :: MonadValidate [RoleBasedSchemaValidationError] m => UnionTypeDefinition -> UnionTypeDefinition -> m UnionTypeDefinition
- validateObjectDefinition :: (MonadValidate [RoleBasedSchemaValidationError] m, MonadReader SchemaDocument m) => ObjectTypeDefinition InputValueDefinition -> ObjectTypeDefinition RemoteSchemaInputValueDefinition -> HashSet Name -> m (ObjectTypeDefinition RemoteSchemaInputValueDefinition)
- validateSchemaDefinitions :: MonadValidate [RoleBasedSchemaValidationError] m => [SchemaDefinition] -> m (Maybe Name, Maybe Name, Maybe Name)
- createPossibleTypesMap :: [ObjectTypeDefinition RemoteSchemaInputValueDefinition] -> HashMap Name [Name]
- partitionTypeSystemDefinitions :: [TypeSystemDefinition] -> ([SchemaDefinition], [TypeDefinition () InputValueDefinition])
- getSchemaDocIntrospection :: [TypeDefinition () RemoteSchemaInputValueDefinition] -> (Maybe Name, Maybe Name, Maybe Name) -> IntrospectionResult
- validateRemoteSchema :: (MonadValidate [RoleBasedSchemaValidationError] m, MonadReader SchemaDocument m) => RemoteSchemaIntrospection -> m IntrospectionResult
- resolveRoleBasedRemoteSchema :: MonadError QErr m => SchemaDocument -> RemoteSchemaCtx -> m (IntrospectionResult, [SchemaDependency])
Documentation
data PresetInputTypeInfo Source #
PresetScalar Name | |
PresetEnum Name [EnumValue] | |
PresetInputObject [InputValueDefinition] |
data GraphQLType Source #
data RoleBasedSchemaValidationError Source #
NonMatchingType Name GraphQLType GType GType | error to indicate that a type provided by the user differs from the corresponding type defined in the upstream remote schema |
TypeDoesNotExist GraphQLType Name | error to indicate when a type definition doesn't exist in the upstream remote schema |
NonMatchingDefaultValue Name Name (Maybe (Value Void)) (Maybe (Value Void)) | error to indicate when the default value of an argument differs from the default value of the corresponding argument |
NonExistingInputArgument Name Name | error to indicate when a given input argument doesn't exist in the corresponding upstream input object |
MissingNonNullableArguments Name (NonEmpty Name) | |
NonExistingDirectiveArgument Name GraphQLType Name (NonEmpty Name) | error to indicate when a given directive argument doesn't exist in the corresponding upstream directive |
NonExistingField (FieldDefinitionType, Name) Name | error to indicate when a given field doesn't exist in a field type (Object/Interface) |
NonExistingUnionMemberTypes Name (NonEmpty Name) | error to indicate when member types of an Union don't exist in the corresponding upstream union |
CustomInterfacesNotAllowed Name (NonEmpty Name) | error to indicate when an object is trying to implement an interface which exists in the schema document but the interface doesn't exist in the upstream remote. |
ObjectImplementsNonExistingInterfaces Name (NonEmpty Name) | error to indicate when object implements interfaces that don't exist |
NonExistingEnumValues Name (NonEmpty Name) | error to indicate enum values in an enum do not exist in the corresponding upstream enum |
MultipleSchemaDefinitionsFound | error to indicate when the user provided schema contains more than one schema definition |
MissingQueryRoot | error to indicate when the schema definition doesn't contain the query root. |
DuplicateTypeNames (NonEmpty Name) | |
DuplicateDirectives (GraphQLType, Name) (NonEmpty Name) | |
DuplicateFields (FieldDefinitionType, Name) (NonEmpty Name) | |
DuplicateArguments Name (NonEmpty Name) | |
DuplicateEnumValues Name (NonEmpty Name) | |
InvalidPresetDirectiveLocation | |
MultiplePresetDirectives (GraphQLType, Name) | |
NoPresetArgumentFound | |
InvalidPresetArgument Name | |
ExpectedInputTypeButGotOutputType Name | |
EnumValueNotFound Name Name | |
ExpectedEnumValue Name (Value Void) | |
KeyDoesNotExistInInputObject Name Name | |
ExpectedInputObject Name (Value Void) | |
ExpectedScalarValue Name (Value Void) | |
DisallowSessionVarForListType Name | |
InvalidStaticValue | |
UnexpectedNonMatchingNames Name Name GraphQLType | Error to indicate we're comparing non corresponding type definitions. Ideally, this error will never occur unless there's a programming error |
lookupInputType :: SchemaDocument -> Name -> Maybe PresetInputTypeInfo Source #
parsePresetValue :: forall m. (MonadValidate [RoleBasedSchemaValidationError] m, MonadReader SchemaDocument m) => GType -> Name -> Bool -> Value Void -> m (Value RemoteSchemaVariable) Source #
parsePresetValue
constructs a GraphQL value when an input value definition
contains a preset with it. This function checks if the given preset value
is a legal value to the field that's specified it. For example: A scalar input
value cannot contain an input object value. When the preset value is a session
variable, we treat it as a session variable whose value will be resolved while
the query is executed. In the case of session variables preset, we make the GraphQL
value as a Variable value and during the execution we resolve all these
"session variable" variable(s) and then query the remote server.
parsePresetDirective :: forall m. (MonadValidate [RoleBasedSchemaValidationError] m, MonadReader SchemaDocument m) => GType -> Name -> Directive Void -> m (Value RemoteSchemaVariable) Source #
:: MonadValidate [RoleBasedSchemaValidationError] m | |
=> Directive a | provided directive |
-> Directive a | upstream directive |
-> (GraphQLType, Name) | parent type and name |
-> m () |
validateDirective checks if the arguments of a given directive
is a subset of the corresponding upstream directive arguments
*NOTE*: This function assumes that the providedDirective
and the
upstreamDirective
have the same name.
validateDirectives :: MonadValidate [RoleBasedSchemaValidationError] m => [Directive a] -> [Directive a] -> TypeSystemDirectiveLocation -> (GraphQLType, Name) -> m (Maybe (Directive a)) Source #
validateDirectives checks if the providedDirectives
are a subset of upstreamDirectives
and then validate
each of the directives by calling the validateDirective
validateEnumTypeDefinition Source #
:: MonadValidate [RoleBasedSchemaValidationError] m | |
=> EnumTypeDefinition | provided enum type definition |
-> EnumTypeDefinition | upstream enum type definition |
-> m EnumTypeDefinition |
validateEnumTypeDefinition
checks the validity of an enum definition
provided by the user against the corresponding upstream enum.
The function does the following things:
1. Validates the directives (if any)
2. For each enum provided, check if the enum values are a subset of
the enum values of the corresponding upstream enum
*NOTE*: This function assumes that the providedEnum
and the upstreamEnum
have the same name.
validateInputValueDefinition :: (MonadValidate [RoleBasedSchemaValidationError] m, MonadReader SchemaDocument m) => InputValueDefinition -> InputValueDefinition -> Name -> m RemoteSchemaInputValueDefinition Source #
validateInputValueDefinition
validates a given input value definition
, against the corresponding upstream input value definition. Two things
are validated to do the same, the type and the default value of the
input value definitions should be equal.
validateArguments :: (MonadValidate [RoleBasedSchemaValidationError] m, MonadReader SchemaDocument m) => ArgumentsDefinition InputValueDefinition -> ArgumentsDefinition RemoteSchemaInputValueDefinition -> Name -> m [RemoteSchemaInputValueDefinition] Source #
validateArguments
validates the provided arguments against the corresponding
upstream remote schema arguments.
validateInputObjectTypeDefinition :: (MonadValidate [RoleBasedSchemaValidationError] m, MonadReader SchemaDocument m) => InputObjectTypeDefinition InputValueDefinition -> InputObjectTypeDefinition RemoteSchemaInputValueDefinition -> m (InputObjectTypeDefinition RemoteSchemaInputValueDefinition) Source #
validateFieldDefinition :: (MonadValidate [RoleBasedSchemaValidationError] m, MonadReader SchemaDocument m) => FieldDefinition InputValueDefinition -> FieldDefinition RemoteSchemaInputValueDefinition -> (FieldDefinitionType, Name) -> m (FieldDefinition RemoteSchemaInputValueDefinition) Source #
validateFieldDefinitions Source #
:: (MonadValidate [RoleBasedSchemaValidationError] m, MonadReader SchemaDocument m) | |
=> [FieldDefinition InputValueDefinition] | |
-> [FieldDefinition RemoteSchemaInputValueDefinition] | |
-> (FieldDefinitionType, Name) | parent type and name |
-> m [FieldDefinition RemoteSchemaInputValueDefinition] |
validateInterfaceDefinition :: (MonadValidate [RoleBasedSchemaValidationError] m, MonadReader SchemaDocument m) => InterfaceTypeDefinition () InputValueDefinition -> InterfaceTypeDefinition [Name] RemoteSchemaInputValueDefinition -> m (InterfaceTypeDefinition () RemoteSchemaInputValueDefinition) Source #
validateScalarDefinition :: MonadValidate [RoleBasedSchemaValidationError] m => ScalarTypeDefinition -> ScalarTypeDefinition -> m ScalarTypeDefinition Source #
validateUnionDefinition :: MonadValidate [RoleBasedSchemaValidationError] m => UnionTypeDefinition -> UnionTypeDefinition -> m UnionTypeDefinition Source #
validateObjectDefinition Source #
:: (MonadValidate [RoleBasedSchemaValidationError] m, MonadReader SchemaDocument m) | |
=> ObjectTypeDefinition InputValueDefinition | |
-> ObjectTypeDefinition RemoteSchemaInputValueDefinition | |
-> HashSet Name | Interfaces declared by in the role-based schema |
-> m (ObjectTypeDefinition RemoteSchemaInputValueDefinition) |
validateSchemaDefinitions :: MonadValidate [RoleBasedSchemaValidationError] m => [SchemaDefinition] -> m (Maybe Name, Maybe Name, Maybe Name) Source #
helper function to validate the schema definitions mentioned in the schema document.
createPossibleTypesMap :: [ObjectTypeDefinition RemoteSchemaInputValueDefinition] -> HashMap Name [Name] Source #
Construction of the possibleTypes
map for interfaces, while parsing the
user provided Schema document, it doesn't include the possibleTypes
, so
constructing here, manually.
partitionTypeSystemDefinitions :: [TypeSystemDefinition] -> ([SchemaDefinition], [TypeDefinition () InputValueDefinition]) Source #
getSchemaDocIntrospection :: [TypeDefinition () RemoteSchemaInputValueDefinition] -> (Maybe Name, Maybe Name, Maybe Name) -> IntrospectionResult Source #
getSchemaDocIntrospection converts the PartitionedTypeDefinitions
to
IntrospectionResult
because the function buildRemoteParser
function which
builds the remote schema parsers accepts an IntrospectionResult
. The
conversion involves converting `G.TypeDefinition ()` to `G.TypeDefinition
[G.Name]`. The `[G.Name]` here being the list of object names that an
interface implements. This is needed to be done here by-hand because while
specifying the SchemaDocument
through the GraphQL DSL, it doesn't include
the possibleTypes
along with an object.
validateRemoteSchema :: (MonadValidate [RoleBasedSchemaValidationError] m, MonadReader SchemaDocument m) => RemoteSchemaIntrospection -> m IntrospectionResult Source #
validateRemoteSchema accepts two arguments, the SchemaDocument
of
the role-based schema, that is provided by the user and the SchemaIntrospection
of the upstream remote schema. This function, in turn calls the other validation
functions for scalars, enums, unions, interfaces,input objects and objects.
resolveRoleBasedRemoteSchema :: MonadError QErr m => SchemaDocument -> RemoteSchemaCtx -> m (IntrospectionResult, [SchemaDependency]) Source #