scalar_leafs.ex (2877B)
1 # [sic] 2 defmodule Absinthe.Phase.Document.Validation.ScalarLeafs do 3 @moduledoc false 4 5 # Validates that all leaf nodes are scalars. 6 # 7 # # Examples: 8 # Assume `user` field is an object, and `email` is a scalar. 9 # 10 # ## DO NOT 11 # ``` 12 # { 13 # user 14 # } 15 # ``` 16 # 17 # ## DO 18 # ``` 19 # { 20 # user {name email} 21 # } 22 # ``` 23 # 24 # ## DO NOT 25 # ``` 26 # { 27 # email { fields on scalar } 28 # } 29 # ``` 30 # 31 # ## DO 32 # ``` 33 # { 34 # email 35 # } 36 # ``` 37 38 alias Absinthe.{Blueprint, Phase, Type} 39 40 use Absinthe.Phase 41 use Absinthe.Phase.Validation 42 43 @doc """ 44 Run the validation. 45 """ 46 @spec run(Blueprint.t(), Keyword.t()) :: Phase.result_t() 47 def run(input, _options \\ []) do 48 result = Blueprint.prewalk(input, &handle_node(&1, input.schema)) 49 {:ok, result} 50 end 51 52 defp handle_node(%{schema_node: nil} = node, _schema), do: {:halt, node} 53 54 defp handle_node(%Blueprint.Document.Field{schema_node: schema_node} = node, schema) do 55 type = Type.expand(schema_node.type, schema) 56 process(node, Type.unwrap(type), type) 57 end 58 59 defp handle_node(node, _) do 60 node 61 end 62 63 @has_subfields [ 64 Type.Object, 65 Type.Union, 66 Type.Interface 67 ] 68 69 defp process(%{selections: []} = node, %unwrapped{}, type) when unwrapped in @has_subfields do 70 bad_node(node, type, :missing_subfields) 71 end 72 73 defp process(%{selections: s} = node, %unwrapped{}, type) 74 when s != [] and unwrapped not in @has_subfields do 75 bad_node(node, type, :bad_subfields) 76 end 77 78 defp process(node, _, _) do 79 node 80 end 81 82 defp bad_node(node, type, :bad_subfields = flag) do 83 node 84 |> flag_invalid(flag) 85 |> put_error(error(node, no_subselection_allowed_message(node.name, Type.name(type)))) 86 end 87 88 defp bad_node(node, type, :missing_subfields = flag) do 89 node 90 |> flag_invalid(flag) 91 |> put_error(error(node, required_subselection_message(node.name, Type.name(type)))) 92 end 93 94 # Generate the error 95 @spec error(Blueprint.Document.Field.t(), String.t()) :: Phase.Error.t() 96 defp error(node, message) do 97 %Phase.Error{ 98 phase: __MODULE__, 99 message: message, 100 locations: [node.source_location] 101 } 102 end 103 104 @doc """ 105 Generate the error message for an extraneous field subselection. 106 """ 107 @spec no_subselection_allowed_message(String.t(), String.t()) :: String.t() 108 def no_subselection_allowed_message(field_name, type_name) do 109 ~s(Field "#{field_name}" must not have a selection since type "#{type_name}" has no subfields.) 110 end 111 112 @doc """ 113 Generate the error message for a missing field subselection. 114 """ 115 @spec required_subselection_message(String.t(), String.t()) :: String.t() 116 def required_subselection_message(field_name, type_name) do 117 ~s(Field "#{field_name}" of type "#{type_name}" must have a selection of subfields. Did you mean "#{ 118 field_name 119 } { ... }"?) 120 end 121 end