Skip to content
This repository was archived by the owner on Jul 25, 2024. It is now read-only.

Commit fa58ba7

Browse files
committed
Separating out the main plug to have two versions.
* Version that can only execute queries * Seperate version that can load GraphiQL
1 parent f25ed54 commit fa58ba7

File tree

8 files changed

+315
-87
lines changed

8 files changed

+315
-87
lines changed

lib/graphql/plug.ex

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,25 +26,26 @@ defmodule GraphQL.Plug do
2626
pass: ["*/*"],
2727
json_decoder: Poison
2828

29-
plug GraphQL.Plug.Endpoint
29+
# plug GraphQL.Plug.Endpoint
30+
3031
# TODO extract
3132
# plug GraphQL.Plug.GraphiQL
3233

33-
# TODO remove duplication call GraphQL.Plug.Helper.extract_init_options/1 here
3434
def init(opts) do
35-
schema = case Keyword.get(opts, :schema) do
36-
{mod, func} -> apply(mod, func, [])
37-
s -> s
38-
end
39-
root_value = Keyword.get(opts, :root_value, %{})
40-
%{:schema => schema, :root_value => root_value}
35+
GraphQL.Plug.BareEndpoint.init(opts)
4136
end
4237

4338
def call(conn, opts) do
4439
# TODO use private
4540
conn = assign(conn, :graphql_options, opts)
4641
conn = super(conn, opts)
4742

43+
conn = if allow_graphiql? do
44+
GraphQL.Plug.Endpoint.call(conn, opts)
45+
else
46+
GraphQL.Plug.BareEndpoint.call(conn, opts)
47+
end
48+
4849
# TODO consider not logging instrospection queries
4950
Logger.debug """
5051
Processed GraphQL query:
@@ -54,4 +55,9 @@ defmodule GraphQL.Plug do
5455

5556
conn
5657
end
58+
59+
defp allow_graphiql? do
60+
config = Application.get_env(:plug_graphql, __MODULE__, [])
61+
Keyword.get(config, :allow_graphiql, false)
62+
end
5763
end

lib/graphql/plug/bare_endpoint.ex

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
defmodule GraphQL.Plug.BareEndpoint do
2+
@moduledoc """
3+
This is the core plug for mounting a GraphQL server.
4+
5+
You can build your own pipeline by mounting the
6+
`GraphQL.Plug.Endpoint` plug directly.
7+
8+
```elixir
9+
forward "/graphql", GraphQL.Plug.Endpoint, schema: {MyApp.Schema, :schema}
10+
```
11+
12+
You may want to look at how `GraphQL.Plug` configures its pipeline.
13+
Specifically note how `Plug.Parsers` are configured, as this is required
14+
for pre-parsing the various POST bodies depending on `content-type`.
15+
16+
This plug currently includes _GraphiQL_ support but this should end
17+
up in it's own plug.
18+
"""
19+
20+
import Plug.Conn
21+
alias Plug.Conn
22+
alias GraphQL.Plug.RootValue
23+
alias GraphQL.Plug.Parameters
24+
25+
@behaviour Plug
26+
27+
def init(opts) do
28+
schema = case Keyword.get(opts, :schema) do
29+
{mod, func} -> apply(mod, func, [])
30+
s -> s
31+
end
32+
root_value = Keyword.get(opts, :root_value, %{})
33+
%{schema: schema, root_value: root_value}
34+
end
35+
36+
def call(%Conn{method: m} = conn, opts) when m in ["GET", "POST"] do
37+
%{schema: schema, root_value: root_value} = conn.assigns[:graphql_options] || opts
38+
39+
query = Parameters.query(conn)
40+
variables = Parameters.variables(conn)
41+
operation_name = Parameters.operation_name(conn)
42+
evaluated_root_value = RootValue.evaluate(conn, root_value)
43+
44+
cond do
45+
query ->
46+
handle_call(conn, schema, evaluated_root_value, query, variables, operation_name)
47+
true ->
48+
handle_error(conn, "Must provide query string.")
49+
end
50+
end
51+
52+
def call(%Conn{method: _} = conn, _) do
53+
handle_error(conn, "GraphQL only supports GET and POST requests.")
54+
end
55+
56+
def handle_error(conn, message) do
57+
{:ok, errors} = Poison.encode %{errors: [%{message: message}]}
58+
conn
59+
|> put_resp_content_type("application/json")
60+
|> send_resp(400, errors)
61+
end
62+
63+
def handle_call(conn, schema, root_value, query, variables, operation_name) do
64+
conn
65+
|> put_resp_content_type("application/json")
66+
|> execute(schema, root_value, query, variables, operation_name)
67+
end
68+
69+
defp execute(conn, schema, root_value, query, variables, operation_name) do
70+
case GraphQL.execute(schema, query, root_value, variables, operation_name) do
71+
{:ok, data} ->
72+
case Poison.encode(data) do
73+
{:ok, json} -> send_resp(conn, 200, json)
74+
{:error, errors} -> send_resp(conn, 400, errors)
75+
end
76+
{:error, errors} ->
77+
case Poison.encode(errors) do
78+
{:ok, json} -> send_resp(conn, 400, json)
79+
{:error, errors} -> send_resp(conn, 400, errors)
80+
end
81+
end
82+
end
83+
end

lib/graphql/plug/endpoint.ex

Lines changed: 12 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ defmodule GraphQL.Plug.Endpoint do
1919

2020
import Plug.Conn
2121
alias Plug.Conn
22+
alias GraphQL.Plug.BareEndpoint
23+
alias GraphQL.Plug.RootValue
24+
alias GraphQL.Plug.Parameters
2225

2326
@behaviour Plug
2427

@@ -46,40 +49,29 @@ defmodule GraphQL.Plug.Endpoint do
4649
[:graphiql_version, :query, :variables, :result]
4750

4851
def init(opts) do
49-
schema = case Keyword.get(opts, :schema) do
50-
{mod, func} -> apply(mod, func, [])
51-
s -> s
52-
end
53-
root_value = Keyword.get(opts, :root_value, %{})
54-
%{schema: schema, root_value: root_value}
52+
BareEndpoint.init(opts)
5553
end
5654

5755
def call(%Conn{method: m} = conn, opts) when m in ["GET", "POST"] do
5856
%{schema: schema, root_value: root_value} = conn.assigns[:graphql_options] || opts
5957

60-
query = query(conn)
61-
variables = variables(conn)
62-
operation_name = operation_name(conn)
63-
evaluated_root_value = evaluate_root_value(conn, root_value)
58+
query = Parameters.query(conn)
59+
variables = Parameters.variables(conn)
60+
operation_name = Parameters.operation_name(conn)
61+
evaluated_root_value = RootValue.evaluate(conn, root_value)
6462

6563
cond do
6664
use_graphiql?(conn) ->
6765
handle_graphiql_call(conn, schema, evaluated_root_value, query, variables, operation_name)
6866
query ->
69-
handle_call(conn, schema, evaluated_root_value, query, variables, operation_name)
67+
BareEndpoint.handle_call(conn, schema, evaluated_root_value, query, variables, operation_name)
7068
true ->
71-
handle_error(conn, "Must provide query string.")
69+
BareEndpoint.handle_error(conn, "Must provide query string.")
7270
end
7371
end
7472

7573
def call(%Conn{method: _} = conn, _) do
76-
handle_error(conn, "GraphQL only supports GET and POST requests.")
77-
end
78-
79-
defp handle_call(conn, schema, root_value, query, variables, operation_name) do
80-
conn
81-
|> put_resp_content_type("application/json")
82-
|> execute(schema, root_value, query, variables, operation_name)
74+
BareEndpoint.handle_error(conn, "GraphQL only supports GET and POST requests.")
8375
end
8476

8577
defp escape_string(s) do
@@ -94,72 +86,13 @@ defmodule GraphQL.Plug.Endpoint do
9486
{_, data} = GraphQL.execute(schema, query, root_value, variables, operation_name)
9587
{:ok, variables} = Poison.encode(variables, pretty: true)
9688
{:ok, result} = Poison.encode(data, pretty: true)
89+
9790
graphiql = graphiql_html(@graphiql_version, escape_string(query), escape_string(variables), escape_string(result))
9891
conn
9992
|> put_resp_content_type("text/html")
10093
|> send_resp(200, graphiql)
10194
end
10295

103-
defp handle_error(conn, message) do
104-
{:ok, errors} = Poison.encode %{errors: [%{message: message}]}
105-
conn
106-
|> put_resp_content_type("application/json")
107-
|> send_resp(400, errors)
108-
end
109-
110-
defp execute(conn, schema, root_value, query, variables, operation_name) do
111-
case GraphQL.execute(schema, query, root_value, variables, operation_name) do
112-
{:ok, data} ->
113-
case Poison.encode(data) do
114-
{:ok, json} -> send_resp(conn, 200, json)
115-
{:error, errors} -> send_resp(conn, 400, errors)
116-
end
117-
{:error, errors} ->
118-
case Poison.encode(errors) do
119-
{:ok, json} -> send_resp(conn, 400, json)
120-
{:error, errors} -> send_resp(conn, 400, errors)
121-
end
122-
end
123-
end
124-
125-
defp evaluate_root_value(conn, {mod, func}) do
126-
apply(mod, func, [conn])
127-
end
128-
129-
defp evaluate_root_value(conn, root_fn) when is_function(root_fn, 1) do
130-
apply(root_fn, [conn])
131-
end
132-
133-
defp evaluate_root_value(_, nil) do
134-
%{}
135-
end
136-
137-
defp evaluate_root_value(_, root_value) do
138-
root_value
139-
end
140-
141-
defp query(conn) do
142-
query = Map.get(conn.params, "query")
143-
if query && String.strip(query) != "", do: query, else: nil
144-
end
145-
146-
defp variables(conn) do
147-
decode_variables Map.get(conn.params, "variables", %{})
148-
end
149-
150-
defp decode_variables(variables) when is_binary(variables) do
151-
case Poison.decode(variables) do
152-
{:ok, variables} -> variables
153-
{:error, _} -> %{} # express-graphql ignores these errors currently
154-
end
155-
end
156-
defp decode_variables(vars), do: vars
157-
158-
defp operation_name(conn) do
159-
Map.get(conn.params, "operationName") ||
160-
Map.get(conn.params, "operation_name")
161-
end
162-
16396
defp use_graphiql?(conn) do
16497
case get_req_header(conn, "accept") do
16598
[accept_header | _] ->

lib/graphql/plug/parameters.ex

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
defmodule GraphQL.Plug.Parameters do
2+
@moduledoc """
3+
This module provides the functions for parsing out parameters
4+
from a `Plug.Conn`
5+
"""
6+
7+
@spec operation_name(Plug.Conn.t) :: String.t
8+
def operation_name(conn) do
9+
Map.get(conn.params, "operationName") ||
10+
Map.get(conn.params, "operation_name")
11+
end
12+
13+
@spec query(Plug.Conn.t) :: String.t | nil
14+
def query(conn) do
15+
query = Map.get(conn.params, "query")
16+
if query && String.strip(query) != "", do: query, else: nil
17+
end
18+
19+
@spec variables(Plug.Conn.t) :: Map
20+
def variables(conn) do
21+
decode_variables Map.get(conn.params, "variables", %{})
22+
end
23+
24+
defp decode_variables(variables) when is_binary(variables) do
25+
case Poison.decode(variables) do
26+
{:ok, variables} -> variables
27+
{:error, _} -> %{} # express-graphql ignores these errors currently
28+
end
29+
end
30+
defp decode_variables(vars), do: vars
31+
32+
end

lib/graphql/plug/root_value.ex

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
defmodule GraphQL.Plug.RootValue do
2+
@moduledoc """
3+
This module provides the functions that are used for
4+
evaluating the root value in the Plug prior to submitting
5+
the query for execution.
6+
"""
7+
@type root_value :: {module, atom} | (Plug.Conn.t -> Map) | Map | nil
8+
@spec evaluate(Plug.Conn.t, root_value) :: Map
9+
10+
def evaluate(conn, {mod, func}) do
11+
apply(mod, func, [conn])
12+
end
13+
14+
def evaluate(conn, root_fn) when is_function(root_fn, 1) do
15+
apply(root_fn, [conn])
16+
end
17+
18+
def evaluate(_, nil) do
19+
%{}
20+
end
21+
22+
def evaluate(_, root_value) do
23+
root_value
24+
end
25+
end

mix.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ defmodule GraphQL.Plug.Mixfile do
2626
defp deps do
2727
[{:earmark, "~> 0.1", only: :dev},
2828
{:ex_doc, "~> 0.11", only: :dev},
29+
{:mix_test_watch, only: :dev},
2930
{:cowboy, "~> 1.0"},
3031
{:plug, "~> 0.14 or ~> 1.0"},
3132
{:poison, "~> 1.5 or ~> 2.0"},

mix.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
"cowlib": {:hex, :cowlib, "1.0.2"},
33
"earmark": {:hex, :earmark, "0.1.19"},
44
"ex_doc": {:hex, :ex_doc, "0.11.0"},
5+
"fs": {:hex, :fs, "0.9.2"},
56
"graphql": {:hex, :graphql, "0.1.2"},
7+
"mix_test_watch": {:hex, :mix_test_watch, "0.2.6"},
68
"plug": {:hex, :plug, "1.0.2"},
79
"poison": {:hex, :poison, "2.1.0"},
810
"ranch": {:hex, :ranch, "1.2.0"}}

0 commit comments

Comments
 (0)