arguments_of_correct_type.ex (5147B)
1 defmodule Absinthe.Phase.Document.Validation.ArgumentsOfCorrectType do 2 @moduledoc false 3 4 # Validates document to ensure that all arguments are of the correct type. 5 6 alias Absinthe.{Blueprint, Phase, Phase.Document.Validation.Utils, Schema, Type} 7 8 use Absinthe.Phase 9 10 @doc """ 11 Run this validation. 12 """ 13 @spec run(Blueprint.t(), Keyword.t()) :: Phase.result_t() 14 def run(input, _options \\ []) do 15 result = Blueprint.prewalk(input, &handle_node(&1, input.schema)) 16 17 {:ok, result} 18 end 19 20 # Check arguments, objects, fields, and lists 21 @spec handle_node(Blueprint.node_t(), Schema.t()) :: Blueprint.node_t() 22 # handled by Phase.Document.Validation.KnownArgumentNames 23 defp handle_node(%Blueprint.Input.Argument{schema_node: nil} = node, _schema) do 24 {:halt, node} 25 end 26 27 # handled by Phase.Document.Validation.ProvidedNonNullArguments 28 defp handle_node(%Blueprint.Input.Argument{input_value: %{normalized: nil}} = node, _schema) do 29 {:halt, node} 30 end 31 32 defp handle_node(%Blueprint.Input.Argument{flags: %{invalid: _}} = node, schema) do 33 descendant_errors = collect_child_errors(node.input_value, schema) 34 35 message = 36 error_message( 37 node.name, 38 Blueprint.Input.inspect(node.input_value), 39 descendant_errors 40 ) 41 42 error = error(node, message) 43 44 node = node |> put_error(error) 45 46 {:halt, node} 47 end 48 49 defp handle_node(node, _) do 50 node 51 end 52 53 defp collect_child_errors(%Blueprint.Input.List{} = node, schema) do 54 node.items 55 |> Enum.map(& &1.normalized) 56 |> Enum.with_index() 57 |> Enum.flat_map(fn 58 {%{schema_node: nil} = child, _} -> 59 collect_child_errors(child, schema) 60 61 {%{flags: %{invalid: _}} = child, idx} -> 62 child_type_name = 63 child.schema_node 64 |> Type.value_type(schema) 65 |> Type.name(schema) 66 67 child_inspected_value = Blueprint.Input.inspect(child) 68 69 [ 70 value_error_message(idx, child_type_name, child_inspected_value) 71 | collect_child_errors(child, schema) 72 ] 73 74 {child, _} -> 75 collect_child_errors(child, schema) 76 end) 77 end 78 79 defp collect_child_errors(%Blueprint.Input.Object{} = node, schema) do 80 node.fields 81 |> Enum.flat_map(fn 82 %{flags: %{invalid: _}, schema_node: nil} = child -> 83 field_suggestions = 84 case Type.unwrap(node.schema_node) do 85 %Type.Scalar{} -> [] 86 %Type.Enum{} -> [] 87 nil -> [] 88 _ -> suggested_field_names(node.schema_node, child.name) 89 end 90 91 [unknown_field_error_message(child.name, field_suggestions)] 92 93 %{flags: %{invalid: _}} = child -> 94 child_type_name = 95 Type.value_type(child.schema_node, schema) 96 |> Type.name(schema) 97 98 child_errors = 99 case child.schema_node do 100 %Type.Scalar{} -> [] 101 %Type.Enum{} -> [] 102 _ -> collect_child_errors(child.input_value, schema) 103 end 104 105 child_inspected_value = Blueprint.Input.inspect(child.input_value) 106 107 [ 108 value_error_message(child.name, child_type_name, child_inspected_value) 109 | child_errors 110 ] 111 112 child -> 113 collect_child_errors(child.input_value.normalized, schema) 114 end) 115 end 116 117 defp collect_child_errors(%Blueprint.Input.Value{normalized: norm}, schema) do 118 collect_child_errors(norm, schema) 119 end 120 121 defp collect_child_errors(_node, _) do 122 [] 123 end 124 125 defp suggested_field_names(schema_node, name) do 126 schema_node.fields 127 |> Map.values() 128 |> Enum.map(& &1.name) 129 |> Absinthe.Utils.Suggestion.sort_list(name) 130 end 131 132 # Generate the error for the node 133 @spec error(Blueprint.node_t(), String.t()) :: Phase.Error.t() 134 defp error(node, message) do 135 %Phase.Error{ 136 phase: __MODULE__, 137 message: message, 138 locations: [node.source_location] 139 } 140 end 141 142 def error_message(arg_name, inspected_value, verbose_errors \\ []) 143 144 def error_message(arg_name, inspected_value, []) do 145 ~s(Argument "#{arg_name}" has invalid value #{inspected_value}.) 146 end 147 148 def error_message(arg_name, inspected_value, verbose_errors) do 149 error_message(arg_name, inspected_value) <> "\n" <> Enum.join(verbose_errors, "\n") 150 end 151 152 def value_error_message(id, expected_type_name, inspected_value) when is_integer(id) do 153 ~s(In element ##{id + 1}: ) <> 154 expected_type_error_message(expected_type_name, inspected_value) 155 end 156 157 def value_error_message(id, expected_type_name, inspected_value) do 158 ~s(In field "#{id}": ) <> expected_type_error_message(expected_type_name, inspected_value) 159 end 160 161 def unknown_field_error_message(field_name, suggestions \\ []) 162 163 def unknown_field_error_message(field_name, []) do 164 ~s(In field "#{field_name}": Unknown field.) 165 end 166 167 def unknown_field_error_message(field_name, suggestions) do 168 ~s(In field "#{field_name}": Unknown field.) <> 169 Utils.MessageSuggestions.suggest_message(suggestions) 170 end 171 172 defp expected_type_error_message(expected_type_name, inspected_value) do 173 ~s(Expected type "#{expected_type_name}", found #{inspected_value}.) 174 end 175 end