Skip to content

Commit

Permalink
add multi endpoint support
Browse files Browse the repository at this point in the history
- Multiple endpoints may now run independently each with their own schema process.
- Global context has been replaced with `endpoint_context` type containing schema PID and ets tables.
- New API in `graphql` with `ep_` prefix (ex `ep_execute()`).  These take an `endpoint_context`.
- New API in `graphql` with `p_` prefix (ex `p_execute()`).  These take a schema PID/atom.  This is most useful when you have a named schema process that can be
referred to by its name atom.
- Existing API wraps the `ep_` API and uses the named schema process `graphql_default_endpoint`
- `endpoint_context` is passed inside `Ctx` maps.  The variable `EP` is added when `endpoint_context` is needed but no `Ctx` is available.
- ct tests pass
- addresses case jlouis#91
  • Loading branch information
goertzenator committed Apr 13, 2018
1 parent 361733c commit 2cf0500
Show file tree
Hide file tree
Showing 12 changed files with 618 additions and 480 deletions.
169 changes: 134 additions & 35 deletions src/graphql.erl
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
%% GraphQL Documents
-export([
parse/1,
elaborate/1,
type_check/1, type_check_params/3,
elaborate/1, p_elaborate/2, ep_elaborate/2,
type_check/1, p_type_check/2, ep_type_check/2,
type_check_params/3, p_type_check_params/4, ep_type_check_params/4,
validate/1,
execute/1, execute/2
execute/1, p_execute/2, ep_execute/2,
execute/2, p_execute/3, ep_execute/3
]).

-export([
Expand All @@ -27,9 +29,14 @@

%% Schema Definitions
-export([
load_schema/2,
insert_schema_definition/1,
validate_schema/0
load_schema/2, p_load_schema/3, ep_load_schema/3,
insert_schema_definition/1, p_insert_schema_definition/2, ep_insert_schema_definition/2,
validate_schema/0, p_validate_schema/1, ep_validate_schema/1
]).

-export([
get_endpoint/1,
default_endpoint/0
]).

%% Internal
Expand All @@ -50,6 +57,15 @@

-define(DEFAULT_TIMEOUT, 750).


%% Endpoints
-type server_ref() :: pid() | atom().
-type endpoint_context() :: graphql_schema:endpoint_context().





%% EARLY EXIT
%% --------------------------------------------------------------------------
throw(Msg) ->
Expand Down Expand Up @@ -93,61 +109,144 @@ 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) ->


%% Endpoint API
%% All calls require an endpoint_context() which can be acquired with
%% get_endpoint(server_ref()) and default_endpoint().


ep_load_schema(EP, Mapping, Input) when is_binary(Input) ->
ep_load_schema(EP, Mapping, binary_to_list(Input));
ep_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;
{error, Err, _EndLine} ->
{error, Err}
end.

-spec validate(ast()) -> ok | {error, term()}.
validate(AST) ->
graphql_validate:x(AST).
-spec ep_type_check(endpoint_context(), ast()) -> {ok, #{ atom() => term() }}.
ep_type_check(EP, AST) ->
graphql_type_check:x(EP, AST).

-spec ep_elaborate(endpoint_context(), ast()) -> ast().
ep_elaborate(EP, AST) ->
graphql_elaborate:x(EP, AST).

-spec ep_type_check_params(endpoint_context(), any(), any(), any()) -> param_context().
ep_type_check_params(EP, FunEnv, OpName, Vars) ->
graphql_type_check:x_params(EP, FunEnv, OpName, Vars).

-spec ep_execute(endpoint_context(), ast()) -> #{ atom() => json() }.
ep_execute(EP, AST) ->
Ctx = #{ params => #{}, default_timeout => ?DEFAULT_TIMEOUT },
ep_execute(EP, Ctx, AST).

-spec ep_execute(endpoint_context(), context(), ast()) -> #{ atom() => json() }.
ep_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.

%% @doc insert_schema_definition/1 loads a schema definition into the Graph Schema
%% @end
-spec ep_insert_schema_definition(endpoint_context(), schema_definition()) -> ok | {error, Reason}
when Reason :: term().
ep_insert_schema_definition(EP, Defn) ->
graphql_schema:load(EP, Defn).

%% STUB for now
-spec ep_validate_schema(endpoint_context()) -> ok | {error, any()}.
ep_validate_schema(EP) ->
graphql_schema_validate:x(EP).





%% Pid API
%% All calls require a pid()/atom() that refers to the schema gen_server.

-spec get_endpoint(server_ref()) -> endpoint_context().
get_endpoint(P) -> graphql_schema:get_endpoint(P).


p_load_schema(P, Mapping, Input) -> ep_load_schema(get_endpoint(P), Mapping, Input).

-spec p_type_check(server_ref(), ast()) -> {ok, #{ atom() => term() }}.
p_type_check(P, AST) -> ep_type_check(get_endpoint(P), AST).

-spec p_elaborate(server_ref(), ast()) -> ast().
p_elaborate(P, AST) -> ep_elaborate(get_endpoint(P), AST).

-spec p_type_check_params(server_ref(), any(), any(), any()) -> param_context().
p_type_check_params(P, FunEnv, OpName, Vars) -> ep_type_check_params(get_endpoint(P), FunEnv, OpName, Vars).

-spec p_execute(server_ref(), ast()) -> #{ atom() => json() }.
p_execute(P, AST) -> ep_execute(get_endpoint(P), AST).

-spec p_execute(server_ref(), context(), ast()) -> #{ atom() => json() }.
p_execute(P, Ctx, AST) -> ep_execute(get_endpoint(P), Ctx, AST).

%% @doc insert_schema_definition/1 loads a schema definition into the Graph Schema
%% @end
-spec p_insert_schema_definition(server_ref(), schema_definition()) -> ok | {error, Reason}
when Reason :: term().
p_insert_schema_definition(P, Defn) -> ep_insert_schema_definition(get_endpoint(P), Defn).

%% STUB for now
-spec p_validate_schema(server_ref()) -> ok | {error, any()}.
p_validate_schema(P) -> ep_validate_schema(get_endpoint(P)).


%% Default Endpoint API
%% All calls implicitly refer to the application default endpoint.

-spec default_endpoint() -> endpoint_context().
default_endpoint() -> get_endpoint(graphql_default_endpoint).


load_schema(Mapping, Input) -> ep_load_schema(default_endpoint(), Mapping, Input).

-spec type_check(ast()) -> {ok, #{ atom() => term() }}.
type_check(AST) ->
graphql_type_check:x(AST).
type_check(AST) -> ep_type_check(default_endpoint(), AST).

-spec elaborate(ast()) -> ast().
elaborate(AST) ->
graphql_elaborate:x(AST).
elaborate(AST) -> ep_elaborate(default_endpoint(), AST).

-spec type_check_params(any(), any(), any()) -> param_context().
type_check_params(FunEnv, OpName, Vars) ->
graphql_type_check:x_params(FunEnv, OpName, Vars).
type_check_params(FunEnv, OpName, Vars) -> ep_type_check_params(default_endpoint(), FunEnv, OpName, Vars).

-spec execute(ast()) -> #{ atom() => json() }.
execute(AST) ->
Ctx = #{ params => #{}, default_timeout => ?DEFAULT_TIMEOUT },
execute(Ctx, AST).
execute(AST) -> ep_execute(default_endpoint(), AST).

-spec execute(context(), ast()) -> #{ 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
#{ errors := Errs } = Result ->
Result#{ errors := graphql_err:format_errors(Ctx, Errs) };
Result -> Result
end.
execute(Ctx, AST) -> ep_execute(default_endpoint(), Ctx, AST).

%% @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).
when Reason :: term().
insert_schema_definition(Defn) -> ep_insert_schema_definition(default_endpoint(), Defn).

%% STUB for now
-spec validate_schema() -> ok | {error, any()}.
validate_schema() ->
graphql_schema_validate:x().
validate_schema() -> ep_validate_schema(default_endpoint()).



%% Schema Independent API
%% Use in conjunction with any of the above API variants.

-spec validate(ast()) -> ok | {error, term()}.
validate(AST) ->
graphql_validate:x(AST).

26 changes: 13 additions & 13 deletions src/graphql_builtins.erl
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

-include("graphql_schema.hrl").

-export([directive_schema/1]).
-export([standard_types_inject/0]).
-export([directive_schema/2]).
-export([standard_types_inject/1]).

-spec standard_types_inject() -> ok.
standard_types_inject() ->
-spec standard_types_inject(graphql_schema:endpoint_context()) -> ok.
standard_types_inject(EP) ->
String = {scalar, #{
id => 'String',
description => <<"UTF-8 Text Strings"/utf8>> }},
Expand All @@ -22,33 +22,33 @@ 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:ep_insert_schema_definition(EP, String),
ok = graphql:ep_insert_schema_definition(EP, Float),
ok = graphql:ep_insert_schema_definition(EP, Int),
ok = graphql:ep_insert_schema_definition(EP, Bool),
ok = graphql:ep_insert_schema_definition(EP, ID),
ok.

%% Construct schema types for the directives the system supports
directive_schema(include) ->
directive_schema(EP, include) ->
#directive_type {
id = <<"include">>,
locations = [field, fragment_spread, inline_fragment],
args = #{
<<"if">> =>
#schema_arg{
ty = graphql_schema:get(<<"Bool">>),
ty = graphql_schema:get(EP, <<"Bool">>),
default = false,
description = <<"Wether or not the item should be included">> }
}};
directive_schema(skip) ->
directive_schema(EP, skip) ->
#directive_type {
id = <<"skip">>,
locations = [field, fragment_spread, inline_fragment],
args = #{
<<"if">> =>
#schema_arg{
ty = graphql_schema:get(<<"Bool">>),
ty = graphql_schema:get(EP, <<"Bool">>),
default = false,
description = <<"Wether or not the item should be skipped">> }
}}.
Expand Down
13 changes: 7 additions & 6 deletions src/graphql_dot.erl
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
-export([dump/1]).

-spec dump(string()) -> ok.
dump(FName) ->
Graph = x(),
dump(FName) -> ep_dump(graphql:default_endpoint(), FName).
ep_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".
Expand All @@ -30,8 +31,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],
Expand Down
Loading

0 comments on commit 2cf0500

Please sign in to comment.