Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support open ended scalars #1069

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Dev

- Feature: Always inline functions when using persistent_term backend.
- Feature: [Support optional open ended scalars](https://github.com/absinthe-graphql/absinthe/pull/1069)

## 1.6.4

Expand Down Expand Up @@ -72,11 +73,12 @@
## v1.5.0 (Rc)

- Breaking Bug Fix: Variable types must align exactly with the argument type. Previously
Absinthe allowed variables of different types to be used by accident as long as the data parsed.
Absinthe allowed variables of different types to be used by accident as long as the data parsed.
- Feature (Experimental): `:persistent_term` based schema backend
- Breaking Change: `telemetry` event keys [changed](https://github.com/absinthe-graphql/absinthe/pull/901) since the beta release.

## v1.5.0 (Beta)

- Feature: SDL directives, other improvements
- Feature: Output rendered SDL for a schema
- Feature: Substantially lower subscription memory usage.
Expand Down
4 changes: 3 additions & 1 deletion lib/absinthe/blueprint/schema/scalar_type_definition.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ defmodule Absinthe.Blueprint.Schema.ScalarTypeDefinition do
serialize: nil,
directives: [],
source_location: nil,
open_ended: false,
# Added by phases
flags: %{},
errors: [],
Expand All @@ -37,7 +38,8 @@ defmodule Absinthe.Blueprint.Schema.ScalarTypeDefinition do
description: type_def.description,
definition: type_def.module,
serialize: type_def.serialize,
parse: type_def.parse
parse: type_def.parse,
open_ended: type_def.open_ended
}
end

Expand Down
7 changes: 6 additions & 1 deletion lib/absinthe/phase/document/arguments/data.ex
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,12 @@ defmodule Absinthe.Phase.Document.Arguments.Data do
def handle_node(%Input.Value{normalized: %Input.Object{fields: fields}} = node) do
data =
for field <- fields, include_field?(field), into: %{} do
{field.schema_node.identifier, field.input_value.data}
# Scalar child nodes will not have a schema_node
if field.schema_node != nil do
{field.schema_node.identifier, field.input_value.data}
else
{field.name, field.input_value.data}
end
end

%{node | data: data}
Expand Down
6 changes: 5 additions & 1 deletion lib/absinthe/phase/document/arguments/flag_invalid.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule Absinthe.Phase.Document.Arguments.FlagInvalid do
#
# This is later used by the ArgumentsOfCorrectType phase.

alias Absinthe.{Blueprint, Phase}
alias Absinthe.{Blueprint, Phase, Type}

use Absinthe.Phase

Expand Down Expand Up @@ -34,6 +34,10 @@ defmodule Absinthe.Phase.Document.Arguments.FlagInvalid do
check_children(node, node.items |> Enum.map(& &1.normalized), :bad_list)
end

defp handle_node(%Blueprint.Input.Object{schema_node: %Type.Scalar{open_ended: true}} = node) do
node
end

defp handle_node(%Blueprint.Input.Object{} = node) do
check_children(node, node.fields, :bad_object)
end
Expand Down
22 changes: 18 additions & 4 deletions lib/absinthe/phase/document/arguments/parse.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ defmodule Absinthe.Phase.Document.Arguments.Parse do
{:ok, result}
end

defp handle_node(%{schema_node: nil} = node, _context) do
{:halt, node}
end

defp handle_node(%{normalized: nil} = node, _context) do
node
end
Expand Down Expand Up @@ -50,10 +46,28 @@ defmodule Absinthe.Phase.Document.Arguments.Parse do
end
end

defp build_value(
%Input.Object{} = normalized,
%Type.Scalar{open_ended: true} = schema_node,
context
) do
case Type.Scalar.parse(schema_node, normalized, context) do
:error ->
{:error, :bad_parse}

{:ok, val} ->
{:ok, val}
end
end

defp build_value(_normalized, %Type.Scalar{}, _context) do
kdawgwilk marked this conversation as resolved.
Show resolved Hide resolved
{:error, :bad_parse}
end

defp build_value(%{value: value} = _normalized, nil = _schema_node, _context) do
{:ok, value}
kdawgwilk marked this conversation as resolved.
Show resolved Hide resolved
end

defp build_value(%Input.Null{}, %Type.Enum{}, _) do
{:ok, nil}
end
Expand Down
3 changes: 2 additions & 1 deletion lib/absinthe/type/scalar.ex
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ defmodule Absinthe.Type.Scalar do
definition: nil,
__reference__: nil,
parse: nil,
serialize: nil
serialize: nil,
open_ended: false

@typedoc "The internal, canonical representation of a scalar value"
@type value_t :: any
Expand Down
52 changes: 52 additions & 0 deletions test/absinthe/execution/arguments_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,58 @@ defmodule Absinthe.Execution.ArgumentsTest do
)
end

describe "open ended scalar" do
@graphql """
query {
entities(representations: [{__typename: "Product", id: "123"}])
}
"""
test "supports passing an object directly" do
assert_data(
%{"entities" => [%{"__typename" => "Product", "id" => "123"}]},
run(@graphql, @schema)
)
end

@graphql """
query($representations: [Any!]!) {
entities(representations: $representations)
}
"""
test "supports passing an object through variables" do
assert_data(
%{"entities" => [%{"__typename" => "Product", "id" => "123"}]},
run(@graphql, @schema,
variables: %{"representations" => [%{"__typename" => "Product", "id" => "123"}]}
)
)
end

@graphql """
query {
entities(representations: [{__typename: "Product", id: null}])
}
"""
test "supports passing an object with a nested value of null" do
assert_data(
%{"entities" => [%{"__typename" => "Product", "id" => nil}]},
run(@graphql, @schema)
)
end

@graphql """
query {
entities(representations: [{__typename: "Product", contact_type: PHONE}])
}
"""
test "supports passing an object with a nested value of ENUM" do
assert_data(
%{"entities" => [%{"__typename" => "Product", "contact_type" => "PHONE"}]},
run(@graphql, @schema)
)
end
end
kdawgwilk marked this conversation as resolved.
Show resolved Hide resolved

describe "errors" do
@graphql """
query FindUser {
Expand Down
13 changes: 13 additions & 0 deletions test/support/fixtures/arguments_schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ defmodule Absinthe.Fixtures.ArgumentsSchema do
false: "NO"
}

scalar :any, open_ended: true do
parse fn value -> {:ok, value} end
kdawgwilk marked this conversation as resolved.
Show resolved Hide resolved
serialize fn value -> value end
end

scalar :input_name do
parse fn %{value: value} -> {:ok, %{first_name: value}} end
serialize fn %{first_name: name} -> name end
Expand Down Expand Up @@ -62,6 +67,14 @@ defmodule Absinthe.Fixtures.ArgumentsSchema do
end

query do
field :entities, list_of(:any) do
kdawgwilk marked this conversation as resolved.
Show resolved Hide resolved
arg :representations, non_null(list_of(non_null(:any)))

resolve fn %{representations: representations}, _ ->
{:ok, representations}
end
end

field :stuff, :integer do
arg :stuff, non_null(:input_stuff)

Expand Down