Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
tlvenn committed Jul 14, 2017
2 parents a7f8da0 + 2934988 commit 5be8214
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 38 deletions.
9 changes: 6 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
# CHANGELOG

## 1.3.0
### Status: Beta

- Enhancement: Added `Absinthe.Relay.Node.ParseIDs`. Use it instead of
- Breaking Change: The functions in the `Connection` module that produce connections
now return `{:ok, connection}` or `{:error, reason}` as they do internal error handling
of connection related arguments

- Enhancement: Added `Absinthe.Relay.Node.ParseIDs` middleware. Use it instead of
`Absinthe.Relay.Helpers.parsing_node_ids/2`, which will be removed in a future
release.
- Enhancement: Allow multiple possible node types when parsing node IDs.
(Thanks, @avitex.)
- Bug Fix: Handle errors when parsing multiple arguments for node IDs more
gracefully. (Thanks to @avitex and @dpehrson.)
gracefully. (Thanks to @avitex and @dpehrson.)
7 changes: 2 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@ Support for the [Relay framework](https://facebook.github.io/relay/)
from Elixir, using the [Absinthe](https://github.com/absinthe-graphql/absinthe)
package.

Please note that this is the initial release of this package and that
significant API changes are expected before v1.0.

## Installation

Install from [Hex.pm](https://hex.pm/packages/absinthe_relay):

```elixir
def deps do
[{:absinthe_relay, "~> 1.2.0"}]
[{:absinthe_relay, "~> 1.3.0"}]
end
```

Expand All @@ -25,7 +22,7 @@ def application do
end
```

Note: Absinthe requires Elixir 1.3 or higher.
Note: Absinthe requires Elixir 1.4 or higher.

## Upgrading

Expand Down
8 changes: 3 additions & 5 deletions lib/absinthe/relay/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,10 @@ defmodule Absinthe.Relay.Connection do
connection field :pets, node_type: :pet do
resolve fn
pagination_args, %{source: person} ->
connection = Absinthe.Relay.Connection.from_list(
Absinthe.Relay.Connection.from_list(
Enum.map(person.pet_ids, &pet_from_id(&1)),
pagination_args
)
{:ok, connection}
end
end
end
Expand Down Expand Up @@ -284,14 +283,13 @@ defmodule Absinthe.Relay.Connection do
@spec from_slice(data :: list, offset :: offset) :: {:ok, t}
@spec from_slice(data :: list, offset :: offset, opts :: from_slice_opts) :: {:ok, t}
def from_slice(items, offset, opts \\ []) do
opts = Map.new(opts)
{edges, first, last} = build_cursors(items, offset)

page_info = %{
start_cursor: first,
end_cursor: last,
has_previous_page: Map.get(opts, :has_previous_page, false),
has_next_page: Map.get(opts, :has_next_page, false),
has_previous_page: Keyword.get(opts, :has_previous_page, false),
has_next_page: Keyword.get(opts, :has_next_page, false),
}
{:ok, %{edges: edges, page_info: page_info}}
end
Expand Down
2 changes: 1 addition & 1 deletion lib/absinthe/relay/node.ex
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ defmodule Absinthe.Relay.Node do
end
end
defp do_from_global_id(_, decoded, _schema) do
{:error, "Could not extract value from decoded ID `#{decoded}'"}
{:error, "Could not extract value from decoded ID `#{inspect decoded}'"}
end

@doc """
Expand Down
139 changes: 120 additions & 19 deletions lib/absinthe/relay/node/parse_ids.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,40 @@ defmodule Absinthe.Relay.Node.ParseIDs do
end
```
Parse a nested structure of node (global) IDs. This behaves similarly to the
examples above, but acts recursively when given a keyword list.
```
input_object :parent_input do
field :id, non_null(:id)
field :children, list_of(:child_input)
field :child, non_null(:child_input)
end
input_object :child_input do
field :id, non_null(:id)
end
mutation do
payload field :update_parent do
input do
field :parent, :parent_input
end
output do
field :parent, :parent
end
middleware Absinthe.Relay.Node.ParseIDs, parent: [
id: :parent,
children: [id: :child],
child: [id: :child]
]
resolve &resolve_parent/2
end
end
```
As with any piece of middleware, this can configured schema-wide using the
`middleware/3` function in your schema. In this example all top level
query fields are made to support node IDs with the associated criteria in
Expand Down Expand Up @@ -113,18 +147,37 @@ defmodule Absinthe.Relay.Node.ParseIDs do
@doc false
@spec parse(map, rules, Absinthe.Resolution.t) :: {:ok, map} | {:error, [String.t]}
def parse(args, rules, resolution) do
matching_rules(rules, args)
|> Enum.reduce({%{}, []}, fn {key, expected_type}, {node_id_args, errors} ->
with global_id <- Map.get(args, key),
{:ok, node_id} <- Node.from_global_id(global_id, resolution.schema),
argument_name <- find_argument_name(key, resolution),
{:ok, node_id} <- check_node_id(node_id, expected_type, argument_name) do
{Map.put(node_id_args, key, node_id), errors}
else
{:error, msg} ->
{node_id_args, [msg | errors]}
end
case args do
list when is_list(list) ->
parse_list(list, rules, resolution)
_ ->
parse_args(args, rules, resolution)
end
end

@spec parse(list, rules, Absinthe.Resolution.t) :: {:ok, map} | {:error, [String.t]}
defp parse_list(list, rules, resolution) do
Enum.reduce(list, {:ok, []}, fn(map, result) ->
parse_list_item(result, map, rules, resolution)
end)
end

defp parse_list_item({:error, errors} = initial_result, map, rules, resolution) do
case parse(map, rules, resolution) do
{:error, error} -> {:error, [error | errors]}
_ -> initial_result
end
end
defp parse_list_item({:ok, maps}, map, rules, resolution) do
case parse(map, rules, resolution) do
{:ok, new_map} -> {:ok, [new_map | maps]}
{:error, error} -> {:error, [error]}
end
end

@spec parse(map, rules, Absinthe.Resolution.t) :: {:ok, map} | {:error, [String.t]}
defp parse_args(args, rules, resolution) do
parse_all_rules(args, rules, resolution)
|> case do
{node_id_args, []} ->
{:ok, Map.merge(args, node_id_args)}
Expand All @@ -133,11 +186,59 @@ defmodule Absinthe.Relay.Node.ParseIDs do
end
end

@spec matching_rules(rules, map) :: rules
defp matching_rules(rules, args) do
rules
|> Enum.filter(fn {key, _} -> Map.has_key?(args, key) end)
|> Map.new
defp parse_all_rules(args, rules, resolution) do
Enum.reduce(rules, {%{}, []}, fn (rule, result) ->
parse_rule(args, resolution, rule, result)
end)
end

defp parse_rule(args, resolution, {key, _} = rule, {node_id_args, errors} = result) do
argument_name = find_argument_name(key, resolution)

with {:ok, global_id} <- get_global_id(args, key),
{:ok, expected_type} <- get_expected_type(rule),
{:ok, node_id} <- Node.from_global_id(global_id, resolution.schema),
{:ok, node_id} <- check_node_id(node_id, expected_type, argument_name) do
{Map.put(node_id_args, key, node_id), errors}
else
{:error, error} ->
error = ~s<In argument "#{argument_name}": #{error}.>
{node_id_args, [error | errors]}
{:check_node_error, error} ->
{node_id_args, [error | errors]}
{:missing_key, _} ->
result
{:nested_rule, nested_rule} ->
args
|> Map.get(key)
|> parse_nested_rule(resolution, nested_rule, result)
end
end

defp parse_nested_rule(args, resolution, {key, rules}, {node_id_args, errors}) do
case parse(args, rules, resolution) do
{:ok, parsed_args} ->
{Map.put(node_id_args, key, parsed_args), errors}
{:error, nested_errors} ->
{node_id_args, nested_errors ++ errors}
end
end

defp get_expected_type({_, expected_type_or_nested_rule} = rule) do
if Keyword.keyword?(expected_type_or_nested_rule) do
{:nested_rule, rule}
else
{:ok, expected_type_or_nested_rule}
end
end

defp get_global_id(args, key) do
case Map.get(args, key) do
nil ->
{:missing_key, key}
global_id ->
{:ok, global_id}
end
end

@spec find_argument_name(atom, Absinthe.Resolution.t) :: nil | String.t
Expand All @@ -158,14 +259,14 @@ defmodule Absinthe.Relay.Node.ParseIDs do
true ->
{:ok, id_map}
false ->
{:error, ~s<In argument "#{argument_name}": Expected node type in #{inspect(expected_types)}, found #{inspect(type)}.>}
{:check_node_error, ~s<In argument "#{argument_name}": Expected node type in #{inspect(expected_types)}, found #{inspect(type)}.>}
end
end
defp check_node_id(%{type: expected_type, id: id}, expected_type, _) do
{:ok, id}
end
defp check_node_id(%{type: type}, expected_type, argument_name) do
{:error, ~s<In argument "#{argument_name}": Expected node type #{inspect(expected_type)}, found #{inspect(type)}.>}
{:check_node_error, ~s<In argument "#{argument_name}": Expected node type #{inspect(expected_type)}, found #{inspect(type)}.>}
end

end
end
4 changes: 2 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule AbsintheRelay.Mixfile do
use Mix.Project

@version "1.3.0-rc.0"
@version "1.3.0"

def project do
[app: :absinthe_relay,
Expand Down Expand Up @@ -33,7 +33,7 @@ defmodule AbsintheRelay.Mixfile do

defp deps do
[
{:absinthe, "~> 1.3.0-rc.0"},
{:absinthe, "~> 1.3.0"},
{:ecto, "~> 1.0 or ~> 2.0", optional: true},
{:poison, ">= 0.0.0", only: [:dev, :test]},
{:ex_doc, "~> 0.11.0", only: :dev},
Expand Down
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
%{"absinthe": {:hex, :absinthe, "1.3.0-rc.0", "99c4557fd10fc1e7d8476cc49444974d9a7e13d02bbe6a7de434513b9df55fcb", [:mix], []},
%{"absinthe": {:hex, :absinthe, "1.3.0", "0b58aec87c115025c6abbbdaebdd2b5d545d5c47a342e5a8c790d5989d27b24c", [:mix], []},
"decimal": {:hex, :decimal, "1.3.1", "157b3cedb2bfcb5359372a7766dd7a41091ad34578296e951f58a946fcab49c6", [:mix], []},
"earmark": {:hex, :earmark, "0.2.1", "ba6d26ceb16106d069b289df66751734802777a3cbb6787026dd800ffeb850f3", [:mix], []},
"ecto": {:hex, :ecto, "2.1.4", "d1ba932813ec0e0d9db481ef2c17777f1cefb11fc90fa7c142ff354972dfba7e", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, optional: true]}]},
Expand Down
Loading

0 comments on commit 5be8214

Please sign in to comment.