diff --git a/src/graphql.erl b/src/graphql.erl index 167b0cf..f6d6834 100644 --- a/src/graphql.erl +++ b/src/graphql.erl @@ -9,10 +9,12 @@ %% GraphQL Documents -export([ parse/1, - type_check/1, type_check_params/3, - insert_root/1, + type_check/1, type_check/2, + type_check_params/3, type_check_params/4, + insert_root/1, insert_root/2, validate/1, - execute/1, execute/2 + execute/1, execute/2, execute/3, + get_endpoint/1 ]). -export([ @@ -34,9 +36,9 @@ %% Schema Definitions -export([ - load_schema/2, - insert_schema_definition/1, - validate_schema/0 + load_schema/2, load_schema/3, + insert_schema_definition/1, insert_schema_definition/2, + validate_schema/0, validate_schema/1 ]). %% Internal @@ -47,7 +49,7 @@ -type schema_definition() :: {atom(), #{ atom() => term() }}. --export_type([json/0, param_context/0]). +-export_type([json/0, param_context/0, endpoint_context/0]). -type token() :: {'$graphql_token', pid(), reference(), reference()}. -type defer_map() :: #{ worker => pid(), @@ -126,14 +128,18 @@ parse(Input) when is_list(Input) -> {error, {scanner_error, Err}} end. -load_schema(Mapping, Input) when is_binary(Input) -> - load_schema(Mapping, binary_to_list(Input)); -load_schema(Mapping, Input) when is_list(Input) -> +load_schema(Mapping, Input) -> + Ep = graphql_schema:get_endpoint_ctx(), + load_schema(Ep, Mapping, Input). + +load_schema(Ep, Mapping, Input) when is_binary(Input) -> + load_schema(Ep, Mapping, binary_to_list(Input)); +load_schema(Ep, Mapping, Input) when is_list(Input) -> case graphql_scanner:string(Input) of {ok, Tokens, _EndLine} -> case graphql_parser:parse(Tokens) of {ok, _} = Result -> - graphql_schema_parse:inject(Mapping, Result); + graphql_schema_parse:inject(Ep, Mapping, Result); {error, Err} -> {error, Err} end; @@ -147,11 +153,22 @@ validate(AST) -> -spec type_check(document()) -> {ok, #{ atom() => term() }}. type_check(AST) -> - graphql_check:check(AST). + Ep = graphql_schema:get_endpoint_ctx(), + type_check(Ep, AST). + +-spec type_check(endpoint_context(), document()) -> {ok, #{ atom() => term() }}. +type_check(Ep, AST) -> + graphql_check:check(Ep, AST). + -spec type_check_params(any(), any(), any()) -> param_context(). type_check_params(FunEnv, OpName, Vars) -> - graphql_check:check_params(FunEnv, OpName, Vars). + Ep = graphql_schema:get_endpoint_ctx(), + type_check_params(Ep, FunEnv, OpName, Vars). + +-spec type_check_params(endpoint_context(), any(), any(), any()) -> param_context(). +type_check_params(Ep, FunEnv, OpName, Vars) -> + graphql_check:check_params(Ep, FunEnv, OpName, Vars). -spec execute(document()) -> #{ atom() => json() }. execute(AST) -> @@ -159,29 +176,54 @@ execute(AST) -> execute(Ctx, AST). -spec execute(context(), document()) -> #{ atom() => json() }. -execute(#{default_timeout := _DT } = Ctx, AST) -> - graphql_execute:x(Ctx, AST); execute(Ctx, AST) -> - case graphql_execute:x(Ctx#{ default_timeout => ?DEFAULT_TIMEOUT}, AST) of + Ep = graphql_schema:get_endpoint_ctx(), + execute(Ep, Ctx, AST). + +-spec execute(endpoint_context(), context(), document()) -> #{ atom() => json() }. +execute(Ep, #{default_timeout := _DT } = Ctx, AST) -> + graphql_execute:x(Ep, Ctx, AST); +execute(Ep, Ctx, AST) -> + case graphql_execute:x(Ep, Ctx#{ default_timeout => ?DEFAULT_TIMEOUT}, AST) of #{ errors := Errs } = Result -> Result#{ errors := graphql_err:format_errors(Ctx, Errs) }; Result -> Result end. +-spec get_endpoint(atom()) -> endpoint_context(). +get_endpoint(Name) -> + graphql_schema:get_endpoint_ctx(Name). + %% @doc insert_schema_definition/1 loads a schema definition into the Graph Schema %% @end -spec insert_schema_definition(schema_definition()) -> ok | {error, Reason} when Reason :: term(). insert_schema_definition(Defn) -> - graphql_schema:load(Defn). + Ep = graphql_schema:get_endpoint_ctx(), + insert_schema_definition(Ep, Defn). + +-spec insert_schema_definition(endpoint_context(), schema_definition()) -> ok | {error, Reason} + when Reason :: term(). +insert_schema_definition(Ep, Defn) -> + graphql_schema:load(Ep, Defn). -spec insert_root(schema_definition()) -> ok. insert_root(Defn) -> + Ep = graphql_schema:get_endpoint_ctx(), + insert_root(Ep, Defn). + +-spec insert_root(endpoint_context(), schema_definition()) -> ok. +insert_root(Ep, Defn) -> Root = graphql_schema_canonicalize:x(Defn), - Schema = graphql_schema_validate:root(Root), - graphql_schema:load_schema(Schema). + Schema = graphql_schema_validate:root(Ep, Root), + graphql_schema:load_schema(Ep, Schema). %% STUB for now -spec validate_schema() -> ok | {error, any()}. validate_schema() -> - graphql_schema_validate:x(). + Ep = graphql_schema:get_endpoint_ctx(), + graphql_schema_validate:x(Ep). + +-spec validate_schema(endpoint_context()) -> ok | {error, any()}. +validate_schema(Ep) -> + graphql_schema_validate:x(Ep). diff --git a/src/graphql_builtins.erl b/src/graphql_builtins.erl index 33ab83f..ad14ce1 100644 --- a/src/graphql_builtins.erl +++ b/src/graphql_builtins.erl @@ -2,10 +2,10 @@ -include("graphql_schema.hrl"). --export([standard_types_inject/0, standard_directives_inject/0]). +-export([standard_types_inject/1, standard_directives_inject/1]). --spec standard_types_inject() -> ok. -standard_types_inject() -> +-spec standard_types_inject(endpoint_context()) -> ok. +standard_types_inject(Ep) -> String = {scalar, #{ id => 'String', description => <<"UTF-8 Text Strings"/utf8>> }}, @@ -21,15 +21,15 @@ standard_types_inject() -> ID = {scalar, #{ id => 'ID', description => <<"Representation of an opaque ID in the system. Always returned/given as strings, but clients are not allowed to deconstruct them. The server might change them as it sees fit later on, and the clients must be able to handle this situation."/utf8>> }}, - ok = graphql:insert_schema_definition(String), - ok = graphql:insert_schema_definition(Float), - ok = graphql:insert_schema_definition(Int), - ok = graphql:insert_schema_definition(Bool), - ok = graphql:insert_schema_definition(ID), + ok = graphql:insert_schema_definition(Ep, String), + ok = graphql:insert_schema_definition(Ep, Float), + ok = graphql:insert_schema_definition(Ep, Int), + ok = graphql:insert_schema_definition(Ep, Bool), + ok = graphql:insert_schema_definition(Ep, ID), ok. --spec standard_directives_inject() -> ok. -standard_directives_inject() -> +-spec standard_directives_inject(endpoint_context()) -> ok. +standard_directives_inject(Ep) -> SkipDirective = {directive, #{ id => <<"skip">>, description => <<"Allows excluding a field depending on argument">>, @@ -60,7 +60,7 @@ standard_directives_inject() -> default => <<"No longer supported">>, description => <<"A message to the developer on why this field is deprecated and what to use instead">> }} }}, - ok = graphql:insert_schema_definition(SkipDirective), - ok = graphql:insert_schema_definition(IncludeDirective), - ok = graphql:insert_schema_definition(DeprecatedDirective), + ok = graphql:insert_schema_definition(Ep, SkipDirective), + ok = graphql:insert_schema_definition(Ep, IncludeDirective), + ok = graphql:insert_schema_definition(Ep, DeprecatedDirective), ok. diff --git a/src/graphql_check.erl b/src/graphql_check.erl index 3a6beac..d1ee924 100644 --- a/src/graphql_check.erl +++ b/src/graphql_check.erl @@ -54,11 +54,12 @@ -include("graphql_internal.hrl"). -include("graphql_schema.hrl"). --export([check/1, check_params/3]). --export([funenv/1]). +-export([check/2, check_params/4]). +-export([funenv/2]). -record(ctx, { + endpoint_ctx :: endpoint_context(), path = [] :: [any()], vars = #{} :: #{ binary() => #vardef{} }, frags = #{} :: #{ binary() => #frag{} }, @@ -90,42 +91,42 @@ %% Elaborate a type and also determine its polarity. This is used for %% input and output types -spec infer_type(ctx(), ty_name() | ty()) -> {ok, {polarity(), ty()}}. -infer_type(Ctx, Tau) -> - case infer_type(Tau) of +infer_type(Ctx = #ctx{endpoint_ctx = Ep}, Tau) -> + case infer_type_(Ep, Tau) of {error, Reason} -> err(Ctx, Reason); {Polarity, TauPrime} -> {ok, {Polarity, TauPrime}} end. --spec infer_type(ty_name() | ty()) -> {polarity(), ty()} | {error, Reason :: term()}. -infer_type({non_null, Ty}) -> - case infer_type(Ty) of +-spec infer_type_(endpoint_context(), ty_name() | ty()) -> {polarity(), ty()} | {error, Reason :: term()}. +infer_type_(Ep, {non_null, Ty}) -> + case infer_type_(Ep, Ty) of {error, Reason} -> {error, Reason}; {Polarity, V} -> {Polarity, {non_null, V}} end; -infer_type({list, Ty}) -> - case infer_type(Ty) of +infer_type_(Ep, {list, Ty}) -> + case infer_type_(Ep, Ty) of {error, Reason} -> {error, Reason}; {Polarity, V} -> {Polarity, {list, V}} end; -infer_type({scalar, Name}) -> - #scalar_type{} = Ty = graphql_schema:get(Name), - {_polarity, Ty} = infer_type(Ty); +infer_type_(Ep, {scalar, Name}) -> + #scalar_type{} = Ty = graphql_schema:get(Ep, Name), + {_polarity, Ty} = infer_type_(Ep, Ty); %% NonPolar -infer_type(#scalar_type{} = Ty) -> {'*', Ty}; -infer_type({enum, _} = E) -> {'*', E}; -infer_type(#enum_type{} = Ty) -> {'*', Ty}; +infer_type_(_Ep, #scalar_type{} = Ty) -> {'*', Ty}; +infer_type_(_Ep, {enum, _} = E) -> {'*', E}; +infer_type_(_Ep, #enum_type{} = Ty) -> {'*', Ty}; %% Positive -infer_type(#input_object_type{} = Ty) -> {'+', Ty}; +infer_type_(_Ep, #input_object_type{} = Ty) -> {'+', Ty}; %% Negative -infer_type(#object_type{} = Ty) -> {'-', Ty}; -infer_type(#interface_type{} = Ty) -> {'-', Ty}; -infer_type(#union_type{} = Ty) -> {'-', Ty}; +infer_type_(_Ep, #object_type{} = Ty) -> {'-', Ty}; +infer_type_(_Ep, #interface_type{} = Ty) -> {'-', Ty}; +infer_type_(_Ep, #union_type{} = Ty) -> {'-', Ty}; %% Lookup -infer_type({name, _, N}) -> infer_type(N); -infer_type(N) when is_binary(N) -> - case graphql_schema:lookup(N) of +infer_type_(Ep, {name, _, N}) -> infer_type_(Ep, N); +infer_type_(Ep, N) when is_binary(N) -> + case graphql_schema:lookup(Ep, N) of not_found -> {error, {not_found, N}}; %% Non-polar types #enum_type{} = Enum -> {'*', Enum}; @@ -163,20 +164,20 @@ infer_output_type(Ctx, Ty) -> %% Given a context and some graphql expression, we derive %% a valid type for that expression. This is mostly handled by %% a lookup into the environment. -infer(Ctx, #directive { id = ID }) -> +infer(Ctx = #ctx{ endpoint_ctx = Ep}, #directive { id = ID }) -> Name = graphql_ast:name(ID), - case graphql_schema:lookup(Name) of + case graphql_schema:lookup(Ep, Name) of #directive_type{} = Tau -> {ok, Tau}; not_found -> err(Ctx, {unknown_directive, Name}) end; -infer(Ctx, #op { ty = Ty } = Op) -> +infer(Ctx = #ctx{endpoint_ctx = Ep}, #op { ty = Ty } = Op) -> CtxP = add_path(Ctx, Op), - case graphql_schema:lookup('ROOT') of + case graphql_schema:lookup(Ep, 'ROOT') of not_found -> err(Ctx, no_root_schema); Schema -> Root = graphql_schema:resolve_root_type(Ty, Schema), - case graphql_schema:lookup(Root) of + case graphql_schema:lookup(Ep, Root) of not_found -> err(CtxP, {type_not_found, Root}); #object_type{} = Tau -> @@ -351,8 +352,8 @@ check_value(Ctx, Val, {list, Sigma}) -> %% should be treated as if it were wrapped in a singleton type %% if we are in list-context check_value(Ctx, [Val], {list, Sigma}); -check_value(Ctx, {enum, N}, #enum_type { id = ID } = Sigma) -> - case graphql_schema:validate_enum(ID, N) of +check_value(Ctx = #ctx{endpoint_ctx = Ep}, {enum, N}, #enum_type { id = ID } = Sigma) -> + case graphql_schema:validate_enum(Ep, ID, N) of not_found -> err(Ctx, {unknown_enum, N}); ok -> @@ -543,19 +544,18 @@ check(Ctx, #op { vardefs = VDefs, directives = Dirs, selection_set = SSet } = Op %% To check a document, establish a default context and %% check the document. -check(#document{} = Doc) -> - try check_(Doc) of Res -> Res +check(Ep, #document{} = Doc) -> + try check_(Ep, Doc) of Res -> Res catch throw:{error, Path, Msg} -> graphql_err:abort(Path, type_check, Msg) end. -check_(#document{ definitions = Defs } = Doc) -> +check_(Ep, #document{ definitions = Defs } = Doc) -> Fragments = lists:filter( fun(#frag{}) -> true; (_) -> false end, Defs), - FragEnv = fragenv(Fragments), - CtxP = #ctx{}, - Ctx = CtxP#ctx { frags = FragEnv }, + FragEnv = fragenv(Ep, Fragments), + Ctx = #ctx{endpoint_ctx = Ep, frags = FragEnv}, COps = [begin {ok, Type} = infer(Ctx, Op), {ok, COp} = check(Ctx, Op, Type), @@ -563,7 +563,7 @@ check_(#document{ definitions = Defs } = Doc) -> end || Op <- Defs], {ok, #{ ast => Doc#document { definitions = COps }, - fun_env => funenv(COps) }}. + fun_env => funenv(Ep, COps) }}. %% GraphQL queries are really given in two stages. One stage is the @@ -580,14 +580,16 @@ check_(#document{ definitions = Defs } = Doc) -> %% This is the entry-point when checking parameters for an already parsed, %% type checked and internalized query. It serves to verify that a requested %% operation and its parameters matches the types in the operation referenced -check_params(FunEnv, OpName, Params) -> +check_params(Ep, FunEnv, OpName, Params) -> try case operation(FunEnv, OpName, Params) of undefined -> #{}; not_found -> - err(#ctx{}, {operation_not_found, OpName}); + err(#ctx{endpoint_ctx = Ep}, + {operation_not_found, OpName}); VarEnv -> - Ctx = #ctx { vars = VarEnv, + Ctx = #ctx { endpoint_ctx = Ep, + vars = VarEnv, path = [OpName], sub_context = variable }, check_params_(Ctx, Params) @@ -739,10 +741,10 @@ sub_output(Ctx, #interface_type { id = IID }, %% Interface Tau subsumes interface Sigma if they have concrete %% objects in common. This means there is at least one valid expansion, %% so this should be allowed. -sub_output(Ctx, #interface_type { id = SpreadID }, +sub_output(Ctx = #ctx{endpoint_ctx = Ep}, #interface_type { id = SpreadID }, #interface_type { id = ScopeID }) -> - Taus = graphql_schema:lookup_interface_implementors(SpreadID), - Sigmas = graphql_schema:lookup_interface_implementors(ScopeID), + Taus = graphql_schema:lookup_interface_implementors(Ep, SpreadID), + Sigmas = graphql_schema:lookup_interface_implementors(Ep, ScopeID), case ordsets:intersection( ordsets:from_list(Taus), ordsets:from_list(Sigmas)) of @@ -753,9 +755,9 @@ sub_output(Ctx, #interface_type { id = SpreadID }, end; %% Interfaces subsume unions, if the union has at least one member %% who implements the interface. -sub_output(Ctx, #interface_type { id = SpreadID }, +sub_output(Ctx = #ctx{endpoint_ctx = Ep}, #interface_type { id = SpreadID }, #union_type{ id = ScopeID, types = ScopeMembers }) -> - Taus = graphql_schema:lookup_interface_implementors(SpreadID), + Taus = graphql_schema:lookup_interface_implementors(Ep, SpreadID), case ordsets:intersection( ordsets:from_list(Taus), ordsets:from_list(ScopeMembers)) of @@ -774,9 +776,9 @@ sub_output(Ctx, #union_type { id = UID, types = UMembers }, %% Unions subsume interfaces iff there is an intersection between %% what members the union has and what the implementors of the interface %% are. -sub_output(Ctx, #union_type { id = SpreadID, types = SpreadMembers }, +sub_output(Ctx = #ctx{endpoint_ctx = Ep}, #union_type { id = SpreadID, types = SpreadMembers }, #interface_type { id = ScopeID }) -> - Sigmas = graphql_schema:lookup_interface_implementors(ScopeID), + Sigmas = graphql_schema:lookup_interface_implementors(Ep, ScopeID), case ordsets:intersection( ordsets:from_list(SpreadMembers), ordsets:from_list(Sigmas)) of @@ -872,24 +874,26 @@ varenv(VarList) -> [{graphql_ast:name(Var), Def} || #vardef { id = Var } = Def <- VarList]). %% Build a funenv -funenv(Ops) -> +funenv(Ep, Ops) -> F = fun (#frag{}, FE) -> FE; (#op { id = ID, vardefs = VDefs }, FE) -> Name = graphql_ast:name(ID), - {ok, VarEnv} = var_defs(#ctx{}, maps:values(VDefs)), + Ctx = #ctx{endpoint_ctx = Ep}, + {ok, VarEnv} = var_defs(Ctx, maps:values(VDefs)), FE#{ Name => VarEnv } end, lists:foldl(F, #{}, Ops). -annotate_frag(#frag { ty = Ty } = Frag) -> - {ok, Tau} = infer_output_type(#ctx{}, Ty), +annotate_frag(Ep, #frag { ty = Ty } = Frag) -> + Ctx = #ctx{endpoint_ctx = Ep}, + {ok, Tau} = infer_output_type(Ctx, Ty), Frag#frag { schema = Tau }. %% Build a fragenv -fragenv(Frags) -> +fragenv(Ep, Frags) -> maps:from_list( - [{graphql_ast:name(ID), annotate_frag(Frg)} || #frag { id = ID } = Frg <- Frags]). + [{graphql_ast:name(ID), annotate_frag(Ep, Frg)} || #frag { id = ID } = Frg <- Frags]). %% Figure out what kind of directive location we are in directive_location(#op { ty = Ty }) -> diff --git a/src/graphql_dot.erl b/src/graphql_dot.erl index 7a0531a..e198ba4 100644 --- a/src/graphql_dot.erl +++ b/src/graphql_dot.erl @@ -2,17 +2,17 @@ -include("graphql_schema.hrl"). --export([dump/1]). +-export([dump/2]). --spec dump(string()) -> ok. -dump(FName) -> - Graph = x(), +-spec dump(endpoint_context(), string()) -> ok. +dump(Ep, FName) -> + Graph = x(Ep), file:write_file(FName, Graph). -x() -> +x(Ep) -> H = header(), F = footer(), - Body = body(), + Body = body(Ep), [H, Body, F]. footer() -> "}\n". @@ -30,8 +30,8 @@ header() -> " nodesep=0.3;", " remincross=true;"]). -body() -> - Entries = graphql_schema:all(), +body(Ep) -> + Entries = graphql_schema:all(Ep), Map = maps:from_list([{id(E), E} || E <- Entries]), [ [format(entry(E, Map)) || E <- Entries], diff --git a/src/graphql_err.erl b/src/graphql_err.erl index 1066f61..1e369db 100644 --- a/src/graphql_err.erl +++ b/src/graphql_err.erl @@ -12,6 +12,7 @@ -export([crash/2, err/2]). +-spec abort([any()], any()) -> no_return(). abort(Path, Msg) -> abort(Path, uncategorized, Msg). diff --git a/src/graphql_execute.erl b/src/graphql_execute.erl index 6ac0428..9090fb0 100644 --- a/src/graphql_execute.erl +++ b/src/graphql_execute.erl @@ -8,7 +8,7 @@ -compile(inline_list_funcs). -compile({inline_size, 50}). --export([x/1, x/2]). +-export([x/2, x/3]). -export([builtin_input_coercer/1]). -type source() :: reference(). -type demonitor() :: {reference(), pid()} . @@ -43,6 +43,8 @@ %% tree? Updated as we process different parts of the tree defer_target = top_level :: top_level | reference(), + endpoint_ctx :: endpoint_context(), + %% The callers context given to the execute call ctx = #{} :: #{ atom() => term() } }). @@ -74,12 +76,12 @@ work = #{} :: #{ source() => defer_closure() }, timeout :: non_neg_integer() }). --spec x(graphql:ast()) -> #{ atom() => graphql:json() }. -x(X) -> x(#{ params => #{} }, X). +-spec x(endpoint_context(), graphql:ast()) -> #{ atom() => graphql:json() }. +x(Ep, X) -> x(Ep, #{ params => #{} }, X). --spec x(term(), graphql:ast()) -> #{ atom() => graphql:json() }. -x(Ctx, X) -> - Canon = canon_context(Ctx), +-spec x(endpoint_context(), term(), graphql:ast()) -> #{ atom() => graphql:json() }. +x(Ep, Ctx, X) -> + Canon = canon_context(Ep, Ctx), execute_request(Canon, X). execute_request(InitialCtx, #document { definitions = Operations }) -> @@ -517,13 +519,16 @@ format_directives([#directive { id = N, args = Args }|Ds]) -> resolve_field_value(#ectx { op_type = OpType, ctx = CallerContext, defer_process = Proc, - defer_request_id = ReqId }, + defer_request_id = ReqId, + endpoint_ctx = Ep + }, #object_type { id = OID, directives = ODirectives} = ObjectType, Value, Name, FDirectives, Fun, Args) -> AnnotatedCallerCtx = CallerContext#{ op_type => OpType, field => Name, + endpoint_ctx => Ep, field_directives => format_directives(FDirectives), object_type => OID, object_directives => format_directives(ODirectives), @@ -571,11 +576,11 @@ handle_resolver_result({defer, Token, DeferStateMap}) -> {defer, Token, DeferStateMap}; handle_resolver_result(_Unknown) -> wrong. -complete_value(Ctx, Ty, Fields, {ok, Value}) when is_binary(Ty) -> +complete_value(Ctx = #ectx{endpoint_ctx = Ep}, Ty, Fields, {ok, Value}) when is_binary(Ty) -> error_logger:warning_msg( "Canary: Type lookup during value completion for: ~p~n", [Ty]), - SchemaType = graphql_schema:get(Ty), + SchemaType = graphql_schema:get(Ep, Ty), complete_value(Ctx, SchemaType, Fields, {ok, Value}); complete_value(#ectx{ defer_target = Upstream } = Ctx, {non_null, InnerTy}, Fields, Result) -> @@ -611,7 +616,7 @@ complete_value(Ctx, {list, InnerTy}, Fields, {ok, Value}) -> complete_value_list(Ctx, InnerTy, Fields, Value); complete_value(Ctx, #scalar_type { id = ID, resolve_module = RM }, _Fields, {ok, Value}) -> complete_value_scalar(Ctx, ID, RM, Value); -complete_value(Ctx, #enum_type { id = ID, +complete_value(Ctx = #ectx{endpoint_ctx = Ep}, #enum_type { id = ID, resolve_module = RM}, _Fields, {ok, Value}) -> case complete_value_scalar(Ctx, ID, RM, Value) of @@ -619,7 +624,7 @@ complete_value(Ctx, #enum_type { id = ID, {ok, null, Errors}; {ok, Result, Errors} -> - case graphql_schema:validate_enum(ID, Result) of + case graphql_schema:validate_enum(Ep, ID, Result) of ok -> {ok, Result, Errors}; {other_enums, _EnumTypes} -> @@ -639,20 +644,20 @@ complete_value(Ctx, _Ty, _Fields, {error, Reason}) -> null(Ctx, Reason). %% Complete an abstract value -complete_value_abstract(Ctx, Resolver, Fields, {ok, Value}) -> - case resolve_abstract_type(Resolver, Value) of +complete_value_abstract(Ctx = #ectx{endpoint_ctx = Ep}, Resolver, Fields, {ok, Value}) -> + case resolve_abstract_type(Ep, Resolver, Value) of {ok, ResolvedType} -> complete_value(Ctx, ResolvedType, Fields, {ok, Value}); {error, Reason} -> null(Ctx, Reason) end. -resolve_abstract_type(Module, Value) when is_atom(Module) -> - resolve_abstract_type(fun Module:execute/1, Value); -resolve_abstract_type(Resolver, Value) when is_function(Resolver, 1) -> +resolve_abstract_type(Ep, Module, Value) when is_atom(Module) -> + resolve_abstract_type(Ep, fun Module:execute/1, Value); +resolve_abstract_type(Ep, Resolver, Value) when is_function(Resolver, 1) -> try Resolver(Value) of {ok, Ty} -> - Obj = #object_type{} = graphql_schema:get(binarize(Ty)), + Obj = #object_type{} = graphql_schema:get(Ep, binarize(Ty)), {ok, Obj}; {error, Reason} -> {error, {type_resolver_error, Reason}} @@ -956,25 +961,25 @@ resolve_args_(Ctx, [{ID, Val} | As], Acc) -> %% %% For a discussion about the Pet -> [Pet] coercion in the %% specification, see (Oct2016 Section 3.1.7) -var_coerce(Tau, Sigma, V) when is_binary(Sigma) -> - X = graphql_schema:lookup(Sigma), - var_coerce(Tau, X, V); -var_coerce(Tau, Sigma, V) when is_binary(Tau) -> - X = graphql_schema:lookup(Tau), - var_coerce(X, Sigma, V); -var_coerce(Refl, Refl, Value) -> Value; -var_coerce({non_null, Tau}, {non_null, Sigma}, Value) -> - var_coerce(Tau, Sigma, Value); -var_coerce({non_null, Tau}, Tau, Value) -> Value; -var_coerce({list, Tau}, {list, Sigma}, Values) -> - var_coerce(Tau, Sigma, Values); -var_coerce(Tau, {list, SType}, Value) -> [var_coerce(Tau, SType, Value)]. +var_coerce(Ep, Tau, Sigma, V) when is_binary(Sigma) -> + X = graphql_schema:lookup(Ep, Sigma), + var_coerce(Ep, Tau, X, V); +var_coerce(Ep, Tau, Sigma, V) when is_binary(Tau) -> + X = graphql_schema:lookup(Ep, Tau), + var_coerce(Ep, X, Sigma, V); +var_coerce(_Ep, Refl, Refl, Value) -> Value; +var_coerce(Ep, {non_null, Tau}, {non_null, Sigma}, Value) -> + var_coerce(Ep, Tau, Sigma, Value); +var_coerce(_Ep, {non_null, Tau}, Tau, Value) -> Value; +var_coerce(Ep, {list, Tau}, {list, Sigma}, Values) -> + var_coerce(Ep, Tau, Sigma, Values); +var_coerce(Ep, Tau, {list, SType}, Value) -> [var_coerce(Ep, Tau, SType, Value)]. %% Produce a valid value for an argument. value(Ctx, {Ty, Val}) -> value(Ctx, Ty, Val); value(Ctx, #{ type := Ty, value := Val }) -> value(Ctx, Ty, Val). -value(#ectx{ params = Params }, SType, #var { id = ID, ty = DType, +value(#ectx{params = Params, endpoint_ctx = Ep}, SType, #var { id = ID, ty = DType, default = Default }) -> %% Parameter expansion and type check is already completed %% at this stage @@ -982,11 +987,11 @@ value(#ectx{ params = Params }, SType, #var { id = ID, ty = DType, not_found -> case Default of %% Coerce undefined values to "null" - undefined -> var_coerce(DType, SType, null); - _ -> var_coerce(DType, SType, Default) + undefined -> var_coerce(Ep, DType, SType, null); + _ -> var_coerce(Ep, DType, SType, Default) end; Value -> - var_coerce(DType, SType, Value) + var_coerce(Ep, DType, SType, Value) end; value(_Ctx, _Ty, undefined) -> null; @@ -1000,7 +1005,7 @@ value(Ctx, {list, Ty}, Val) -> L when is_list(L) -> L end, [value(Ctx, Ty, V) || V <- Vals]; -value(Ctx, Ty, Val) -> +value(Ctx = #ectx{endpoint_ctx = Ep}, Ty, Val) -> case Ty of #input_object_type { fields = FieldEnv } -> Obj = case Val of @@ -1016,7 +1021,7 @@ value(Ctx, Ty, Val) -> #enum_type {} -> Val; Bin when is_binary(Bin) -> - LoadedTy = graphql_schema:get(Bin), + LoadedTy = graphql_schema:get(Ep, Bin), value(Ctx, LoadedTy, Val) end. @@ -1040,9 +1045,10 @@ alias(#field { alias = Alias }) -> name(Alias). field_type(#field { schema = SF }) -> SF. %% -- CONTEXT CANONICALIZATION ------------ -canon_context(#{ params := Params } = Ctx) -> +canon_context(Ep, #{ params := Params } = Ctx) -> CParams = canon_params(Params), #ectx { params = CParams, + endpoint_ctx = Ep, ctx = Ctx#{ params := CParams} }. canon_params(Ps) -> diff --git a/src/graphql_introspection.erl b/src/graphql_introspection.erl index bfc119e..3521f5d 100644 --- a/src/graphql_introspection.erl +++ b/src/graphql_introspection.erl @@ -3,7 +3,7 @@ -include("graphql_schema.hrl"). -include_lib("graphql/include/graphql.hrl"). --export([inject/0, augment_root/1]). +-export([inject/1, augment_root/2]). -export([execute/4]). @@ -17,11 +17,12 @@ subscription_type/4 ]). --spec augment_root(QueryObj) -> ok - when QueryObj :: binary(). +-spec augment_root(Ep, QueryObj) -> ok + when Ep :: endpoint_context(), + QueryObj :: binary(). -augment_root(QName) -> - #object_type{ fields = Fields } = Obj = graphql_schema:get(QName), +augment_root(Ep, QName) -> + #object_type{ fields = Fields } = Obj = graphql_schema:get(Ep, QName), Schema = #schema_field { ty = {non_null, <<"__Schema">>}, description = <<"The introspection schema">>, @@ -40,64 +41,64 @@ augment_root(QName) -> <<"__schema">> => Schema, <<"__type">> => Type }}, - ok = graphql_schema:insert(Augmented, #{}), + ok = graphql_schema:insert(Ep, Augmented, #{}), ok. -schema_resolver(_Ctx, none, _, #{}) -> +schema_resolver(_Ctx = #{endpoint_ctx := Ep}, none, _, #{}) -> {ok, #{ <<"directives">> => - [directive(include), - directive(skip)]}}. + [directive(Ep, include), + directive(Ep, skip)]}}. -type_resolver(_Ctx, none, _, #{ <<"name">> := N }) -> - case graphql_schema:lookup(N) of +type_resolver(_Ctx = #{endpoint_ctx := Ep}, none, _, #{ <<"name">> := N }) -> + case graphql_schema:lookup(Ep, N) of not_found -> {ok, null}; - Ty -> render_type(Ty) + Ty -> render_type(Ep, Ty) end. -query_type(_Ctx, _Obj, _, _) -> - #root_schema{ query = QType } = graphql_schema:get('ROOT'), - render_type(QType). +query_type(_Ctx = #{endpoint_ctx := Ep}, _Obj, _, _) -> + #root_schema{ query = QType } = graphql_schema:get(Ep, 'ROOT'), + render_type(Ep, QType). -mutation_type(_Ctx, _Obj, _, _) -> - #root_schema { mutation = MType } = graphql_schema:get('ROOT'), +mutation_type(_Ctx = #{endpoint_ctx := Ep}, _Obj, _, _) -> + #root_schema { mutation = MType } = graphql_schema:get(Ep, 'ROOT'), case MType of undefined -> {ok, null}; - MT -> render_type(MT) + MT -> render_type(Ep, MT) end. -subscription_type(_Ctx, _Obj, _, _) -> - #root_schema { subscription = SType } = graphql_schema:get('ROOT'), +subscription_type(_Ctx = #{endpoint_ctx := Ep}, _Obj, _, _) -> + #root_schema { subscription = SType } = graphql_schema:get(Ep, 'ROOT'), case SType of undefined -> {ok, null}; - ST -> render_type(ST) + ST -> render_type(Ep, ST) end. -schema_types(_Ctx, _Obj, _, _Args) -> +schema_types(_Ctx = #{endpoint_ctx := Ep}, _Obj, _, _Args) -> Pass = fun (#root_schema{}) -> false; (#directive_type{}) -> false; (_) -> true end, - Types = [X || X <- graphql_schema:all(), Pass(X)], - {ok, [render_type(Ty) || Ty <- Types]}. + Types = [X || X <- graphql_schema:all(Ep), Pass(X)], + {ok, [render_type(Ep, Ty) || Ty <- Types]}. %% Main renderer. Calls out to the subsets needed -render_type(Name) when is_binary(Name) -> - case graphql_schema:lookup(Name) of +render_type(Ep, Name) when is_binary(Name) -> + case graphql_schema:lookup(Ep, Name) of not_found -> throw({not_found, Name}); - Ty -> render_type(Ty) + Ty -> render_type(Ep, Ty) end; -render_type(Ty) -> {ok, #{ +render_type(Ep, Ty) -> {ok, #{ <<"kind">> => type_kind(Ty), <<"name">> => type_name(Ty), <<"description">> => type_description(Ty), - <<"fields">> => type_fields(Ty), - <<"interfaces">> => type_interfaces(Ty), - <<"possibleTypes">> => type_possibilities(Ty), + <<"fields">> => type_fields(Ep, Ty), + <<"interfaces">> => type_interfaces(Ep, Ty), + <<"possibleTypes">> => type_possibilities(Ep, Ty), <<"enumValues">> => type_enum_values(Ty), - <<"inputFields">> => type_input_fields(Ty), - <<"ofType">> => type_unwrap(Ty) }}. + <<"inputFields">> => type_input_fields(Ep, Ty), + <<"ofType">> => type_unwrap(Ep, Ty) }}. type_kind(#scalar_type{}) -> <<"SCALAR">>; type_kind(#object_type {}) -> <<"OBJECT">>; @@ -124,15 +125,15 @@ type_description(#enum_type{ description = D}) -> D; type_description(#scalar_type{ description = D}) -> D; type_description(_) -> null. -type_interfaces(#object_type{ interfaces = IFs }) -> - ?LAZY({ok, [render_type(Ty) || Ty <- IFs]}); -type_interfaces(_) -> null. +type_interfaces(Ep, #object_type{ interfaces = IFs }) -> + ?LAZY({ok, [render_type(Ep, Ty) || Ty <- IFs]}); +type_interfaces(_, _) -> null. -type_possibilities(#interface_type { id = ID }) -> - ?LAZY(interface_implementors(ID)); -type_possibilities(#union_type { types = Types }) -> - ?LAZY({ok, [render_type(Ty) || Ty <- Types]}); -type_possibilities(_) -> null. +type_possibilities(Ep, #interface_type { id = ID }) -> + ?LAZY(interface_implementors(Ep, ID)); +type_possibilities(Ep, #union_type { types = Types }) -> + ?LAZY({ok, [render_type(Ep, Ty) || Ty <- Types]}); +type_possibilities(_Ep, _) -> null. type_enum_values(#enum_type { values = VMap }) -> [begin @@ -141,25 +142,25 @@ type_enum_values(#enum_type { values = VMap }) -> end || V <- maps:to_list(VMap)]; type_enum_values(_) -> null. -type_unwrap({list, Ty}) -> {ok, U} = render_type(Ty), U; -type_unwrap({non_null, Ty}) -> {ok, U} = render_type(Ty), U; -type_unwrap(_) -> null. +type_unwrap(Ep, {list, Ty}) -> {ok, U} = render_type(Ep, Ty), U; +type_unwrap(Ep, {non_null, Ty}) -> {ok, U} = render_type(Ep, Ty), U; +type_unwrap(_, _) -> null. -type_input_fields(#input_object_type{ fields = FS }) -> - ?LAZY({ok, [render_input_value(F) || F <- maps:to_list(FS)]}); -type_input_fields(_) -> null. +type_input_fields(Ep, #input_object_type{ fields = FS }) -> + ?LAZY({ok, [render_input_value(Ep, F) || F <- maps:to_list(FS)]}); +type_input_fields(_, _) -> null. -type_fields(#object_type { fields = FS }) -> - ?LAZY({ok, [render_field(F) || F <- maps:to_list(FS), interesting_field(F)]}); -type_fields(#interface_type { fields = FS }) -> - ?LAZY({ok, [render_field(F) || F <- maps:to_list(FS), interesting_field(F)]}); -type_fields(_) -> null. +type_fields(Ep, #object_type { fields = FS }) -> + ?LAZY({ok, [render_field(Ep, F) || F <- maps:to_list(FS), interesting_field(F)]}); +type_fields(Ep, #interface_type { fields = FS }) -> + ?LAZY({ok, [render_field(Ep, F) || F <- maps:to_list(FS), interesting_field(F)]}); +type_fields(_, _) -> null. interesting_field({<<"__schema">>, #schema_field {}}) -> false; interesting_field({<<"__type">>, #schema_field{}}) -> false; interesting_field({_, _}) -> true. -render_field({Name, #schema_field { +render_field(Ep, {Name, #schema_field { description = Desc, args = Args, ty = Ty, @@ -169,19 +170,19 @@ render_field({Name, #schema_field { {ok, #{ <<"name">> => Name, <<"description">> => Desc, - <<"args">> => ?LAZY({ok, [render_input_value(IV) || IV <- maps:to_list(Args)]}), - <<"type">> => ?LAZY(render_type(Ty)), + <<"args">> => ?LAZY({ok, [render_input_value(Ep, IV) || IV <- maps:to_list(Args)]}), + <<"type">> => ?LAZY(render_type(Ep, Ty)), <<"isDeprecated">> => IsDeprecated, <<"deprecationReason">> => DeprecationReason }}. -render_input_value({K, #schema_arg { ty = Ty, +render_input_value(Ep, {K, #schema_arg { ty = Ty, description = Desc, default = Default }}) -> {ok, #{ <<"name">> => K, <<"description">> => Desc, - <<"type">> => ?LAZY(render_type(Ty)), + <<"type">> => ?LAZY(render_type(Ep, Ty)), <<"defaultValue">> => case Default of undefined -> null; @@ -189,12 +190,12 @@ render_input_value({K, #schema_arg { ty = Ty, end }}. -interface_implementors(ID) -> +interface_implementors(Ep, ID) -> Pass = fun (#object_type { interfaces = IFs }) -> lists:member(ID, IFs); (_) -> false end, - {ok, [render_type(Ty) || Ty <- graphql_schema:all(), Pass(Ty)]}. + {ok, [render_type(Ep, Ty) || Ty <- graphql_schema:all(Ep), Pass(Ty)]}. render_enum_value({_Value, #enum_value{ val = Key, @@ -215,8 +216,8 @@ render_deprecation(Reason) when is_binary(Reason) -> {true, Reason}. %% -- SCHEMA DEFINITION ------------------------------------------------------- --spec inject() -> ok. -inject() -> +-spec inject(endpoint_context()) -> ok. +inject(Ep) -> Schema = {object, #{ id => '__Schema', resolve_module => ?MODULE, @@ -408,19 +409,19 @@ inject() -> 'FRAGMENT_SPREAD' => #{ value => 5, description => "Fragment spread" }, 'INLINE_FRAGMENT' => #{ value => 6, description => "Inline fragments" } }}}, - ok = graphql:insert_schema_definition(DirectiveLocation), - ok = graphql:insert_schema_definition(Directive), - ok = graphql:insert_schema_definition(TypeKind), - ok = graphql:insert_schema_definition(Enum), - ok = graphql:insert_schema_definition(InputValue), - ok = graphql:insert_schema_definition(Field), - ok = graphql:insert_schema_definition(Type), - ok = graphql:insert_schema_definition(Schema), + ok = graphql:insert_schema_definition(Ep, DirectiveLocation), + ok = graphql:insert_schema_definition(Ep, Directive), + ok = graphql:insert_schema_definition(Ep, TypeKind), + ok = graphql:insert_schema_definition(Ep, Enum), + ok = graphql:insert_schema_definition(Ep, InputValue), + ok = graphql:insert_schema_definition(Ep, Field), + ok = graphql:insert_schema_definition(Ep, Type), + ok = graphql:insert_schema_definition(Ep, Schema), ok. %% @todo: Look up the directive in the schema and then use that lookup as a way to render the %% following part. Most notably, locations can be mapped from the directive type. -directive(Kind) -> +directive(Ep, Kind) -> {Name, Desc} = case Kind of include -> @@ -430,7 +431,7 @@ directive(Kind) -> {<<"skip">>, <<"exclude a selection on a conditional variable">>} end, - {ok, Bool} = render_type(<<"Bool">>), + {ok, Bool} = render_type(Ep, <<"Bool">>), #{ <<"name">> => Name, diff --git a/src/graphql_schema.erl b/src/graphql_schema.erl index 249349c..870550c 100644 --- a/src/graphql_schema.erl +++ b/src/graphql_schema.erl @@ -5,15 +5,16 @@ -include("graphql_schema.hrl"). -include("graphql_internal.hrl"). --export([start_link/0, reset/0]). +-export([start_link/0, start_link/1, reset/0, reset/1, get_endpoint_ctx/1, get_endpoint_ctx/0]). -export([ - all/0, - insert/1, insert/2, - load/1, load_schema/1, - get/1, - lookup/1, - validate_enum/2, - lookup_interface_implementors/1 + all/1, + insert/3, + load/2, + load_schema/2, + get/2, + lookup/2, + validate_enum/3, + lookup_interface_implementors/2 ]). -export([resolve_root_type/2]). @@ -23,36 +24,53 @@ -export([init/1, handle_call/3, handle_cast/2, terminate/2, handle_info/2, code_change/3]). --define(ENUMS, graphql_schema_enums). % Table mapping enum values of type binary() to set of enum type ids --define(OBJECTS, graphql_schema_objects). % Table of all types +-define(DEFAULT_ENDPOINT, default). --record(state, {}). +-type endpoint() :: atom(). +-record(state, {context :: endpoint_context()}). %% -- API ---------------------------- + +-spec get_endpoint_ctx() -> endpoint_context(). +get_endpoint_ctx() -> + {ok, Ep} = gen_server:call(?DEFAULT_ENDPOINT, get_endpoint_ctx), + Ep. + +-spec get_endpoint_ctx(endpoint()) -> endpoint_context(). +get_endpoint_ctx(Name) -> + {ok, Ep} = gen_server:call(Name, get_endpoint_ctx), + Ep. + -spec start_link() -> any(). start_link() -> - Res = gen_server:start_link({local, ?MODULE}, ?MODULE, [], []), - reset(), + start_link(?DEFAULT_ENDPOINT). + +-spec start_link(endpoint()) -> any(). +start_link(Name) -> + Res = gen_server:start_link({local, Name}, ?MODULE, [Name], []), + Ep = get_endpoint_ctx(Name), + reset(Ep), Res. -spec reset() -> ok. reset() -> - ok = gen_server:call(?MODULE, reset), - ok = graphql_introspection:inject(), - ok = graphql_builtins:standard_types_inject(), - ok = graphql_builtins:standard_directives_inject(), + Ep = get_endpoint_ctx(?DEFAULT_ENDPOINT), + reset(Ep). + +-spec reset(endpoint_context()) -> ok. +reset(Ep) -> + ok = gen_server:call(Ep#endpoint_context.pid, reset), + ok = graphql_introspection:inject(Ep), + ok = graphql_builtins:standard_types_inject(Ep), + ok = graphql_builtins:standard_directives_inject(Ep), ok. --spec insert(any()) -> ok. -insert(S) -> - insert(S, #{ canonicalize => true }). - --spec insert(any(), any()) -> +-spec insert(endpoint_context(), any(), any()) -> ok | {error, Reason :: term()}. -insert(S, #{ canonicalize := true }) -> +insert(Ep, S, #{ canonicalize := true }) -> try graphql_schema_canonicalize:x(S) of Rec -> - case gen_server:call(?MODULE, {insert, Rec}) of + case gen_server:call(Ep#endpoint_context.pid, {insert, Rec}) of true -> ok; false -> Identify = fun({_, #{ id := ID }}) -> ID end, @@ -65,55 +83,55 @@ insert(S, #{ canonicalize := true }) -> [{Class,Reason}, ?GET_STACK(Stacktrace)]), {error, {schema_canonicalize, {Class, Reason}}} end; -insert(S, #{}) -> - gen_server:call(?MODULE, {insert, S}). - +insert(Ep, S, #{}) -> + gen_server:call(Ep#endpoint_context.pid, {insert, S}). --spec load_schema(#root_schema{}) -> ok. -load_schema(#root_schema{ query = Q } = Root) -> - ok = graphql_introspection:augment_root(Q), - insert_new_(Root). +-spec load_schema(endpoint_context(), #root_schema{}) -> ok. +load_schema(Ep, #root_schema{ query = Q } = Root) -> + ok = graphql_introspection:augment_root(Ep, Q), + insert_new_(Ep, Root). --spec load(any()) -> ok | {error, Reason} +-spec load(endpoint_context(), any()) -> ok | {error, Reason} when Reason :: term(). -load(S) -> +load(Ep, S) -> try graphql_schema_canonicalize:x(S) of #root_schema { query = Q } = Rec -> - ok = graphql_introspection:augment_root(Q), - insert_new_(Rec); + ok = graphql_introspection:augment_root(Ep, Q), + insert_new_(Ep, Rec); Rec -> - insert_new_(Rec) + insert_new_(Ep, Rec) catch Class:Reason -> {error, {schema_canonicalize, {Class, Reason}}} end. -insert_new_(Rec) -> - case gen_server:call(?MODULE, {insert_new, Rec}) of +insert_new_(#endpoint_context{pid = Pid}, Rec) -> + case gen_server:call(Pid, {insert_new, Rec}) of true -> ok; false -> {error, {already_exists, id(Rec)}} end. --spec all() -> [any()]. -all() -> - ets:match_object(?OBJECTS, '_'). +-spec all(endpoint_context()) -> [any()]. +all(#endpoint_context{objects_tab = ObjectsTab}) -> + ets:match_object(ObjectsTab, '_'). --spec get(binary() | 'ROOT') -> schema_object(). -get(ID) -> - case ets:lookup(?OBJECTS, ID) of +-spec get(endpoint_context(), binary() | 'ROOT') -> schema_object(). +get(#endpoint_context{objects_tab = ObjectsTab}, ID) -> + case ets:lookup(ObjectsTab, ID) of [S] -> S; _ -> exit(schema_not_found) end. %% Check if given enum value matches the given type id, other enums, %% or nothing at all. --spec validate_enum(binary(), binary()) -> ok | not_found | {other_enums, [#enum_type{}]}. -validate_enum(EnumID, EnumValue) -> - try ets:lookup_element(?ENUMS, EnumValue, 2) of + +-spec validate_enum(endpoint_context(), binary(), binary()) -> ok | not_found | {other_enums, [#enum_type{}]}. +validate_enum(Ep=#endpoint_context{enums_tab = EnumsTab}, EnumID, EnumValue) -> + try ets:lookup_element(EnumsTab, EnumValue, 2) of #{EnumID := _} -> ok; EnumIDsMap -> EnumIDs = maps:keys(EnumIDsMap), - OtherEnums = [?MODULE:get(ID) || ID <- EnumIDs], + OtherEnums = [get(Ep, ID) || ID <- EnumIDs], {other_enums, OtherEnums} catch error:badarg -> @@ -126,17 +144,19 @@ validate_enum(EnumID, EnumValue) -> %% %% However, in the spirit of getting something up and running, we start %% with QLC in order to make a working system. --spec lookup_interface_implementors(binary()) -> [binary()]. -lookup_interface_implementors(IFaceID) -> + +-spec lookup_interface_implementors(endpoint_context(), binary()) -> [binary()]. +lookup_interface_implementors(#endpoint_context{objects_tab = ObjectsTab}, IFaceID) -> QH = qlc:q([Obj#object_type.id - || Obj <- ets:table(?OBJECTS), + || Obj <- ets:table(ObjectsTab), element(1, Obj) == object_type, lists:member(IFaceID, Obj#object_type.interfaces)]), qlc:e(QH). --spec lookup(binary() | 'ROOT') -> schema_object() | not_found. -lookup(ID) -> - case ets:lookup(?OBJECTS, ID) of + +-spec lookup(endpoint_context(), binary() | 'ROOT') -> schema_object() | not_found. +lookup(#endpoint_context{objects_tab = ObjectsTab}, ID) -> + case ets:lookup(ObjectsTab, ID) of [S] -> S; _ -> not_found end. @@ -158,15 +178,25 @@ id(#directive_type{ id = ID }) -> ID. %% -- CALLBACKS --spec init([]) -> {ok, #state{}}. -init([]) -> - _Tab1 = ets:new(?ENUMS, - [named_table, protected, {read_concurrency, true}, set, +-spec init([atom()]) -> {ok, #state{}}. +init([Name]) -> + Ep = register_schema(Name), + {ok, #state{context = Ep}}. + +-spec register_schema(endpoint()) -> endpoint_context(). +register_schema(Name) -> + EnumsTab = ets:new(graphql_objects_table, + [protected, {read_concurrency, true}, set, {keypos, 1}]), - _Tab = ets:new(?OBJECTS, - [named_table, protected, {read_concurrency, true}, set, + ObjectsTab = ets:new(graphql_enums_table, + [protected, {read_concurrency, true}, set, {keypos, #object_type.id}]), - {ok, #state{}}. + + #endpoint_context{name = Name, + pid = self(), + enums_tab = EnumsTab, + objects_tab = ObjectsTab}. + -spec handle_cast(any(), S) -> {noreply, S} when S :: #state{}. @@ -176,8 +206,8 @@ handle_cast(_Msg, State) -> {noreply, State}. when S :: #state{}, M :: term(). -handle_call({insert, X}, _From, State) -> - case determine_table(X) of +handle_call({insert, X}, _From, #state{context=Ep}=State) -> + case determine_table(Ep, X) of {error, unknown} -> {reply, {error, {schema, X}}, State}; {enum, Tab, Enum} -> @@ -188,8 +218,8 @@ handle_call({insert, X}, _From, State) -> true = ets:insert(Tab, X), {reply, ok, State} end; -handle_call({insert_new, X}, _From, State) -> - case determine_table(X) of +handle_call({insert_new, X}, _From, #state{context=Ep}=State) -> + case determine_table(Ep, X) of {error, unknown} -> {reply, {error, {schema, X}}, State}; {enum, Tab, Enum} -> @@ -203,9 +233,12 @@ handle_call({insert_new, X}, _From, State) -> Tab -> {reply, ets:insert_new(Tab, X), State} end; -handle_call(reset, _From, State) -> - true = ets:delete_all_objects(?OBJECTS), +handle_call(reset, _From, + #state{context = #endpoint_context{objects_tab = Tab}} = State) -> + true = ets:delete_all_objects(Tab), {reply, ok, State}; +handle_call(get_endpoint_ctx, _From, State) -> + {reply, {ok, State#state.context}, State}; handle_call(_Msg, _From, State) -> {reply, {error, unknown_call}, State}. @@ -223,15 +256,23 @@ code_change(_OldVsn, State, _Aux) -> {ok, State}. %% -- INTERNAL FUNCTIONS ------------------------- %% determine_table/1 figures out the table an object belongs to -determine_table(#root_schema{}) -> ?OBJECTS; -determine_table(#object_type{}) -> ?OBJECTS; -determine_table(#enum_type{}) -> {enum, ?OBJECTS, ?ENUMS}; -determine_table(#interface_type{}) -> ?OBJECTS; -determine_table(#scalar_type{}) -> ?OBJECTS; -determine_table(#input_object_type{}) -> ?OBJECTS; -determine_table(#union_type{}) -> ?OBJECTS; -determine_table(#directive_type{}) -> ?OBJECTS; -determine_table(_) -> {error, unknown}. +determine_table(#endpoint_context{objects_tab = Objs}, + #root_schema{}) -> Objs; +determine_table(#endpoint_context{objects_tab = Objs}, + #object_type{}) -> Objs; +determine_table(#endpoint_context{objects_tab = Objs, enums_tab = Enums}, + #enum_type{}) -> {enum, Objs, Enums}; +determine_table(#endpoint_context{objects_tab = Objs}, + #interface_type{}) -> Objs; +determine_table(#endpoint_context{objects_tab = Objs}, + #scalar_type{}) -> Objs; +determine_table(#endpoint_context{objects_tab = Objs}, + #input_object_type{}) -> Objs; +determine_table(#endpoint_context{objects_tab = Objs}, + #union_type{}) -> Objs; +determine_table(#endpoint_context{objects_tab = Objs}, + #directive_type{}) -> Objs; +determine_table(_, _) -> {error, unknown}. %% insert enum values insert_enum(Tab, #enum_type { id = ID, values = VMap }) -> @@ -241,7 +282,7 @@ insert_enum(Tab, #enum_type { id = ID, values = VMap }) -> ok. append_enum_id(Tab, Key, ID) -> - CurrentIDs = try ets:lookup_element(?ENUMS, Key, 2) of + CurrentIDs = try ets:lookup_element(Tab, Key, 2) of EnumIDsMap -> EnumIDsMap catch error:badarg -> diff --git a/src/graphql_schema.hrl b/src/graphql_schema.hrl index a4de45b..5aff496 100644 --- a/src/graphql_schema.hrl +++ b/src/graphql_schema.hrl @@ -126,3 +126,7 @@ -record(document, { definitions :: [any()] }). + +-record(endpoint_context, {name :: atom(), pid :: pid(), enums_tab :: atom(), objects_tab :: atom()}). + +-type endpoint_context() :: #endpoint_context{}. diff --git a/src/graphql_schema_parse.erl b/src/graphql_schema_parse.erl index 51a7570..dcc5e84 100644 --- a/src/graphql_schema_parse.erl +++ b/src/graphql_schema_parse.erl @@ -3,32 +3,34 @@ -include("graphql_internal.hrl"). -include("graphql_schema.hrl"). --export([inject/2]). +-export([inject/3]). -inject(BaseMapping, {ok, #document { definitions = Entries}}) -> +inject(Ep, BaseMapping, {ok, #document { definitions = Entries}}) -> Mapping = handle_mapping(BaseMapping), {SchemaEntries, Other} = lists:partition(fun schema_defn/1, Entries), report_other_entries(Other), - Defs = [mk(Mapping, E) || E <- SchemaEntries], + Defs = [mk(Ep, Mapping, E) || E <- SchemaEntries], case lists:keytake(schema, 1, Defs) of false -> - [inject(Def) || Def <- Defs]; + [inject(Ep, Def) || Def <- Defs]; {value, Root, Rest} -> %% Guard against multiple schema defs false = lists:keytake(schema, 1, Rest), - [inject(R) || R <- Rest], - inject_root(Root) + [inject(Ep, R) || R <- Rest], + inject_root(Ep, Root) end, ok. -mk(#{}, #p_schema_definition { directives = Directives, - defs = RootOps }) -> +mk(_Ep, #{}, #p_schema_definition { directives = Directives, + defs = RootOps }) -> {schema, #{ defs => maps:from_list([root_op(R) || R <- RootOps]), directives => Directives }}; -mk(#{ scalars := Sc }, #p_scalar { description = Desc, - directives = Directives, - id = ID }) -> +mk(_Ep, + #{ scalars := Sc }, + #p_scalar { description = Desc, + directives = Directives, + id = ID }) -> Name = name(ID), Mod = mapping(Name, Sc), {scalar, #{ @@ -37,20 +39,23 @@ mk(#{ scalars := Sc }, #p_scalar { description = Desc, description => description(Desc), resolve_module => Mod }}; -mk(#{ unions := Us }, #p_union { id = ID, - directives = Directives, - description = Desc, - members = Ms }) -> +mk(Ep, + #{ unions := Us }, + #p_union { id = ID, + directives = Directives, + description = Desc, + members = Ms }) -> Name = name(ID), Mod = mapping(Name, Us), - Types = [handle_type(M) || M <- Ms], + Types = [handle_type(Ep, M) || M <- Ms], {union, #{ id => Name, description => description(Desc), directives => Directives, resolve_module => Mod, types => Types }}; -mk(#{ objects := OM }, +mk(Ep, + #{ objects := OM }, #p_object { id = ID, description = Desc, directives = Directives, @@ -58,7 +63,7 @@ mk(#{ objects := OM }, interfaces = Impls }) -> Name = name(ID), Mod = mapping(Name, OM), - Fields = fields(Fs), + Fields = fields(Ep, Fs), Implements = [name(I) || I <- Impls], {object, #{ id => Name, @@ -67,7 +72,8 @@ mk(#{ objects := OM }, directives => Directives, resolve_module => Mod, interfaces => Implements }}; -mk(#{ enums := En }, +mk(_Ep, + #{ enums := En }, #p_enum {id = ID, directives = Directives, description = Desc, @@ -81,27 +87,29 @@ mk(#{ enums := En }, directives => Directives, values => Variants, resolve_module => Mod }}; -mk(_Map, +mk(Ep, + _Map, #p_input_object { id = ID, description = Desc, directives = Directives, defs = Ds }) -> Name = name(ID), - Defs = input_defs(Ds), + Defs = input_defs(Ep, Ds), {input_object, #{ id => Name, description => description(Desc), directives => Directives, fields => Defs }}; -mk(#{ interfaces := IF }, +mk(Ep, + #{ interfaces := IF }, #p_interface { id = ID, description = Description, directives = Directives, fields = FS }) -> Name = name(ID), Mod = mapping(Name, IF), - Fields = fields(FS), + Fields = fields(Ep, FS), {interface, #{ id => Name, @@ -110,7 +118,8 @@ mk(#{ interfaces := IF }, directives => Directives, fields => Fields }}; -mk(#{}, +mk(Ep, + #{}, #p_directive{ id = ID, description = Description, args = Args, @@ -120,22 +129,22 @@ mk(#{}, #{ id => Name, description => description(Description), - args => handle_args(Args), + args => handle_args(Ep, Args), locations => Locations }}. -fields(Raw) -> - maps:from_list([field(R) || R <- Raw]). +fields(Ep, Raw) -> + maps:from_list([field(Ep, R) || R <- Raw]). -input_defs(Raw) -> - maps:from_list([input_def(D) || D <- Raw]). +input_defs(Ep, Raw) -> + maps:from_list([input_def(Ep, D) || D <- Raw]). -inject_root(Root) -> - graphql:insert_root(Root). +inject_root(Ep, Root) -> + graphql:insert_root(Ep, Root). -inject(Def) -> - case graphql:insert_schema_definition(Def) of +inject(Ep, Def) -> + case graphql:insert_schema_definition(Ep, Def) of ok -> ok; {error, {already_exists, Entry}} -> @@ -176,20 +185,20 @@ name({name, _, N}) -> N. description(undefined) -> <<"No description provided">>; description(D) -> D. -input_def(#p_input_value { id = ID, +input_def(Ep, #p_input_value { id = ID, description = Desc, directives = Directives, default = Default, type = Type }) -> Name = name(ID), K = binary_to_atom(Name, utf8), - V = #{ type => handle_type(Type), + V = #{ type => handle_type(Ep, Type), default => Default, directives => Directives, description =>description(Desc) }, {K, V}. -field(#p_field_def{ id = ID, +field(Ep, #p_field_def{ id = ID, description = Desc, directives = Directives, type = T, @@ -197,10 +206,10 @@ field(#p_field_def{ id = ID, Name = name(ID), %% We assume schemas are always under our control, so this is safe K = binary_to_atom(Name, utf8), - V = #{ type => handle_type(T), + V = #{ type => handle_type(Ep, T), description => description(Desc), directives => Directives, - args => handle_args(Args)}, + args => handle_args(Ep, Args)}, {K, V}. root_op(#p_root_operation { op_type = OpType, @@ -211,8 +220,8 @@ root_op(#p_root_operation { op_type = OpType, {subscription, _} -> {subscription, name(Name)} end. -handle_args(Args) -> - maps:from_list([input_def(A) || A <- Args]). +handle_args(Ep, Args) -> + maps:from_list([input_def(Ep, A) || A <- Args]). variants(Vs) -> F = fun @@ -231,13 +240,13 @@ mapi(F, L) -> mapi(F, L, 0). mapi(_F, [], _) -> []; mapi(F, [X|Xs], K) -> [F(X, K) | mapi(F, Xs, K+1)]. -handle_type({non_null, T}) -> {non_null, handle_type(T)}; -handle_type({list, T}) -> {list, handle_type(T)}; -handle_type({name, _, T}) -> binary_to_atom(T, utf8); -handle_type({scalar, Name}) -> - #scalar_type{} = Ty = graphql_schema:get(Name), - handle_type(Ty); -handle_type(#scalar_type{ id = Id }) -> binary_to_atom(Id, utf8). +handle_type(Ep, {non_null, T}) -> {non_null, handle_type(Ep, T)}; +handle_type(Ep, {list, T}) -> {list, handle_type(Ep, T)}; +handle_type(_Ep, {name, _, T}) -> binary_to_atom(T, utf8); +handle_type(Ep, {scalar, Name}) -> + #scalar_type{} = Ty = graphql_schema:get(Ep, Name), + handle_type(Ep, Ty); +handle_type(_Ep, #scalar_type{ id = Id }) -> binary_to_atom(Id, utf8). mapping(Name, Map) -> case maps:get(Name, Map, undefined) of diff --git a/src/graphql_schema_validate.erl b/src/graphql_schema_validate.erl index 8271fb5..8eac497 100644 --- a/src/graphql_schema_validate.erl +++ b/src/graphql_schema_validate.erl @@ -3,7 +3,7 @@ -include("graphql_schema.hrl"). -include("graphql.hrl"). --export([x/0, root/1]). +-export([x/1, root/2]). -define (DIRECTIVE_LOCATIONS, [ 'QUERY', 'MUTATION', 'SUBSCRIPTION', 'FIELD', 'FRAGMENT_DEFINITION', @@ -11,20 +11,20 @@ 'FIELD_DEFINITION', 'ARGUMENT_DEFINITION', 'INTERFACE', 'UNION', 'ENUM', 'ENUM_VALUE', 'INPUT_OBJECT', 'INPUT_FIELD_DEFINITION']). --spec root(#root_schema{}) -> #root_schema{}. -root(#root_schema{ query = Q, +-spec root(endpoint_context(), #root_schema{}) -> #root_schema{}. +root(Ep, #root_schema{ query = Q, mutation = M, subscription = S} = Root) -> - ok = x(), - {ok, QC} = root_lookup(Q, query), - {ok, MC} = root_lookup(M, mutation), - {ok, SC} = root_lookup(S, subscription), + ok = x(Ep), + {ok, QC} = root_lookup(Ep, Q, query), + {ok, MC} = root_lookup(Ep, M, mutation), + {ok, SC} = root_lookup(Ep, S, subscription), Root#root_schema { query = QC, mutation = MC, subscription = SC }. -root_lookup(undefined, Type) -> +root_lookup(Ep, undefined, Type) -> %% If given an undefined entry, try to use the default %% and inject it. Make mention that we are using a default %% Value @@ -33,28 +33,28 @@ root_lookup(undefined, Type) -> mutation -> <<"Mutation">>; subscription -> <<"Subscription">> end, - root_lookup_(Val, Type, default); -root_lookup(Val, Type) -> - root_lookup_(Val, Type, direct). + root_lookup_(Ep, Val, Type, default); +root_lookup(Ep, Val, Type) -> + root_lookup_(Ep, Val, Type, direct). %% Root lookup rules are as follows: %% - Queries MUST exist and it has to be the default value if nothing has %% been added %% - Directly written Mutations and Subscriptions MUST exist %% - Try to coerce otherwise -root_lookup_(Q, Type, Def) -> - case graphql_schema:lookup(Q) of +root_lookup_(Ep, Q, Type, Def) -> + case graphql_schema:lookup(Ep, Q) of not_found when Type == query -> err({schema_without_query, Q}); not_found when Def == direct -> err({schema_missing_type, Q}); not_found -> {ok, undefined}; #object_type{} -> {ok, Q} end. --spec x() -> ok. -x() -> - Objects = graphql_schema:all(), +-spec x(endpoint_context()) -> ok. +x(Ep) -> + Objects = graphql_schema:all(Ep), try - [x(Obj) || Obj <- Objects], + [x(Ep, Obj) || Obj <- Objects], ok catch throw:Error -> @@ -66,8 +66,8 @@ x() -> exit(Error) end. -x(Obj) -> - try validate(Obj) of +x(Ep, Obj) -> + try validate(Ep, Obj) of ok -> ok catch throw:{invalid, Reason} -> @@ -75,34 +75,34 @@ x(Obj) -> end. -validate(#scalar_type {} = X) -> scalar_type(X); -validate(#root_schema {} = X) -> root_schema(X); -validate(#object_type {} = X) -> object_type(X); -validate(#enum_type {} = X) -> enum_type(X); -validate(#interface_type {} = X) -> interface_type(X); -validate(#union_type {} = X) -> union_type(X); -validate(#input_object_type {} = X) -> input_object_type(X); -validate(#directive_type{} = X) -> directive_type(X). +validate(Ep, #scalar_type {} = X) -> scalar_type(Ep, X); +validate(Ep, #root_schema {} = X) -> root_schema(Ep, X); +validate(Ep, #object_type {} = X) -> object_type(Ep, X); +validate(Ep, #enum_type {} = X) -> enum_type(Ep, X); +validate(Ep, #interface_type {} = X) -> interface_type(Ep, X); +validate(Ep, #union_type {} = X) -> union_type(Ep, X); +validate(Ep, #input_object_type {} = X) -> input_object_type(Ep, X); +validate(Ep, #directive_type{} = X) -> directive_type(Ep, X). -scalar_type(#scalar_type {directives = Ds}) -> - is_valid_directives(Ds, 'SCALAR'), +scalar_type(Ep, #scalar_type {directives = Ds}) -> + is_valid_directives(Ep, Ds, 'SCALAR'), ok. -enum_type(#enum_type { directives = Ds, values = Vs }) -> - is_valid_directives(Ds, 'ENUM'), - all(fun schema_enum_value/1, maps:to_list(Vs)), +enum_type(Ep, #enum_type { directives = Ds, values = Vs }) -> + is_valid_directives(Ep, Ds, 'ENUM'), + all(Ep, fun schema_enum_value/2, maps:to_list(Vs)), ok. -input_object_type(#input_object_type { fields = FS, directives = Ds }) -> - all(fun schema_input_type_arg/1, maps:to_list(FS)), - is_valid_directives(Ds, 'INPUT_OBJECT'), +input_object_type(Ep, #input_object_type { fields = FS, directives = Ds }) -> + all(Ep, fun schema_input_type_arg/2, maps:to_list(FS)), + is_valid_directives(Ep, Ds, 'INPUT_OBJECT'), ok. -union_type(#union_type { types = [] } = Union) -> +union_type(_Ep, #union_type { types = [] } = Union) -> err({empty_union, Union}); -union_type(#union_type { types = Types, directives = Ds }) -> - all(fun is_union_type/1, Types), - is_valid_directives(Ds, 'UNION'), +union_type(Ep, #union_type { types = Types, directives = Ds }) -> + all(Ep, fun is_union_type/2, Types), + is_valid_directives(Ep, Ds, 'UNION'), case unique([name(T) || T <- Types]) of ok -> ok; @@ -110,60 +110,60 @@ union_type(#union_type { types = Types, directives = Ds }) -> err({union_not_unique, X}) end. -interface_type(#interface_type { fields= FS, directives = Ds }) -> - all(fun schema_field/1, maps:to_list(FS)), - is_valid_directives(Ds, 'INTERFACE'), +interface_type(Ep, #interface_type { fields= FS, directives = Ds }) -> + all(Ep, fun schema_field/2, maps:to_list(FS)), + is_valid_directives(Ep, Ds, 'INTERFACE'), ok. -object_type(#object_type { +object_type(Ep, #object_type { fields = FS, interfaces = IFaces, directives = Ds} = Obj) -> - all(fun is_interface/1, IFaces), - all(fun(IF) -> implements(lookup(IF), Obj) end, IFaces), - all(fun schema_field/1, maps:to_list(FS)), - is_valid_directives(Ds, 'OBJECT'), + all(Ep, fun is_interface/2, IFaces), + all(Ep, fun(Ctx, IF) -> implements(lookup(Ctx, IF), Obj) end, IFaces), + all(Ep, fun schema_field/2, maps:to_list(FS)), + is_valid_directives(Ep, Ds, 'OBJECT'), ok. -directive_type(#directive_type { +directive_type(Ep, #directive_type { args = _Args, locations = Locations }) -> - all(fun is_directive_location/1, Locations), + all(Ep, fun is_directive_location/2, Locations), ok. -root_schema(#root_schema { +root_schema(Ep, #root_schema { query = Q, mutation = M, subscription = S, interfaces = IFaces, directives = Ds }) -> - is_object(Q), - undefined_object(M), - undefined_object(S), - all(fun is_interface/1, IFaces), - is_valid_directives(Ds, 'SCHEMA'), + is_object(Ep, Q), + undefined_object(Ep, M), + undefined_object(Ep, S), + all(Ep, fun is_interface/2, IFaces), + is_valid_directives(Ep, Ds, 'SCHEMA'), ok. -schema_field({_, #schema_field { ty = Ty, args = Args, directives = Ds }}) -> - all(fun schema_input_type_arg/1, maps:to_list(Args)), - type(Ty), - is_valid_directives(Ds, 'FIELD_DEFINITION'), +schema_field(Ep, {_, #schema_field { ty = Ty, args = Args, directives = Ds }}) -> + all(Ep, fun schema_input_type_arg/2, maps:to_list(Args)), + type(Ep, Ty), + is_valid_directives(Ep, Ds, 'FIELD_DEFINITION'), ok. -schema_input_type_arg({_, #schema_arg { ty = Ty }}) -> +schema_input_type_arg(Ep, {_, #schema_arg { ty = Ty }}) -> %% TODO: Default check! %% TODO: argument definition directive check - input_type(Ty), + input_type(Ep, Ty), ok. -schema_enum_value({_, #enum_value { directives = Ds }}) -> - is_valid_directives(Ds, 'ENUM_VALUE'), +schema_enum_value(Ep, {_, #enum_value { directives = Ds }}) -> + is_valid_directives(Ep, Ds, 'ENUM_VALUE'), ok. -undefined_object(undefined) -> ok; -undefined_object(Obj) -> is_object(Obj). +undefined_object(_Ep, undefined) -> ok; +undefined_object(Ep, Obj) -> is_object(Ep, Obj). implements(#interface_type { fields = IFFields } = IFace, #object_type { fields = ObjFields }) -> @@ -195,31 +195,31 @@ implements_field_check([{IK, _} | _] = IL, [{OK, _} | OS]) when IK > OK -> implements_field_check([{IK, _} | _], [{OK, _} | _]) when IK < OK -> {error, {field_not_found_in_object, IK}}. -is_interface(IFace) -> - case lookup(IFace) of +is_interface(Ep, IFace) -> + case lookup(Ep, IFace) of #interface_type{} -> ok; _ -> err({not_interface, IFace}) end. -is_object(Obj) -> - case lookup(Obj) of +is_object(Ep, Obj) -> + case lookup(Ep, Obj) of #object_type{} -> ok; _ -> err({not_object, Obj}) end. -is_union_type(Obj) -> - case lookup(Obj) of +is_union_type(Ep, Obj) -> + case lookup(Ep, Obj) of #object_type{} -> ok; _ -> err({not_union_type, Obj}) end. -is_valid_directives(Dirs, Location) -> - all(fun(D) -> is_valid_directive(D, Location) end, Dirs). +is_valid_directives(Ep, Dirs, Location) -> + all(Ep, fun(Ctx, D) -> is_valid_directive(Ctx, D, Location) end, Dirs). -is_valid_directive(#directive{ id = Id }, Location) -> - is_valid_directive(name(Id), Location); -is_valid_directive(Dir, Location) -> - try lookup(Dir) of +is_valid_directive(Ep, #directive{ id = Id }, Location) -> + is_valid_directive(Ep, name(Id), Location); +is_valid_directive(Ep, Dir, Location) -> + try lookup(Ep, Dir) of #directive_type{ id = Id, locations = Locations } -> case lists:member(Location, Locations) of true -> ok; @@ -235,16 +235,16 @@ is_valid_directive(Dir, Location) -> err({not_directive, Dir}) end. -is_directive_location(Loc) -> +is_directive_location(_Ep, Loc) -> case lists:member(Loc, ?DIRECTIVE_LOCATIONS) of true -> ok; false -> err({not_directive_location, Loc}) end. -type({non_null, T}) -> type(T); -type({list, T}) -> type(T); -type(X) when is_binary(X) -> - case lookup(X) of +type(Ep, {non_null, T}) -> type(Ep, T); +type(Ep, {list, T}) -> type(Ep, T); +type(Ep, X) when is_binary(X) -> + case lookup(Ep, X) of #input_object_type {} -> err({invalid_output_type, X}); @@ -252,10 +252,10 @@ type(X) when is_binary(X) -> ok end. -input_type({non_null, T}) -> input_type(T); -input_type({list, T}) -> input_type(T); -input_type(X) when is_binary(X) -> - case lookup(X) of +input_type(Ep, {non_null, T}) -> input_type(Ep, T); +input_type(Ep, {list, T}) -> input_type(Ep, T); +input_type(Ep, X) when is_binary(X) -> + case lookup(Ep, X) of #input_object_type {} -> ok; #enum_type {} -> ok; #scalar_type {} -> ok; @@ -263,13 +263,13 @@ input_type(X) when is_binary(X) -> err({invalid_input_type, X}) end. -all(_F, []) -> ok; -all(F, [E|Es]) -> - ok = F(E), - all(F, Es). +all(_Ep, _F, []) -> ok; +all(Ep, F, [E|Es]) -> + ok = F(Ep, E), + all(Ep, F, Es). -lookup(Key) -> - case graphql_schema:lookup(Key) of +lookup(Ep, Key) -> + case graphql_schema:lookup(Ep, Key) of not_found -> err({not_found, Key}); X -> X end.