Skip to content
Open
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
5 changes: 4 additions & 1 deletion lib/absinthe.ex
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ defmodule Absinthe do
executing an operation.
* `:max_complexity` -> An integer (or `:infinity`) for the maximum allowed
complexity for the operation being executed.
# `:maximum_number_of_suggestions` -> An integer for the maximum number of suggestions
to be used on error messages. 0 can be passed to disable suggestions and avoid schema instrospection.

## Examples

Expand All @@ -95,7 +97,8 @@ defmodule Absinthe do
analyze_complexity: boolean,
variables: %{optional(String.t()) => any()},
max_complexity: non_neg_integer | :infinity,
pipeline_modifier: pipeline_modifier_fun()
pipeline_modifier: pipeline_modifier_fun(),
maximum_number_of_suggestions: non_neg_integer() | nil
]

@type run_result :: {:ok, result_t} | {:error, String.t()}
Expand Down
55 changes: 30 additions & 25 deletions lib/absinthe/phase/document/validation/arguments_of_correct_type.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,30 @@ defmodule Absinthe.Phase.Document.Validation.ArgumentsOfCorrectType do
Run this validation.
"""
@spec run(Blueprint.t(), Keyword.t()) :: Phase.result_t()
def run(input, _options \\ []) do
result = Blueprint.prewalk(input, &handle_node(&1, input.schema))
def run(input, options \\ []) do
result = Blueprint.prewalk(input, &handle_node(&1, input.schema, options))

{:ok, result}
end

# Check arguments, objects, fields, and lists
@spec handle_node(Blueprint.node_t(), Schema.t()) :: Blueprint.node_t()
@spec handle_node(Blueprint.node_t(), Schema.t(), Absinthe.run_opts()) :: Blueprint.node_t()
# handled by Phase.Document.Validation.KnownArgumentNames
defp handle_node(%Blueprint.Input.Argument{schema_node: nil} = node, _schema) do
defp handle_node(%Blueprint.Input.Argument{schema_node: nil} = node, _schema, _options) do
{:halt, node}
end

# handled by Phase.Document.Validation.ProvidedNonNullArguments
defp handle_node(%Blueprint.Input.Argument{input_value: %{normalized: nil}} = node, _schema) do
defp handle_node(
%Blueprint.Input.Argument{input_value: %{normalized: nil}} = node,
_schema,
_options
) do
{:halt, node}
end

defp handle_node(%Blueprint.Input.Argument{flags: %{invalid: _}} = node, schema) do
descendant_errors = collect_child_errors(node.input_value, schema)
defp handle_node(%Blueprint.Input.Argument{flags: %{invalid: _}} = node, schema, options) do
descendant_errors = collect_child_errors(node.input_value, schema, options)

message =
error_message(
Expand All @@ -46,17 +50,17 @@ defmodule Absinthe.Phase.Document.Validation.ArgumentsOfCorrectType do
{:halt, node}
end

defp handle_node(node, _) do
defp handle_node(node, _, _options) do
node
end

defp collect_child_errors(%Blueprint.Input.List{} = node, schema) do
defp collect_child_errors(%Blueprint.Input.List{} = node, schema, options) do
node.items
|> Enum.map(& &1.normalized)
|> Enum.with_index()
|> Enum.flat_map(fn
{%{schema_node: nil} = child, _} ->
collect_child_errors(child, schema)
collect_child_errors(child, schema, options)

{%{flags: %{invalid: invalid_flag}} = child, idx} ->
child_type_name =
Expand All @@ -73,15 +77,15 @@ defmodule Absinthe.Phase.Document.Validation.ArgumentsOfCorrectType do
child_inspected_value,
custom_error(invalid_flag)
)
| collect_child_errors(child, schema)
| collect_child_errors(child, schema, options)
]

{child, _} ->
collect_child_errors(child, schema)
collect_child_errors(child, schema, options)
end)
end

defp collect_child_errors(%Blueprint.Input.Object{} = node, schema) do
defp collect_child_errors(%Blueprint.Input.Object{} = node, schema, options) do
node.fields
|> Enum.flat_map(fn
%{flags: %{invalid: _}, schema_node: nil} = child ->
Expand All @@ -93,7 +97,7 @@ defmodule Absinthe.Phase.Document.Validation.ArgumentsOfCorrectType do
_ -> suggested_field_names(node.schema_node, child.name)
end

[unknown_field_error_message(child.name, field_suggestions)]
[unknown_field_error_message(child.name, field_suggestions, options)]

%{flags: %{invalid: invalid_flag}} = child ->
child_type_name =
Expand All @@ -104,7 +108,7 @@ defmodule Absinthe.Phase.Document.Validation.ArgumentsOfCorrectType do
case child.schema_node do
%Type.Scalar{} -> []
%Type.Enum{} -> []
_ -> collect_child_errors(child.input_value, schema)
_ -> collect_child_errors(child.input_value, schema, options)
end

child_inspected_value = Blueprint.Input.inspect(child.input_value)
Expand All @@ -120,22 +124,23 @@ defmodule Absinthe.Phase.Document.Validation.ArgumentsOfCorrectType do
]

child ->
collect_child_errors(child.input_value.normalized, schema)
collect_child_errors(child.input_value.normalized, schema, options)
end)
end

defp collect_child_errors(
%Blueprint.Input.Value{normalized: %{flags: %{invalid: {_, reason}}} = norm},
schema
schema,
options
) do
[reason | collect_child_errors(norm, schema)]
[reason | collect_child_errors(norm, schema, options)]
end

defp collect_child_errors(%Blueprint.Input.Value{normalized: norm}, schema) do
collect_child_errors(norm, schema)
defp collect_child_errors(%Blueprint.Input.Value{normalized: norm}, schema, options) do
collect_child_errors(norm, schema, options)
end

defp collect_child_errors(_node, _) do
defp collect_child_errors(_node, _, _options) do
[]
end

Expand Down Expand Up @@ -179,15 +184,15 @@ defmodule Absinthe.Phase.Document.Validation.ArgumentsOfCorrectType do
expected_type_error_message(expected_type_name, inspected_value, custom_error)
end

def unknown_field_error_message(field_name, suggestions \\ [])
def unknown_field_error_message(field_name, suggestions \\ [], options \\ [])

def unknown_field_error_message(field_name, []) do
def unknown_field_error_message(field_name, [], _options) do
~s(In field "#{field_name}": Unknown field.)
end

def unknown_field_error_message(field_name, suggestions) do
def unknown_field_error_message(field_name, suggestions, options) do
~s(In field "#{field_name}": Unknown field.) <>
Utils.MessageSuggestions.suggest_message(suggestions)
Utils.MessageSuggestions.suggest_message(suggestions, options)
end

defp expected_type_error_message(expected_type_name, inspected_value, nil) do
Expand Down
81 changes: 50 additions & 31 deletions lib/absinthe/phase/document/validation/fields_on_correct_type.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ defmodule Absinthe.Phase.Document.Validation.FieldsOnCorrectType do
Run the validation.
"""
@spec run(Blueprint.t(), Keyword.t()) :: Phase.result_t()
def run(input, _options \\ []) do
result = Blueprint.prewalk(input, &handle_node(&1, input))
def run(input, options \\ []) do
result = Blueprint.prewalk(input, &handle_node(&1, input, options))
{:ok, result}
end

@spec handle_node(Blueprint.node_t(), Schema.t()) :: Blueprint.node_t()
defp handle_node(%Blueprint.Document.Operation{schema_node: nil} = node, _) do
@spec handle_node(Blueprint.node_t(), Schema.t(), Absinthe.run_opts()) :: Blueprint.node_t()
defp handle_node(%Blueprint.Document.Operation{schema_node: nil} = node, _, _options) do
error = %Phase.Error{
phase: __MODULE__,
message: "Operation \"#{node.type}\" not supported",
Expand All @@ -31,7 +31,8 @@ defmodule Absinthe.Phase.Document.Validation.FieldsOnCorrectType do

defp handle_node(
%{selections: selections, schema_node: parent_schema_node} = node,
%{schema: schema} = input
%{schema: schema} = input,
options
)
when not is_nil(parent_schema_node) do
possible_parent_types = possible_types(parent_schema_node, schema)
Expand All @@ -41,16 +42,18 @@ defmodule Absinthe.Phase.Document.Validation.FieldsOnCorrectType do
%Blueprint.Document.Field{schema_node: nil} = field ->
type = named_type(parent_schema_node, schema)

field
|> flag_invalid(:unknown_field)
|> put_error(
error =
error(
field,
type.name,
suggested_type_names(field.name, type, input),
suggested_field_names(field.name, type, input)
suggested_field_names(field.name, type, input),
options
)
)

field
|> flag_invalid(:unknown_field)
|> put_error(error)

%Blueprint.Document.Fragment.Spread{errors: []} = spread ->
fragment = Enum.find(input.fragments, &(&1.name == spread.name))
Expand All @@ -59,7 +62,7 @@ defmodule Absinthe.Phase.Document.Validation.FieldsOnCorrectType do
if Enum.any?(possible_child_types, &(&1 in possible_parent_types)) do
spread
else
spread_error(spread, possible_parent_types, possible_child_types, schema)
spread_error(spread, possible_parent_types, possible_child_types, schema, options)
end

%Blueprint.Document.Fragment.Inline{} = fragment ->
Expand All @@ -68,7 +71,7 @@ defmodule Absinthe.Phase.Document.Validation.FieldsOnCorrectType do
if Enum.any?(possible_child_types, &(&1 in possible_parent_types)) do
fragment
else
spread_error(fragment, possible_parent_types, possible_child_types, schema)
spread_error(fragment, possible_parent_types, possible_child_types, schema, options)
end

other ->
Expand All @@ -78,7 +81,7 @@ defmodule Absinthe.Phase.Document.Validation.FieldsOnCorrectType do
%{node | selections: selections}
end

defp handle_node(node, _) do
defp handle_node(node, _, _options) do
node
end

Expand All @@ -88,7 +91,7 @@ defmodule Absinthe.Phase.Document.Validation.FieldsOnCorrectType do
end
end

defp spread_error(spread, parent_types_idents, child_types_idents, schema) do
defp spread_error(spread, parent_types_idents, child_types_idents, schema, _options) do
parent_types = idents_to_names(parent_types_idents, schema)
child_types = idents_to_names(child_types_idents, schema)

Expand Down Expand Up @@ -142,38 +145,54 @@ defmodule Absinthe.Phase.Document.Validation.FieldsOnCorrectType do
end

# Generate the error for a field
@spec error(Blueprint.node_t(), String.t(), [String.t()], [String.t()]) :: Phase.Error.t()
defp error(field_node, parent_type_name, type_suggestions, field_suggestions) do
@spec error(Blueprint.node_t(), String.t(), [String.t()], [String.t()], Absinthe.run_opts()) ::
Phase.Error.t()
defp error(field_node, parent_type_name, type_suggestions, field_suggestions, options) do
message =
error_message(
field_node.name,
parent_type_name,
type_suggestions,
field_suggestions,
options
)

%Phase.Error{
phase: __MODULE__,
message:
error_message(field_node.name, parent_type_name, type_suggestions, field_suggestions),
message: message,
locations: [field_node.source_location]
}
end

@doc """
Generate an error for a field
"""
@spec error_message(String.t(), String.t(), [String.t()], [String.t()]) :: String.t()
def error_message(field_name, type_name, type_suggestions \\ [], field_suggestions \\ [])

def error_message(field_name, type_name, [], []) do
@spec error_message(String.t(), String.t(), [String.t()], [String.t()], Absinthe.run_opts()) ::
String.t()
def error_message(
field_name,
type_name,
type_suggestions \\ [],
field_suggestions \\ [],
options \\ []
)

def error_message(field_name, type_name, [], [], _options) do
~s(Cannot query field "#{field_name}" on type "#{type_name}".)
end

def error_message(field_name, type_name, [], field_suggestions) do
error_message(field_name, type_name) <>
Utils.MessageSuggestions.suggest_message(field_suggestions)
def error_message(field_name, type_name, [], field_suggestions, options) do
error_message(field_name, type_name, [], [], options) <>
Utils.MessageSuggestions.suggest_message(field_suggestions, options)
end

def error_message(field_name, type_name, type_suggestions, []) do
error_message(field_name, type_name) <>
Utils.MessageSuggestions.suggest_fragment_message(type_suggestions)
def error_message(field_name, type_name, type_suggestions, [], options) do
error_message(field_name, type_name, [], [], options) <>
Utils.MessageSuggestions.suggest_fragment_message(type_suggestions, options)
end

def error_message(field_name, type_name, type_suggestions, _) do
error_message(field_name, type_name, type_suggestions)
def error_message(field_name, type_name, type_suggestions, _, options) do
error_message(field_name, type_name, type_suggestions, [], options)
end

defp suggested_type_names(external_field_name, type, blueprint) do
Expand Down Expand Up @@ -214,7 +233,7 @@ defmodule Absinthe.Phase.Document.Validation.FieldsOnCorrectType do

defp find_possible_interfaces(field_name, possible_types, schema) do
possible_types
|> types_to_interface_idents
|> types_to_interface_idents()
|> Enum.uniq()
|> sort_by_implementation_count(possible_types)
|> Enum.map(&Schema.lookup_type(schema, &1))
Expand Down
Loading