zf

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

schema.ex (8545B)


      1 defmodule Absinthe.Phase.Schema do
      2   @moduledoc false
      3 
      4   # Populate all schema nodes and the adapter for the blueprint tree. If the
      5   # blueprint tree is a _schema_ tree, this schema is the meta schema (source of
      6   # SDL directives, etc).
      7   #
      8   # Note that no validation occurs in this phase.
      9 
     10   use Absinthe.Phase
     11 
     12   alias Absinthe.{Blueprint, Type, Schema}
     13 
     14   # The approach here is pretty simple.
     15   # We start at the top blueprint node and set the appropriate schema node on operations
     16   # directives and so forth.
     17   #
     18   # Then, as `prewalk` walks down the tree we hit a node. If that node has a schema_node
     19   # set by its parent, we walk to its children and set the schema node on those children.
     20   # We do not need to walk any further because `prewalk` will do that for us.
     21   #
     22   # Thus at each node we need only concern ourselves with immediate children.
     23   @spec run(Blueprint.t(), Keyword.t()) :: {:ok, Blueprint.t()}
     24   def run(input, options \\ []) do
     25     {input, schema} = apply_settings(input, Map.new(options))
     26 
     27     result =
     28       input
     29       |> update_context(schema)
     30       |> Blueprint.prewalk(&handle_node(&1, schema, input.adapter))
     31 
     32     {:ok, result}
     33   end
     34 
     35   # Set schema and adapter settings on the blueprint appropriate to whether we're
     36   # applying a normal schema for a document or a prototype schema used to define
     37   # a schema.
     38   defp apply_settings(input, %{prototype_schema: schema} = options) do
     39     adapter = Map.get(options, :adapter, Absinthe.Adapter.LanguageConventions)
     40     {%{input | prototype_schema: schema, adapter: adapter}, schema}
     41   end
     42 
     43   defp apply_settings(input, options) do
     44     adapter = Map.get(options, :adapter, Absinthe.Adapter.LanguageConventions)
     45     {%{input | schema: options.schema, adapter: adapter}, options.schema}
     46   end
     47 
     48   defp update_context(input, nil), do: input
     49 
     50   defp update_context(input, schema) do
     51     context = schema.context(input.execution.context)
     52     put_in(input.execution.context, context)
     53   end
     54 
     55   defp handle_node(%Blueprint{} = node, schema, adapter) do
     56     set_children(node, schema, adapter)
     57   end
     58 
     59   defp handle_node(%Absinthe.Blueprint.Document.VariableDefinition{} = node, _, _) do
     60     {:halt, node}
     61   end
     62 
     63   defp handle_node(node, schema, adapter) do
     64     set_children(node, schema, adapter)
     65   end
     66 
     67   defp set_children(parent, schema, adapter) do
     68     Blueprint.prewalk(parent, fn
     69       ^parent -> parent
     70       %Absinthe.Blueprint.Input.Variable{} = child -> {:halt, child}
     71       child -> {:halt, set_schema_node(child, parent, schema, adapter)}
     72     end)
     73   end
     74 
     75   # Do note, the `parent` arg is the parent blueprint node, not the parent's schema node.
     76   defp set_schema_node(
     77          %Blueprint.Document.Fragment.Inline{type_condition: %{name: type_name} = condition} =
     78            node,
     79          _parent,
     80          schema,
     81          _adapter
     82        ) do
     83     schema_node = Absinthe.Schema.lookup_type(schema, type_name)
     84     %{node | schema_node: schema_node, type_condition: %{condition | schema_node: schema_node}}
     85   end
     86 
     87   defp set_schema_node(%Blueprint.Directive{name: name} = node, _parent, schema, adapter) do
     88     %{node | schema_node: find_schema_directive(name, schema, adapter)}
     89   end
     90 
     91   defp set_schema_node(
     92          %Blueprint.Document.Operation{type: op_type} = node,
     93          _parent,
     94          schema,
     95          _adapter
     96        ) do
     97     %{node | schema_node: Absinthe.Schema.lookup_type(schema, op_type)}
     98   end
     99 
    100   defp set_schema_node(
    101          %Blueprint.Document.Fragment.Named{type_condition: %{name: type_name} = condition} =
    102            node,
    103          _parent,
    104          schema,
    105          _adapter
    106        ) do
    107     schema_node = Absinthe.Schema.lookup_type(schema, type_name)
    108     %{node | schema_node: schema_node, type_condition: %{condition | schema_node: schema_node}}
    109   end
    110 
    111   defp set_schema_node(
    112          %Blueprint.Document.VariableDefinition{type: type_reference} = node,
    113          _parent,
    114          schema,
    115          _adapter
    116        ) do
    117     wrapped =
    118       type_reference
    119       |> type_reference_to_type(schema)
    120 
    121     %{node | schema_node: wrapped}
    122   end
    123 
    124   defp set_schema_node(node, %{schema_node: nil}, _, _) do
    125     # if we don't know the parent schema node, and we aren't one of the earlier nodes,
    126     # then we can't know our schema node.
    127     node
    128   end
    129 
    130   defp set_schema_node(
    131          %Blueprint.Document.Fragment.Inline{type_condition: nil} = node,
    132          parent,
    133          schema,
    134          adapter
    135        ) do
    136     type =
    137       case parent.schema_node do
    138         %{type: type} -> type
    139         other -> other
    140       end
    141       |> Type.expand(schema)
    142       |> Type.unwrap()
    143 
    144     set_schema_node(
    145       %{node | type_condition: %Blueprint.TypeReference.Name{name: type.name, schema_node: type}},
    146       parent,
    147       schema,
    148       adapter
    149     )
    150   end
    151 
    152   defp set_schema_node(%Blueprint.Document.Field{} = node, parent, schema, adapter) do
    153     %{node | schema_node: find_schema_field(parent.schema_node, node.name, schema, adapter)}
    154   end
    155 
    156   defp set_schema_node(%Blueprint.Input.Argument{name: name} = node, parent, _schema, adapter) do
    157     schema_node = find_schema_argument(parent.schema_node, name, adapter)
    158     %{node | schema_node: schema_node}
    159   end
    160 
    161   defp set_schema_node(%Blueprint.Document.Fragment.Spread{} = node, _, _, _) do
    162     node
    163   end
    164 
    165   defp set_schema_node(%Blueprint.Input.Field{} = node, parent, schema, adapter) do
    166     case node.name do
    167       "__" <> _ ->
    168         %{node | schema_node: nil}
    169 
    170       name ->
    171         %{node | schema_node: find_schema_field(parent.schema_node, name, schema, adapter)}
    172     end
    173   end
    174 
    175   defp set_schema_node(%Blueprint.Input.List{} = node, parent, _schema, _adapter) do
    176     case Type.unwrap_non_null(parent.schema_node) do
    177       %{of_type: internal_type} ->
    178         %{node | schema_node: internal_type}
    179 
    180       _ ->
    181         node
    182     end
    183   end
    184 
    185   defp set_schema_node(%Blueprint.Input.Value{} = node, parent, schema, _) do
    186     case parent.schema_node do
    187       %Type.Argument{type: type} ->
    188         %{node | schema_node: type |> Type.expand(schema)}
    189 
    190       %Absinthe.Type.Field{type: type} ->
    191         %{node | schema_node: type |> Type.expand(schema)}
    192 
    193       type ->
    194         %{node | schema_node: type |> Type.expand(schema)}
    195     end
    196   end
    197 
    198   defp set_schema_node(%{schema_node: nil} = node, %Blueprint.Input.Value{} = parent, _schema, _) do
    199     %{node | schema_node: parent.schema_node}
    200   end
    201 
    202   defp set_schema_node(node, _, _, _) do
    203     node
    204   end
    205 
    206   # Given a schema field or directive, lookup a child argument definition
    207   @spec find_schema_argument(
    208           nil | Type.Field.t() | Type.Argument.t(),
    209           String.t(),
    210           Absinthe.Adapter.t()
    211         ) :: nil | Type.Argument.t()
    212   defp find_schema_argument(%{args: arguments}, name, adapter) do
    213     internal_name = adapter.to_internal_name(name, :argument)
    214 
    215     arguments
    216     |> Map.values()
    217     |> Enum.find(&match?(%{name: ^internal_name}, &1))
    218   end
    219 
    220   # Given a name, lookup a schema directive
    221   @spec find_schema_directive(String.t(), Absinthe.Schema.t(), Absinthe.Adapter.t()) ::
    222           nil | Type.Directive.t()
    223   defp find_schema_directive(name, schema, adapter) do
    224     internal_name = adapter.to_internal_name(name, :directive)
    225     schema.__absinthe_directive__(internal_name)
    226   end
    227 
    228   # Given a schema type, lookup a child field definition
    229   @spec find_schema_field(nil | Type.t(), String.t(), Absinthe.Schema.t(), Absinthe.Adapter.t()) ::
    230           nil | Type.Field.t()
    231 
    232   defp find_schema_field(%{of_type: type}, name, schema, adapter) do
    233     find_schema_field(type, name, schema, adapter)
    234   end
    235 
    236   defp find_schema_field(%{fields: fields}, name, _schema, adapter) do
    237     internal_name = adapter.to_internal_name(name, :field)
    238 
    239     fields
    240     |> Map.values()
    241     |> Enum.find(&match?(%{name: ^internal_name}, &1))
    242   end
    243 
    244   defp find_schema_field(%Type.Field{type: maybe_wrapped_type}, name, schema, adapter) do
    245     type =
    246       Type.unwrap(maybe_wrapped_type)
    247       |> schema.__absinthe_lookup__
    248 
    249     find_schema_field(type, name, schema, adapter)
    250   end
    251 
    252   defp find_schema_field(_, _, _, _) do
    253     nil
    254   end
    255 
    256   @type_mapping %{
    257     Blueprint.TypeReference.List => Type.List,
    258     Blueprint.TypeReference.NonNull => Type.NonNull
    259   }
    260   defp type_reference_to_type(%Blueprint.TypeReference.Name{name: name}, schema) do
    261     Schema.lookup_type(schema, name)
    262   end
    263 
    264   for {blueprint_type, core_type} <- @type_mapping do
    265     defp type_reference_to_type(%unquote(blueprint_type){} = node, schema) do
    266       inner = type_reference_to_type(node.of_type, schema)
    267       %unquote(core_type){of_type: inner}
    268     end
    269   end
    270 end