View Source graphql_check (graphql v0.17.1)
Type checking of GraphQL query documents
The type checker carries out three tasks:
Make sure that types check. That is, the user supplies a well typed query document.
Make sure that types are properly inferred. Some times, the types the user supply needs an inference pass in order to figure out what the user supplied. The type-checker also infers the possible types and makes sure the query document is well typed.
Handle coercion for the constant fragment of a query document. Whenever a coercible constant value is encountered in a query document, or a coercible parameter occurs in a parameter, the type checker runs "input coercion" which is part canonicalization, part input validation on input data. The coerced value is expanded into the query document or parameter string, so the execution engine always works with coerced data. This choice is somewhat peculiar, but it serves an optimization purpose since we only have to carry out a coercion once for a query with constant values.
Polarity:
This type checker mentions polarity of types. There are 3 kinds of polarity: positive, negative and non-polar. The flows of these are that Client -> Server is positive and Server -> Client is negative. Non-polar types flows both ways. Since the server engine doesn't trust the client, type checking follows some polarity rules. If we check a positive polarity context, we don't trust the client and we use the schema data to verify that everything is covered by the client in a valid way. If we check in negative polarity context, we are the server and can trust things are correct. So we fold over the query document when considering if types are correct. Non-polar values fall naturally in both contexts.
Algorithm:
We use a bidirectional type checker. In general we handle two kinds of typing constructs: G |- e ==> t
(inference) and G |- e <== t, e
(checking) The first of these gets G,e as inputs and derives a t. The second form gets G, e, and t as inputs and derives e' which is an e annotated with more information.
By having these two forms, the type checking algorithm can switch between elaboration and lookup of data and checking that the types are correct. The type checker can thus handle a query in one checking pass over the structure rather than having to rely on two.
Summary
Types
-type ctx() :: #ctx{endpoint_ctx :: endpoint_context(), path :: [any()], vars :: #{binary() => #vardef{id :: graphql:name(), ty :: graphql_type(), default :: undefined | value()}}, frags :: #{binary() => #frag{id :: '...' | graphql:name(), ty :: undefined | graphql_base_type() | graphql_check:ty(), directives :: [graphql:directive()], selection_set :: [#field{id :: graphql:name(), args :: [{binary(), #{type := graphql_type(), value := value(), default := undefined | value()}}], directives :: [any()], selection_set :: [any()], alias :: undefined | graphql:name(), schema :: any()}], schema :: undefined | any()}}, sub_context :: query | variable}.
-type directive_location() ::
'QUERY' | 'MUTATION' | 'SUBSCRIPTION' | 'FIELD' | 'FRAGMENT_DEFINITION' | 'FRAGMENT_SPREAD' |
'INLINE_FRAGMENT' | 'SCHEMA' | 'SCALAR' | 'OBJECT' | 'FIELD_DEFINITION' |
'ARGUMENT_DEFINITION' | 'INTERFACE' | 'UNION' | 'ENUM' | 'ENUM_VALUE' | 'INPUT_OBJECT' |
'INPUT_FIELD_DEFINITION'.
-type directive_type() :: #directive_type{id :: binary(), description :: undefined | binary(), locations :: [directive_location()], args :: #{binary() => schema_arg()}}.
-type enum_type() :: #enum_type{id :: binary(), description :: binary(), resolve_module :: mod(), directives :: [graphql:directive()], values :: #{integer() => enum_value()}}.
-type enum_value() :: #enum_value{val :: binary(), description :: binary(), directives :: [graphql:directive()], deprecation :: undefined | binary()}.
-type graphql_base_type() :: graphql:name() | binary().
-type graphql_type() :: {non_null, graphql_type()} | {list, graphql_type()} | graphql_base_type().
-type input_object_type() :: #input_object_type{id :: binary(), description :: binary(), directives :: [graphql:directive()], fields :: #{binary() => schema_arg()}}.
-type interface_type() :: #interface_type{id :: binary(), description :: binary(), resolve_type :: mod() | fun((any()) -> {ok, atom()} | {error, term()}), directives :: [graphql:directive()], fields :: #{binary() => schema_field()}}.
-type mod() :: atom().
-type object_type() :: #object_type{id :: binary(), description :: binary(), directives :: [graphql:directive()], resolve_module :: mod(), fields :: #{binary() => schema_field()}, interfaces :: [binary()]}.
-type operation_type() :: {query, pos_integer()} | {mutation, pos_integer()} | {subscription, pos_integer()}.
-type polarity() :: '+' | '-' | '*'.
-type resolver() :: fun((ctx, term(), binary(), resolver_args()) -> term()).
-type scalar_type() :: #scalar_type{id :: binary(), description :: binary(), directives :: [graphql:directive()], resolve_module :: mod()}.
-type schema_arg() :: #schema_arg{ty :: schema_type(), default :: any(), description :: binary(), directives :: [graphql:directive()]}.
-type schema_base_type() :: scalar_type() | enum_type() | binary().
-type schema_field() :: #schema_field{ty :: schema_type(), description :: binary() | undefined, resolve :: undefined | resolver(), deprecation :: undefined | binary(), directives :: [graphql:directive()], args :: #{binary() => schema_arg()}}.
-type schema_object() :: object_type() | interface_type() | scalar_type() | input_object_type() | union_type() | enum_type() | directive_type() | root_schema().
-type schema_type() :: {non_null, schema_base_type()} | {non_null, {list, schema_base_type()}} | {list, schema_base_type()} | schema_base_type().
-type ty() :: schema_type() | schema_object().
-type ty_name() :: binary().
-type union_type() :: #union_type{id :: binary(), description :: binary(), resolve_type :: mod() | fun((any()) -> {ok, atom()} | {error, term()}), directives :: [graphql:directive()], types :: [binary() | {name, non_neg_integer(), binary()}]}.
-type value() :: graphql:name() | null | {int, integer(), pos_integer()} | {float, float(), pos_integer()} | {string, binary(), pos_integer()} | {bool, true | false, pos_integer()} | {enum, binary()} | {list, value()} | {object, [value()]}.