zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

object_must_implement_interfaces.ex (6673B)


      1 defmodule Absinthe.Phase.Schema.Validation.ObjectMustImplementInterfaces 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     ifaces =
     12       schema.type_definitions
     13       |> Enum.filter(&match?(%Blueprint.Schema.InterfaceTypeDefinition{}, &1))
     14       |> Map.new(&{&1.identifier, &1})
     15 
     16     types =
     17       schema.type_definitions
     18       |> Map.new(&{&1.identifier, &1})
     19 
     20     schema = Blueprint.prewalk(schema, &validate_objects(&1, ifaces, types))
     21     {:halt, schema}
     22   end
     23 
     24   defp handle_schemas(obj) do
     25     obj
     26   end
     27 
     28   @interface_types [
     29     Blueprint.Schema.ObjectTypeDefinition,
     30     Blueprint.Schema.InterfaceTypeDefinition
     31   ]
     32 
     33   defp validate_objects(%struct{} = object, ifaces, types) when struct in @interface_types do
     34     Enum.reduce(object.interfaces, object, fn ident, object ->
     35       case Map.fetch(ifaces, ident) do
     36         {:ok, iface} -> validate_object(object, iface, types)
     37         _ -> object
     38       end
     39     end)
     40   end
     41 
     42   defp validate_objects(type, _, _) do
     43     type
     44   end
     45 
     46   def validate_object(object, iface, types) do
     47     case check_implements(iface, object, types) do
     48       :ok ->
     49         object
     50 
     51       {:error, invalid_fields} ->
     52         detail = %{
     53           object: object.identifier,
     54           interface: iface.identifier,
     55           fields: invalid_fields
     56         }
     57 
     58         object |> put_error(error(object, detail))
     59     end
     60   end
     61 
     62   defp error(object, data) do
     63     %Absinthe.Phase.Error{
     64       message: explanation(data),
     65       locations: [object.__reference__.location],
     66       phase: __MODULE__,
     67       extra: data
     68     }
     69   end
     70 
     71   @moduledoc false
     72 
     73   @description """
     74   An object type must be a super-set of all interfaces it implements.
     75 
     76   * The object type must include a field of the same name for every field
     77     defined in an interface.
     78     * The object field must be of a type which is equal to or a sub-type of
     79       the interface field (covariant).
     80     * An object field type is a valid sub-type if it is equal to (the same
     81       type as) the interface field type.
     82     * An object field type is a valid sub-type if it is an Object type and the
     83       interface field type is either an Interface type or a Union type and the
     84       object field type is a possible type of the interface field type.
     85     * An object field type is a valid sub-type if it is a List type and the
     86       interface field type is also a List type and the list-item type of the
     87       object field type is a valid sub-type of the list-item type of the
     88       interface field type.
     89     * An object field type is a valid sub-type if it is a Non-Null variant of a
     90       valid sub-type of the interface field type.
     91   * The object field must include an argument of the same name for every
     92     argument defined in the interface field.
     93     * The object field argument must accept the same type (invariant) as the
     94       interface field argument.
     95   * The object field may include additional arguments not defined in the
     96     interface field, but any additional argument must not be required.
     97 
     98   Reference: https://github.com/facebook/graphql/blob/master/spec/Section%203%20--%20Type%20System.md#object-type-validation
     99   """
    100 
    101   def explanation(%{object: obj, interface: interface, fields: fields}) do
    102     """
    103     Type "#{obj}" does not fully implement interface type "#{interface}" \
    104     for fields #{inspect(fields)}
    105 
    106     #{@description}
    107     """
    108   end
    109 
    110   def check_implements(interface, type, types) do
    111     check_covariant(interface, type, nil, types)
    112   end
    113 
    114   defp check_covariant(
    115          %Blueprint.Schema.InterfaceTypeDefinition{fields: ifields},
    116          %{fields: type_fields},
    117          _field_ident,
    118          types
    119        ) do
    120     Enum.reduce(ifields, [], fn %{identifier: ifield_ident} = ifield, invalid_fields ->
    121       case Enum.find(type_fields, &(&1.identifier == ifield_ident)) do
    122         nil ->
    123           [ifield_ident | invalid_fields]
    124 
    125         field ->
    126           case check_covariant(ifield.type, field.type, ifield_ident, types) do
    127             :ok ->
    128               invalid_fields
    129 
    130             {:error, invalid_field} ->
    131               [invalid_field | invalid_fields]
    132           end
    133       end
    134     end)
    135     |> case do
    136       [] ->
    137         :ok
    138 
    139       invalid_fields ->
    140         {:error, invalid_fields}
    141     end
    142   end
    143 
    144   defp check_covariant(
    145          %Blueprint.Schema.InterfaceTypeDefinition{identifier: interface_ident},
    146          interface_ident,
    147          _field_ident,
    148          _types
    149        ) do
    150     :ok
    151   end
    152 
    153   defp check_covariant(
    154          %Blueprint.Schema.InterfaceTypeDefinition{identifier: interface_ident},
    155          field_type,
    156          field_ident,
    157          types
    158        ) do
    159     %{interfaces: field_type_interfaces} = Map.get(types, field_type)
    160     (interface_ident in field_type_interfaces && :ok) || {:error, field_ident}
    161   end
    162 
    163   defp check_covariant(
    164          %wrapper{of_type: inner_type1},
    165          %wrapper{of_type: inner_type2},
    166          field_ident,
    167          types
    168        ) do
    169     check_covariant(inner_type1, inner_type2, field_ident, types)
    170   end
    171 
    172   defp check_covariant(
    173          itype,
    174          %Absinthe.Blueprint.TypeReference.NonNull{of_type: inner_type},
    175          field_ident,
    176          types
    177        ) do
    178     check_covariant(itype, inner_type, field_ident, types)
    179   end
    180 
    181   defp check_covariant(%{identifier: identifier}, %{identifier: identifier}, _field_ident, _types) do
    182     :ok
    183   end
    184 
    185   defp check_covariant(
    186          %Blueprint.TypeReference.Name{name: name},
    187          %Blueprint.TypeReference.Name{name: name},
    188          _field_ident,
    189          _types
    190        ) do
    191     :ok
    192   end
    193 
    194   defp check_covariant(
    195          %Blueprint.TypeReference.Name{name: iface_name},
    196          %Blueprint.TypeReference.Name{name: type_name},
    197          field_ident,
    198          types
    199        ) do
    200     {_, itype} = Enum.find(types, fn {_, %{name: name}} -> name == iface_name end)
    201     {_, type} = Enum.find(types, fn {_, %{name: name}} -> name == type_name end)
    202 
    203     check_covariant(itype, type, field_ident, types)
    204   end
    205 
    206   defp check_covariant(nil, _, field_ident, _), do: {:error, field_ident}
    207   defp check_covariant(_, nil, field_ident, _), do: {:error, field_ident}
    208 
    209   defp check_covariant(itype, type, field_ident, types) when is_atom(itype) do
    210     itype = Map.get(types, itype)
    211     check_covariant(itype, type, field_ident, types)
    212   end
    213 
    214   defp check_covariant(itype, type, field_ident, types) when is_atom(type) do
    215     type = Map.get(types, type)
    216     check_covariant(itype, type, field_ident, types)
    217   end
    218 
    219   defp check_covariant(_, _, field_ident, _types) do
    220     {:error, field_ident}
    221   end
    222 end