known_argument_names.ex (3511B)
1 defmodule Absinthe.Phase.Document.Validation.KnownArgumentNames do 2 @moduledoc false 3 4 # Validates document to ensure that all arguments are in the schema. 5 # 6 # Note: while graphql-js doesn't add errors to unknown arguments that 7 # are provided to unknown fields, Absinthe does -- but when the errors 8 # are harvested from the Blueprint tree, only the first layer of unknown 9 # errors (eg, the field) should be presented to the user. 10 11 alias Absinthe.{Blueprint, Phase, Schema, Type} 12 13 use Absinthe.Phase 14 15 @doc """ 16 Run the validation. 17 """ 18 @spec run(Blueprint.t(), Keyword.t()) :: Phase.result_t() 19 def run(input, _options \\ []) do 20 result = Blueprint.prewalk(input, &handle_node(&1, input.schema)) 21 {:ok, result} 22 end 23 24 @spec handle_node(Blueprint.node_t(), Schema.t()) :: Blueprint.node_t() 25 defp handle_node(%{schema_node: nil} = node, _schema) do 26 node 27 end 28 29 defp handle_node(%{selections: _, schema_node: schema_node} = node, schema) do 30 selections = 31 Enum.map(node.selections, fn 32 %{arguments: arguments} = field -> 33 arguments = 34 Enum.map(arguments, fn 35 %{schema_node: nil} = arg -> 36 arg 37 |> flag_invalid(:no_schema_node) 38 |> put_error(field_error(arg, field, type_name(schema_node, schema))) 39 40 other -> 41 other 42 end) 43 44 %{field | arguments: arguments} 45 46 other -> 47 other 48 end) 49 50 %{node | selections: selections} 51 end 52 53 defp handle_node(%Blueprint.Directive{} = node, _) do 54 arguments = 55 Enum.map(node.arguments, fn 56 %{schema_node: nil} = arg -> 57 arg 58 |> flag_invalid(:no_schema_node) 59 |> put_error(directive_error(arg, node)) 60 61 other -> 62 other 63 end) 64 65 %{node | arguments: arguments} 66 end 67 68 defp handle_node(node, _) do 69 node 70 end 71 72 @spec type_name(Type.t(), Schema.t()) :: String.t() 73 defp type_name(%Type.Field{} = node, schema) do 74 node.type 75 |> Type.unwrap() 76 |> schema.__absinthe_lookup__() 77 |> Map.fetch!(:name) 78 end 79 80 defp type_name(node, _) do 81 node.name 82 end 83 84 # Generate the error for a directive argument 85 @spec directive_error(Blueprint.node_t(), Blueprint.node_t()) :: Phase.Error.t() 86 defp directive_error(argument_node, directive_node) do 87 %Phase.Error{ 88 phase: __MODULE__, 89 message: directive_error_message(argument_node.name, directive_node.name), 90 locations: [argument_node.source_location] 91 } 92 end 93 94 # Generate the error for a field argument 95 @spec field_error(Blueprint.node_t(), Blueprint.node_t(), String.t()) :: Phase.Error.t() 96 defp field_error(argument_node, field_node, type_name) do 97 %Phase.Error{ 98 phase: __MODULE__, 99 message: field_error_message(argument_node.name, field_node.name, type_name), 100 locations: [argument_node.source_location] 101 } 102 end 103 104 @doc """ 105 Generate an error for a directive argument 106 """ 107 @spec directive_error_message(String.t(), String.t()) :: String.t() 108 def directive_error_message(argument_name, directive_name) do 109 ~s(Unknown argument "#{argument_name}" on directive "@#{directive_name}".) 110 end 111 112 @doc """ 113 Generate an error for a field argument 114 """ 115 @spec field_error_message(String.t(), String.t(), String.t()) :: String.t() 116 def field_error_message(argument_name, field_name, type_name) do 117 ~s(Unknown argument "#{argument_name}" on field "#{field_name}" of type "#{type_name}".) 118 end 119 end