Current section

122 Versions

Jump to

Compare versions

43 files changed
+2849 additions
-3217 deletions
  @@ -24,16 +24,16 @@ Please see the website at [http://absinthe-graphql.org](http://absinthe-graphql.
24 24 - Schema definition
25 25 - All types (eg, Object, Input Object, Enum, Union, Interface, Scalar)
26 26 - Circular type references
27 - - Support for [custom scalars](http://absinthe-graphql.org/guides/custom-scalars)
27 + - Support for [custom scalars](http://absinthe-graphql.org/guides/custom-scalars/)
28 28 - Support for custom directives
29 - - Field, argument, and enum value [deprecation](http://absinthe-graphql.org/guides/deprecation)
29 + - Field, argument, and enum value [deprecation](http://absinthe-graphql.org/guides/deprecation/)
30 30 - Compile-time schema validation
31 - - [Introspection](http://absinthe-graphql.org/guides/introspection), compatible with GraphiQL
31 + - [Introspection](http://absinthe-graphql.org/guides/introspection/), compatible with GraphiQL
32 32 - Query execution
33 33 - General
34 34 - Named fragments, inline fragments, and fragment spreads with type conditions
35 35 - `@skip` and `@include` directives
36 - - [Adapter](http://absinthe-graphql.org/guides/adapters) mechanism to support conversion between camelCase query documents
36 + - [Adapter](http://absinthe-graphql.org/guides/adapters/) mechanism to support conversion between camelCase query documents
37 37 and snake_cased schema definition.
38 38
39 39 ## Installation
  @@ -42,7 +42,7 @@ Install from [Hex.pm](https://hex.pm/packages/absinthe):
42 42
43 43 ```elixir
44 44 def deps do
45 - [{:absinthe, "~> 1.0.0"}]
45 + [{:absinthe, "~> 1.1.0"}]
46 46 end
47 47 ```
48 48
  @@ -63,12 +63,12 @@ See [CHANGELOG](./CHANGELOG.md) for upgrade steps between versions.
63 63 ## Documentation
64 64
65 65 - For the tutorial, guides, and general information about Absinthe-related
66 - projects, see [http://absinthe-graphql.org](http://absithe-graphql.org).
67 - - Links to the API documentation are available in the [project list](https://absinthe-graphql.org/projects).
66 + projects, see [http://absinthe-graphql.org](http://absinthe-graphql.org).
67 + - Links to the API documentation are available in the [project list](http://absinthe-graphql.org/projects/).
68 68
69 69 ### Roadmap
70 70
71 - See the Roadmap on [absinthe-graphql.org](https://absinthe-graphql.org/roadmap).
71 + See the Roadmap on [absinthe-graphql.org](http://absinthe-graphql.org/roadmap/).
72 72
73 73 ## Related Projects
74 74
  @@ -81,7 +81,7 @@ BSD License
81 81 Copyright (c) CargoSense, Inc.
82 82
83 83 Parser derived from GraphQL Elixir, Copyright (c) Josh Price
84 - https://github.com/joshprice/graphql-elixir
84 + https://github.com/graphql-elixir/graphql-elixir
85 85
86 86 Redistribution and use in source and binary forms, with or without modification,
87 87 are permitted provided that the following conditions are met:
  @@ -8,9 +8,11 @@
8 8 <<"lib/absinthe/adapter/passthrough.ex">>,<<"lib/absinthe/execution.ex">>,
9 9 <<"lib/absinthe/execution/arguments.ex">>,
10 10 <<"lib/absinthe/execution/directives.ex">>,
11 - <<"lib/absinthe/execution/field.ex">>,
11 + <<"lib/absinthe/execution/field.ex">>,<<"lib/absinthe/execution/input.ex">>,
12 + <<"lib/absinthe/execution/input/meta.ex">>,
12 13 <<"lib/absinthe/execution/resolution.ex">>,
13 14 <<"lib/absinthe/execution/resolution/atom.ex">>,
15 + <<"lib/absinthe/execution/resolution/enum.ex">>,
14 16 <<"lib/absinthe/execution/resolution/field.ex">>,
15 17 <<"lib/absinthe/execution/resolution/interface.ex">>,
16 18 <<"lib/absinthe/execution/resolution/list.ex">>,
  @@ -21,6 +23,7 @@
21 23 <<"lib/absinthe/execution/resolution/selection_set.ex">>,
22 24 <<"lib/absinthe/execution/resolution/union.ex">>,
23 25 <<"lib/absinthe/execution/runner.ex">>,
26 + <<"lib/absinthe/execution/variable.ex">>,
24 27 <<"lib/absinthe/execution/variables.ex">>,<<"lib/absinthe/fetch.ex">>,
25 28 <<"lib/absinthe/flag.ex">>,<<"lib/absinthe/introspection.ex">>,
26 29 <<"lib/absinthe/introspection/field.ex">>,
  @@ -70,6 +73,7 @@
70 73 <<"lib/absinthe/schema/rule/object_must_implement_interfaces.ex">>,
71 74 <<"lib/absinthe/schema/rule/type_names_are_reserved.ex">>,
72 75 <<"lib/absinthe/schema/rule/type_names_are_unique.ex">>,
76 + <<"lib/absinthe/schema/rule/type_names_are_valid.ex">>,
73 77 <<"lib/absinthe/schema/verification.ex">>,
74 78 <<"lib/absinthe/schema/verification/unions.ex">>,
75 79 <<"lib/absinthe/traversal.ex">>,<<"lib/absinthe/traversal/node.ex">>,
  @@ -86,14 +90,15 @@
86 90 <<"lib/absinthe/type/non_null.ex">>,<<"lib/absinthe/type/object.ex">>,
87 91 <<"lib/absinthe/type/reference.ex">>,<<"lib/absinthe/type/scalar.ex">>,
88 92 <<"lib/absinthe/type/union.ex">>,<<"lib/absinthe/utils.ex">>,
93 + <<"lib/absinthe/validation.ex">>,
94 + <<"lib/absinthe/validation/prevent_circular_fragments.ex">>,
89 95 <<"lib/absinthe/validation/required_input.ex">>,
90 - <<"src/absinthe_lexer.erl">>,<<"src/absinthe_lexer.xrl">>,
91 - <<"src/absinthe_parser.erl">>,<<"src/absinthe_parser.yrl">>,
92 - <<"src/ex_graphql_lexer.erl">>,<<"src/ex_graphql_parser.erl">>,
93 - <<"mix.exs">>,<<"README.md">>]}.
96 + <<"lib/mix/tasks/absinthe.schema.json.ex">>,<<"src/absinthe_lexer.erl">>,
97 + <<"src/absinthe_lexer.xrl">>,<<"src/absinthe_parser.erl">>,
98 + <<"src/absinthe_parser.yrl">>,<<"mix.exs">>,<<"README.md">>]}.
94 99 {<<"licenses">>,[<<"BSD">>]}.
95 100 {<<"links">>,[{<<"github">>,<<"https://github.com/CargoSense/absinthe">>}]}.
96 - {<<"maintainers">>,[<<"Bruce Williams">>]}.
101 + {<<"maintainers">>,[<<"Bruce Williams">>,<<"Ben Wilson">>]}.
97 102 {<<"name">>,<<"absinthe">>}.
98 103 {<<"requirements">>,[]}.
99 - {<<"version">>,<<"1.0.0">>}.
104 + {<<"version">>,<<"1.1.0">>}.
  @@ -120,7 +120,7 @@ defmodule Absinthe do
120 120 }
121 121 }
122 122 \"""
123 - |> Absinthe.run(App.Schema, variables: %{id: params[:item_id]})
123 + |> Absinthe.run(App.Schema, variables: %{"id" => params[:item_id]})
124 124 ```
125 125
126 126 The result, if `params[:item_id]` was `"foo"`, would be the same:
  @@ -169,16 +169,22 @@ defmodule Absinthe do
169 169 parse(%Absinthe.Language.Source{body: input})
170 170 end
171 171 def parse(input) do
172 - case input.body |> tokenize do
173 - {:ok, []} -> {:ok, %Absinthe.Language.Document{}}
174 - {:ok, tokens} ->
175 - case :absinthe_parser.parse(tokens) do
176 - {:ok, _doc} = result ->
177 - result
178 - {:error, raw_error} ->
179 - {:error, format_raw_parse_error(raw_error)}
180 - end
181 - other -> other
172 + try do
173 + case input.body |> tokenize do
174 + {:ok, []} -> {:ok, %Absinthe.Language.Document{}}
175 + {:ok, tokens} ->
176 +
177 + case :absinthe_parser.parse(tokens) do
178 + {:ok, _doc} = result ->
179 + result
180 + {:error, raw_error} ->
181 + {:error, format_raw_parse_error(raw_error)}
182 + end
183 + other -> other
184 + end
185 + rescue
186 + error ->
187 + {:error, format_raw_parse_error(error)}
182 188 end
183 189 end
184 190
  @@ -220,7 +226,7 @@ defmodule Absinthe do
220 226 }
221 227 }
222 228 \"""
223 - |> Absinthe.run(App.Schema, variables: %{id: params[:item_id]})
229 + |> Absinthe.run(App.Schema, variables: %{"id" => params[:item_id]})
224 230 ```
225 231
226 232 See the `Absinthe` module documentation for more examples.
  @@ -229,11 +235,11 @@ defmodule Absinthe do
229 235 def run(doc, schema, options \\ [])
230 236 @spec run(binary | Absinthe.Language.Source.t | Absinthe.Language.Document.t, Absinthe.Schema.t, Keyword.t) :: {:ok, Absinthe.Execution.result_t} | {:error, any}
231 237 def run(%Absinthe.Language.Document{} = document, schema, options) do
232 - case execute(schema, document, options) do
233 - {:ok, result} ->
234 - {:ok, result}
235 - other ->
236 - other
238 + case Absinthe.Validation.run(document) do
239 + {:ok, errors, doc} ->
240 + execute(schema, doc, errors, options)
241 + {:error, errors, _} ->
242 + {:ok, %{errors: errors}}
237 243 end
238 244 end
239 245 def run(input, schema, options) do
  @@ -260,6 +266,16 @@ defmodule Absinthe do
260 266 message = "#{problem}: #{field}"
261 267 %{message: message, locations: [%{line: line, column: 0}]}
262 268 end
269 + @unknown_error_msg "An unknown error occurred during parsing"
270 + @spec format_raw_parse_error(map) :: Execution.error_t
271 + defp format_raw_parse_error(%{} = error) do
272 + detail = if Exception.exception?(error) do
273 + ": " <> Exception.message(error)
274 + else
275 + ""
276 + end
277 + %{message: @unknown_error_msg <> detail}
278 + end
263 279
264 280 @doc """
265 281 Evaluates a query document against a schema, without options.
  @@ -280,9 +296,9 @@ defmodule Absinthe do
280 296 # EXECUTION
281 297 #
282 298
283 - @spec execute(Absinthe.Schema.t, Absinthe.Language.Document.t, Keyword.t) :: Absinthe.Execution.result_t
284 - defp execute(schema, document, options) do
285 - %Absinthe.Execution{schema: schema, document: document}
299 + @spec execute(Absinthe.Schema.t, Absinthe.Language.Document.t, [], Keyword.t) :: Absinthe.Execution.result_t
300 + defp execute(schema, document, errors, options) do
301 + %Absinthe.Execution{schema: schema, document: document, errors: errors}
286 302 |> Absinthe.Execution.run(options)
287 303 end
  @@ -62,21 +62,83 @@ defmodule Absinthe.Adapter do
62 62 alias Absinthe.Execution
63 63 alias Absinthe.Language
64 64
65 + def load_variables(variables) do
66 + variables
67 + |> map_var
68 + end
69 +
70 + defp do_load_variables(item) when is_map(item) do
71 + item |> map_var
72 + end
73 + defp do_load_variables(item) when is_list(item) do
74 + item |> list_var
75 + end
76 + defp do_load_variables(item) do
77 + item
78 + end
79 +
80 + defp list_var(items) do
81 + items |> list_var([])
82 + end
83 + defp list_var([], acc), do: :lists.reverse(acc)
84 + defp list_var([item | rest], acc) do
85 + list_var(rest, [do_load_variables(item) | acc])
86 + end
87 +
88 + defp map_var(items) do
89 + items
90 + |> Map.to_list
91 + |> map_var([])
92 + end
93 + defp map_var([], acc), do: :maps.from_list(acc)
94 + defp map_var([{k, v} | rest], acc) do
95 + item = {to_internal_name(k, :key_name), do_load_variables(v)}
96 + map_var(rest, [item | acc])
97 + end
98 +
99 + defp load_variable_definitions(%{variable: variable} = node) do
100 + %{ node |
101 + variable: Map.update!(variable, :name, &to_internal_name(&1, :variable))
102 + }
103 + end
104 +
65 105 def load_document(%{definitions: definitions} = node) do
66 106 %{node |
67 - definitions: definitions |> Enum.map(&load_ast_node/1)}
107 + definitions: definitions |> Enum.map(&load_ast_node/1),
108 + }
68 109 end
69 110
70 111 # Rename a AST node and traverse children
112 + defp load_ast_node(%Language.Directive{} = node) do
113 + %{node |
114 + arguments: Enum.map(node.arguments, &load_ast_node/1)
115 + }
116 + end
117 + defp load_ast_node(%Language.Variable{} = node) do
118 + %{node | name: to_internal_name(node.name, :variable)}
119 + end
71 120 defp load_ast_node(%Language.OperationDefinition{} = node) do
72 121 %{node |
73 122 name: node.name |> to_internal_name(:operation),
74 - selection_set: load_ast_node(node.selection_set)}
123 + selection_set: load_ast_node(node.selection_set),
124 + variable_definitions: node.variable_definitions |> Enum.map(&load_variable_definitions/1)
125 + }
75 126 end
76 127 defp load_ast_node(%Language.Fragment{} = node) do
77 128 %{node |
129 + directives: node.directives |> Enum.map(&load_ast_node/1),
130 + selection_set: load_ast_node(node.selection_set),
131 + }
132 + end
133 + defp load_ast_node(%Language.InlineFragment{} = node) do
134 + %{node |
135 + directives: node.directives |> Enum.map(&load_ast_node/1),
78 136 selection_set: load_ast_node(node.selection_set)}
79 137 end
138 + defp load_ast_node(%Language.FragmentSpread{} = node) do
139 + %{node |
140 + directives: node.directives |> Enum.map(&load_ast_node/1)}
141 + end
80 142 defp load_ast_node(%Language.SelectionSet{} = node) do
81 143 %{node |
82 144 selections: node.selections |> Enum.map(&load_ast_node/1)}
  @@ -85,7 +147,9 @@ defmodule Absinthe.Adapter do
85 147 %{node |
86 148 name: node.name |> to_internal_name(:field),
87 149 selection_set: load_ast_node(node.selection_set),
88 - arguments: node.arguments |> Enum.map(&load_ast_node/1)}
150 + arguments: node.arguments |> Enum.map(&load_ast_node/1),
151 + directives: node.directives |> Enum.map(&load_ast_node/1),
152 + }
89 153 end
90 154 defp load_ast_node(%Language.ObjectValue{} = node) do
91 155 %{node |
  @@ -101,6 +165,9 @@ defmodule Absinthe.Adapter do
101 165 name: node.name |> to_internal_name(:argument),
102 166 value: load_ast_node(node.value)}
103 167 end
168 + defp load_ast_node(%Language.ListValue{} = node) do
169 + %{node | values: Enum.map(node.values, &load_ast_node/1)}
170 + end
104 171 defp load_ast_node(other) do
105 172 other
106 173 end
  @@ -151,10 +218,13 @@ defmodule Absinthe.Adapter do
151 218 |> format_error(locations)
152 219 end
153 220
154 - def format_error(%{value: value} = error_info) do
221 + def format_error(%{value: value} = error_info) when not is_function(value) do
155 222 %{error_info | value: inspect(value)}
156 223 |> format_error([])
157 224 end
225 + def format_error(%{value: value} = error_info) do
226 + format_error(error_info, [])
227 + end
158 228
159 229 defoverridable [load_document: 1,
160 230 dump_results: 1,
  @@ -7,4 +7,8 @@ defmodule Absinthe.Adapter.Passthrough do
7 7
8 8 use Absinthe.Adapter
9 9
10 + def load_document(doc), do: doc
11 +
12 + def dump_results(results), do: results
13 +
10 14 end
Loading more files…