input_output_types_correctly_placed.ex (3830B)
1 defmodule Absinthe.Phase.Schema.Validation.InputOutputTypesCorrectlyPlaced do 2 use Absinthe.Phase 3 alias Absinthe.Blueprint 4 5 def run(bp, _) do 6 bp = Blueprint.prewalk(bp, &handle_schemas/1) 7 {:ok, bp} 8 end 9 10 defp handle_schemas(%Blueprint.Schema.SchemaDefinition{} = schema) do 11 types = Map.new(schema.type_definitions, &{&1.identifier, &1}) 12 schema = Blueprint.prewalk(schema, &validate_type(&1, types, schema)) 13 {:halt, schema} 14 end 15 16 defp handle_schemas(obj) do 17 obj 18 end 19 20 defp validate_type(%Blueprint.Schema.InputValueDefinition{} = arg, types, schema) do 21 type = 22 Blueprint.TypeReference.unwrap(arg.type) 23 |> Blueprint.TypeReference.to_type(schema) 24 25 arg_type = Map.get(types, type) 26 27 if arg_type && wrong_type?(Blueprint.Schema.InputValueDefinition, arg_type) do 28 detail = %{ 29 argument: arg.identifier, 30 type: arg_type.identifier, 31 struct: arg_type.__struct__ 32 } 33 34 arg |> put_error(error(arg.__reference__.location, detail)) 35 else 36 arg 37 end 38 end 39 40 defp validate_type(%struct{fields: fields} = type, types, schema) do 41 fields = 42 Enum.map(fields, fn 43 %{type: _} = field -> 44 type = 45 Blueprint.TypeReference.unwrap(field.type) 46 |> Blueprint.TypeReference.to_type(schema) 47 48 field_type = Map.get(types, type) 49 50 if field_type && wrong_type?(struct, field_type) do 51 detail = %{ 52 field: field.identifier, 53 type: field_type.identifier, 54 struct: field_type.__struct__, 55 parent: struct 56 } 57 58 field |> put_error(error(field.__reference__.location, detail)) 59 else 60 field 61 end 62 63 field -> 64 field 65 end) 66 67 %{type | fields: fields} 68 end 69 70 defp validate_type(type, _types, _schema) do 71 type 72 end 73 74 @output_types [ 75 Blueprint.Schema.ObjectTypeDefinition, 76 Blueprint.Schema.UnionTypeDefinition, 77 Blueprint.Schema.InterfaceTypeDefinition 78 ] 79 defp wrong_type?(type, field_type) when type in @output_types do 80 !output_type?(field_type) 81 end 82 83 @input_types [ 84 Blueprint.Schema.InputObjectTypeDefinition, 85 Blueprint.Schema.InputValueDefinition 86 ] 87 defp wrong_type?(type, field_type) when type in @input_types do 88 !input_type?(field_type) 89 end 90 91 defp error(location, data) do 92 %Absinthe.Phase.Error{ 93 message: explanation(data), 94 locations: [location], 95 phase: __MODULE__, 96 extra: data 97 } 98 end 99 100 @moduledoc false 101 102 @description """ 103 Only input types may be used as inputs. Input types may not be used as output types 104 105 Input types consist of Scalars, Enums, and Input Objects. 106 """ 107 108 def explanation(%{argument: argument, type: type, struct: struct}) do 109 struct = struct |> Module.split() |> List.last() 110 111 """ 112 #{inspect(type)} is not a valid input type for argument #{inspect(argument)} because 113 #{inspect(type)} is an #{Macro.to_string(struct)}. Arguments may only be input types. 114 115 #{@description} 116 """ 117 end 118 119 def explanation(%{field: field, type: type, struct: struct, parent: parent}) do 120 struct = struct |> Module.split() |> List.last() 121 parent = parent |> Module.split() |> List.last() 122 123 """ 124 #{inspect(type)} is not a valid type for field #{inspect(field)} because 125 #{inspect(type)} is an #{struct}, and this field is part of an #{parent}. 126 127 #{@description} 128 """ 129 end 130 131 defp input_type?(%Blueprint.Schema.ScalarTypeDefinition{}), do: true 132 defp input_type?(%Blueprint.Schema.EnumTypeDefinition{}), do: true 133 defp input_type?(%Blueprint.Schema.InputObjectTypeDefinition{}), do: true 134 defp input_type?(_), do: false 135 136 defp output_type?(%Blueprint.Schema.InputObjectTypeDefinition{}), do: false 137 defp output_type?(_), do: true 138 end