-
Notifications
You must be signed in to change notification settings - Fork 529
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add unique field names validation (#1135)
* Add unique field names validation Fixes #1133 Also fixes #1049 as the schema's won't compile anymore, so the warning no longer happens * Optimize duplicate?/3 check * Add changelog entry
- Loading branch information
1 parent
1ad9f67
commit b594d7a
Showing
4 changed files
with
149 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 69 additions & 0 deletions
69
lib/absinthe/phase/schema/validation/unique_field_names.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
defmodule Absinthe.Phase.Schema.Validation.UniqueFieldNames do | ||
@moduledoc false | ||
|
||
@behaviour Absinthe.Phase | ||
alias Absinthe.Blueprint | ||
|
||
def run(bp, _) do | ||
bp = | ||
bp | ||
|> Blueprint.prewalk(&handle_schemas(&1, :name)) | ||
|
||
{:ok, bp} | ||
end | ||
|
||
defp handle_schemas(%Blueprint.Schema.SchemaDefinition{} = schema, key) do | ||
schema = Blueprint.prewalk(schema, &validate_types(&1, key)) | ||
{:halt, schema} | ||
end | ||
|
||
defp handle_schemas(obj, _) do | ||
obj | ||
end | ||
|
||
defp validate_types(%type{} = object, key) | ||
when type in [ | ||
Blueprint.Schema.InputObjectTypeDefinition, | ||
Blueprint.Schema.InterfaceTypeDefinition, | ||
Blueprint.Schema.ObjectTypeDefinition | ||
] do | ||
fields = | ||
for field <- object.fields do | ||
name_counts = Enum.frequencies_by(object.fields, &Map.get(&1, key)) | ||
|
||
if duplicate?(name_counts, field, key) do | ||
Absinthe.Phase.put_error(field, error(field, object)) | ||
else | ||
field | ||
end | ||
end | ||
|
||
%{object | fields: fields} | ||
end | ||
|
||
defp validate_types(type, _) do | ||
type | ||
end | ||
|
||
defp duplicate?(name_counts, field, key) do | ||
field_identifier = Map.get(field, key) | ||
Map.get(name_counts, field_identifier, 0) > 1 | ||
end | ||
|
||
defp error(field, object) do | ||
%Absinthe.Phase.Error{ | ||
message: explanation(field, object), | ||
locations: [field.__reference__.location], | ||
phase: __MODULE__, | ||
extra: field | ||
} | ||
end | ||
|
||
def explanation(field, object) do | ||
""" | ||
The field #{inspect(field.name)} is not unique in type #{inspect(object.name)}. | ||
The field must have a unique name within that Object type; no two fields may share the same name. | ||
""" | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
defmodule Absinthe.Schema.Rule.UniqueFieldNamesTest do | ||
use Absinthe.Case, async: true | ||
|
||
@duplicate_object_fields ~S( | ||
defmodule DuplicateObjectFields do | ||
use Absinthe.Schema | ||
query do | ||
end | ||
import_sdl """ | ||
type Dog { | ||
name: String! | ||
name: String | ||
} | ||
""" | ||
end | ||
) | ||
|
||
@duplicate_interface_fields ~S( | ||
defmodule DuplicateInterfaceFields do | ||
use Absinthe.Schema | ||
query do | ||
end | ||
import_sdl """ | ||
interface Animal { | ||
tail: Boolean | ||
tail: Boolean | ||
} | ||
""" | ||
end | ||
) | ||
|
||
@duplicate_input_fields ~S( | ||
defmodule DuplicateInputFields do | ||
use Absinthe.Schema | ||
query do | ||
end | ||
import_sdl """ | ||
input AnimalInput { | ||
species: String! | ||
species: String! | ||
} | ||
""" | ||
end | ||
) | ||
|
||
test "errors on non unique object field names" do | ||
error = ~r/The field \"name\" is not unique in type \"Dog\"./ | ||
|
||
assert_raise(Absinthe.Schema.Error, error, fn -> | ||
Code.eval_string(@duplicate_object_fields) | ||
end) | ||
end | ||
|
||
test "errors on non unique interface field names" do | ||
error = ~r/The field \"tail\" is not unique in type \"Animal\"./ | ||
|
||
assert_raise(Absinthe.Schema.Error, error, fn -> | ||
Code.eval_string(@duplicate_interface_fields) | ||
end) | ||
end | ||
|
||
test "errors on non unique input field names" do | ||
error = ~r/The field \"species\" is not unique in type \"AnimalInput\"./ | ||
|
||
assert_raise(Absinthe.Schema.Error, error, fn -> | ||
Code.eval_string(@duplicate_input_fields) | ||
end) | ||
end | ||
end |