type.ex (9934B)
1 defmodule Absinthe.Type do 2 @moduledoc false 3 4 alias __MODULE__ 5 6 alias Absinthe.Schema 7 8 @type function_identifier :: {module, any} 9 @type function_ref :: {:ref, module, function_identifier} 10 11 # ALL TYPES 12 13 @type_modules [ 14 Type.Scalar, 15 Type.Object, 16 Type.Interface, 17 Type.Union, 18 Type.Enum, 19 Type.InputObject, 20 Type.List, 21 Type.NonNull 22 ] 23 24 @typedoc "The types that can be custom-built in a schema" 25 @type custom_t :: 26 Type.Scalar.t() 27 | Type.Object.t() 28 | Type.Field.t() 29 | Type.Interface.t() 30 | Type.Union.t() 31 | Type.Enum.t() 32 | Type.InputObject.t() 33 34 @typedoc "All the possible types" 35 @type t :: custom_t | wrapping_t 36 37 @typedoc "A type identifier" 38 @type identifier_t :: atom 39 40 @typedoc "A type reference" 41 @type reference_t :: identifier_t | binary | t 42 43 def function(type, key) do 44 case Map.fetch!(type, key) do 45 {:ref, module, identifier} -> 46 module.__absinthe_function__(identifier, key) 47 48 function -> 49 function 50 end 51 end 52 53 @doc false 54 # this is just for debugging 55 def expand(%module{} = type) do 56 module.functions() 57 |> Enum.reduce(type, fn 58 :middleware, type -> 59 type 60 61 attr, type -> 62 Map.put(type, attr, Absinthe.Type.function(type, attr)) 63 end) 64 end 65 66 @doc "Lookup a custom metadata field on a type" 67 @spec meta(custom_t, atom) :: nil | any 68 def meta(%{__private__: store}, key) do 69 get_in(store, [:meta, key]) 70 end 71 72 @doc "Return all custom metadata on a type" 73 @spec meta(custom_t) :: map 74 def meta(%{__private__: store}) do 75 Keyword.get(store, :meta, []) 76 |> Enum.into(%{}) 77 end 78 79 @doc "Determine if a struct matches one of the types" 80 @spec type?(any) :: boolean 81 def type?(%{__struct__: mod}) when mod in @type_modules, do: true 82 def type?(_), do: false 83 84 @doc "Determine whether a field/argument is deprecated" 85 @spec deprecated?(Type.Field.t() | Type.Argument.t()) :: boolean 86 def deprecated?(%{deprecation: nil}), do: false 87 def deprecated?(%{deprecation: _}), do: true 88 89 def equal?(%{name: name}, %{name: name}), do: true 90 def equal?(_, _), do: false 91 92 def built_in?(type) do 93 type.definition 94 |> built_in_module?() 95 end 96 97 def built_in_module?(module) do 98 module 99 |> Module.split() 100 |> Enum.take(3) 101 |> Module.concat() == Absinthe.Type.BuiltIns 102 end 103 104 # INPUT TYPES 105 106 @input_type_modules [Type.Scalar, Type.Enum, Type.InputObject, Type.List, Type.NonNull] 107 108 @typedoc "These types may be used as input types for arguments and directives." 109 @type input_t :: 110 Type.Scalar.t() 111 | Type.Enum.t() 112 | Type.InputObject.t() 113 | Type.List.t() 114 | Type.NonNull.t() 115 116 @doc "Determine if a term is an input type" 117 @spec input_type?(any) :: boolean 118 def input_type?(term) do 119 term 120 |> named_type 121 |> do_input_type? 122 end 123 124 defp do_input_type?(%{__struct__: mod}) when mod in @input_type_modules, do: true 125 defp do_input_type?(_), do: false 126 127 # OBJECT TYPE 128 129 @doc "Determine if a term is an object type" 130 @spec object_type?(any) :: boolean 131 def object_type?(%Type.Object{}), do: true 132 def object_type?(_), do: false 133 134 @doc "Resolve a type for a value from an interface (if necessary)" 135 @spec resolve_type(t, any) :: t 136 def resolve_type(%{resolve_type: resolver}, value), do: resolver.(value) 137 def resolve_type(type, _value), do: type 138 139 # TYPE WITH FIELDS 140 141 @doc "Determine if a type has fields" 142 @spec fielded?(any) :: boolean 143 def fielded?(%{fields: _}), do: true 144 def fielded?(_), do: false 145 146 # OUTPUT TYPES 147 148 @output_type_modules [Type.Scalar, Type.Object, Type.Interface, Type.Union, Type.Enum] 149 150 @typedoc "These types may be used as output types as the result of fields." 151 @type output_t :: 152 Type.Scalar.t() | Type.Object.t() | Type.Interface.t() | Type.Union.t() | Type.Enum.t() 153 154 @doc "Determine if a term is an output type" 155 @spec output_type?(any) :: boolean 156 def output_type?(term) do 157 term 158 |> named_type 159 |> do_output_type? 160 end 161 162 defp do_output_type?(%{__struct__: mod}) when mod in @output_type_modules, do: true 163 defp do_output_type?(_), do: false 164 165 # LEAF TYPES 166 167 @leaf_type_modules [Type.Scalar, Type.Enum] 168 169 @typedoc "These types may describe types which may be leaf values." 170 @type leaf_t :: Type.Scalar.t() | Type.Enum.t() 171 172 @doc "Determine if a term is a leaf type" 173 @spec leaf_type?(any) :: boolean 174 def leaf_type?(term) do 175 term 176 |> named_type 177 |> do_leaf_type? 178 end 179 180 defp do_leaf_type?(%{__struct__: mod}) when mod in @leaf_type_modules, do: true 181 defp do_leaf_type?(_), do: false 182 183 # COMPOSITE TYPES 184 185 @composite_type_modules [Type.Object, Type.Interface, Type.Union] 186 187 @typedoc "These types may describe the parent context of a selection set." 188 @type composite_t :: Type.Object.t() | Type.Interface.t() | Type.Union.t() 189 190 @doc "Determine if a term is a composite type" 191 @spec composite_type?(any) :: boolean 192 def composite_type?(%{__struct__: mod}) when mod in @composite_type_modules, do: true 193 def composite_type?(_), do: false 194 195 # ABSTRACT TYPES 196 197 @abstract_type_modules [Type.Interface, Type.Union] 198 199 @typedoc "These types may describe the parent context of a selection set." 200 @type abstract_t :: Type.Interface.t() | Type.Union.t() 201 202 @doc "Determine if a term is an abstract type" 203 @spec abstract?(any) :: boolean 204 def abstract?(%{__struct__: mod}) when mod in @abstract_type_modules, do: true 205 def abstract?(_), do: false 206 207 # NULLABLE TYPES 208 209 # @nullable_type_modules [Type.Scalar, Type.Object, Type.Interface, Type.Union, Type.Enum, Type.InputObject, Type.List] 210 211 @typedoc "These types can all accept null as a value." 212 @type nullable_t :: 213 Type.Scalar.t() 214 | Type.Object.t() 215 | Type.Interface.t() 216 | Type.Union.t() 217 | Type.Enum.t() 218 | Type.InputObject.t() 219 | Type.List.t() 220 221 @doc "Unwrap the underlying nullable type or return unmodified" 222 # nullable_t is a subset of t, but broken out for clarity 223 @spec nullable(any) :: nullable_t | t 224 def nullable(%Type.NonNull{of_type: nullable}), do: nullable 225 def nullable(term), do: term 226 227 @doc "Determine if a type is non null" 228 @spec non_null?(t) :: boolean 229 def non_null?(%Type.NonNull{}), do: true 230 def non_null?(_), do: false 231 232 # NAMED TYPES 233 234 @named_type_modules [ 235 Type.Scalar, 236 Type.Object, 237 Type.Interface, 238 Type.Union, 239 Type.Enum, 240 Type.InputObject 241 ] 242 243 @typedoc "These named types do not include modifiers like Absinthe.Type.List or Absinthe.Type.NonNull." 244 @type named_t :: 245 Type.Scalar.t() 246 | Type.Object.t() 247 | Type.Interface.t() 248 | Type.Union.t() 249 | Type.Enum.t() 250 | Type.InputObject.t() 251 252 @doc "Determine the underlying named type, if any" 253 @spec named_type(any) :: nil | named_t 254 def named_type(%{__struct__: mod, of_type: unmodified}) when mod in [Type.List, Type.NonNull] do 255 named_type(unmodified) 256 end 257 258 def named_type(%{__struct__: mod} = term) when mod in @named_type_modules, do: term 259 def named_type(_), do: nil 260 261 @doc "Determine if a type is named" 262 @spec named?(t) :: boolean 263 def named?(%{name: _}), do: true 264 def named?(_), do: false 265 266 # WRAPPERS 267 268 @wrapping_modules [Type.List, Type.NonNull] 269 270 @typedoc "A type wrapped in a List on NonNull" 271 @type wrapping_t :: Type.List.t() | Type.NonNull.t() 272 273 @spec wrapped?(t) :: boolean 274 def wrapped?(%{__struct__: mod}) when mod in @wrapping_modules, do: true 275 def wrapped?(_), do: false 276 277 @doc "Unwrap a type from a List or NonNull" 278 @spec unwrap(custom_t | wrapping_t | map) :: reference_t | map | nil 279 def unwrap(%{of_type: t}), do: unwrap(t) 280 def unwrap(type), do: type 281 282 @doc "Unwrap a type from NonNull" 283 @spec unwrap_non_null(Type.NonNull.t()) :: reference_t 284 @spec unwrap_non_null(type) :: type when type: custom_t | Type.List.t() 285 def unwrap_non_null(%Type.NonNull{of_type: t}), do: unwrap_non_null(t) 286 def unwrap_non_null(type), do: type 287 288 @doc """ 289 Get the GraphQL name for a (possibly wrapped) type, expanding 290 any references if necessary using the provided schema. 291 """ 292 @spec name(reference_t, Schema.t()) :: String.t() 293 def name(ref, schema) do 294 expanded = expand(ref, schema) 295 name(expanded) 296 end 297 298 @doc """ 299 Get the GraphQL name for a (possibly wrapped) type. 300 301 Note: Use `name/2` if the provided type reference needs to 302 be expanded to resolve any atom type references. 303 """ 304 @spec name(wrapping_t | t) :: String.t() 305 def name(%Type.NonNull{of_type: contents}) do 306 name(contents) <> "!" 307 end 308 309 def name(%Type.List{of_type: contents}) do 310 "[" <> name(contents) <> "]" 311 end 312 313 def name(%{name: name}) do 314 name 315 end 316 317 @doc "Expand any atom type references inside a List or NonNull" 318 @spec expand(reference_t, Schema.t()) :: wrapping_t | t 319 def expand(ref, schema) when is_atom(ref) do 320 schema.__absinthe_lookup__(ref) 321 end 322 323 def expand(%{of_type: contents} = ref, schema) do 324 %{ref | of_type: expand(contents, schema)} 325 end 326 327 def expand(type, _) do 328 type 329 end 330 331 # INTROSPECTION TYPE 332 333 @spec introspection?(t) :: boolean 334 def introspection?(%{name: "__" <> _}) do 335 true 336 end 337 338 def introspection?(_) do 339 false 340 end 341 342 # VALUE TYPE 343 344 @spec value_type(t, Schema.t()) :: Type.t() 345 def value_type(%Type.Field{} = node, schema) do 346 Type.expand(node.type, schema) 347 end 348 349 def value_type(type, schema) do 350 Type.expand(type, schema) 351 end 352 353 # VALID TYPE 354 355 def valid_input?(%Type.NonNull{}, nil) do 356 false 357 end 358 359 def valid_input?(%Type.NonNull{of_type: internal_type}, value) do 360 valid_input?(internal_type, value) 361 end 362 363 def valid_input?(_type, nil) do 364 true 365 end 366 367 def valid_input?(%{parse: parse}, value) do 368 case parse.(value) do 369 {:ok, _} -> true 370 :error -> false 371 end 372 end 373 374 def valid_input?(_, _) do 375 true 376 end 377 end