zf

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

notation.ex (51887B)


      1 defmodule Absinthe.Schema.Notation do
      2   alias Absinthe.Blueprint.Schema
      3   alias Absinthe.Utils
      4 
      5   @moduledoc """
      6   Provides a set of macros to use when creating a schema. Especially useful
      7   when moving definitions out into a different module than the schema itself.
      8 
      9   ## Example
     10 
     11       defmodule MyAppWeb.Schema.Types do
     12         use Absinthe.Schema.Notation
     13 
     14         object :item do
     15           field :id, :id
     16           field :name, :string
     17         end
     18 
     19         # ...
     20 
     21       end
     22 
     23   """
     24 
     25   Module.register_attribute(__MODULE__, :placement, accumulate: true)
     26 
     27   defmacro __using__(import_opts \\ [only: :macros]) do
     28     Module.register_attribute(__CALLER__.module, :absinthe_blueprint, accumulate: true)
     29     Module.register_attribute(__CALLER__.module, :absinthe_desc, accumulate: true)
     30     put_attr(__CALLER__.module, %Absinthe.Blueprint{schema: __CALLER__.module})
     31     Module.put_attribute(__CALLER__.module, :absinthe_scope_stack, [:schema])
     32     Module.put_attribute(__CALLER__.module, :absinthe_scope_stack_stash, [])
     33 
     34     quote do
     35       import Absinthe.Resolution.Helpers,
     36         only: [
     37           async: 1,
     38           async: 2,
     39           batch: 3,
     40           batch: 4
     41         ]
     42 
     43       Module.register_attribute(__MODULE__, :__absinthe_type_import__, accumulate: true)
     44       @desc nil
     45       import unquote(__MODULE__), unquote(import_opts)
     46       @before_compile unquote(__MODULE__)
     47     end
     48   end
     49 
     50   ### Macro API ###
     51 
     52   @placement {:config, [under: [:field]]}
     53   @doc """
     54   Configure a subscription field.
     55 
     56   The first argument to the config function is the field arguments passed in the subscription.
     57   The second argument is an `Absinthe.Resolution` struct, which includes information
     58   like the context and other execution data.
     59 
     60   ## Placement
     61 
     62   #{Utils.placement_docs(@placement)}
     63 
     64   ## Examples
     65 
     66   ```elixir
     67   config fn args, %{context: context} ->
     68     if authorized?(context) do
     69       {:ok, topic: args.client_id}
     70     else
     71       {:error, "unauthorized"}
     72     end
     73   end
     74   ```
     75 
     76   Alternatively can provide a list of topics:
     77 
     78   ```elixir
     79   config fn _, _ ->
     80     {:ok, topic: ["topic_one", "topic_two", "topic_three"]}
     81   end
     82   ```
     83 
     84   Using `context_id` option to allow de-duplication of updates:
     85 
     86   ```elixir
     87   config fn _, %{context: context} ->
     88     if authorized?(context) do
     89       {:ok, topic: "topic_one", context_id: "authorized"}
     90     else
     91       {:ok, topic: "topic_one", context_id: "not-authorized"}
     92     end
     93   end
     94   ```
     95 
     96   See `Absinthe.Schema.subscription/1` for details
     97   """
     98   defmacro config(config_fun) do
     99     __CALLER__
    100     |> recordable!(:config, @placement[:config])
    101     |> record_config!(config_fun)
    102   end
    103 
    104   @placement {:trigger, [under: [:field]]}
    105   @doc """
    106   Sets triggers for a subscription, and configures which topics to publish to when that subscription
    107   is triggered.
    108 
    109   A trigger is the name of a mutation. When that mutation runs, data is pushed to the clients
    110   who are subscribed to the subscription.
    111 
    112   A subscription can have many triggers, and a trigger can push to many topics.
    113 
    114   ## Placement
    115 
    116   #{Utils.placement_docs(@placement)}
    117 
    118   ## Example
    119 
    120   ```elixir
    121   mutation do
    122     field :gps_event, :gps_event
    123     field :user_checkin, :user
    124   end
    125 
    126   subscription do
    127     field :location_update, :user do
    128       arg :user_id, non_null(:id)
    129 
    130       config fn args, _ ->
    131         {:ok, topic: args.user_id}
    132       end
    133 
    134       trigger :gps_event, topic: fn gps_event ->
    135         gps_event.user_id
    136       end
    137 
    138       # Trigger on a list of mutations
    139       trigger [:user_checkin], topic: fn user ->
    140         # Returning a list of topics triggers the subscription for each of the topics in the list.
    141         [user.id, user.friend.id]
    142       end
    143     end
    144   end
    145   ```
    146 
    147   Trigger functions are only called once per event, so database calls within
    148   them do not present a significant burden.
    149 
    150   See the `Absinthe.Schema.subscription/2` macro docs for additional details
    151   """
    152   defmacro trigger(mutations, attrs) do
    153     __CALLER__
    154     |> recordable!(:trigger, @placement[:trigger])
    155     |> record_trigger!(List.wrap(mutations), attrs)
    156   end
    157 
    158   # OBJECT
    159 
    160   @placement {:object, [toplevel: true]}
    161   @doc """
    162   Define an object type.
    163 
    164   Adds an `Absinthe.Type.Object` to your schema.
    165 
    166   ## Placement
    167 
    168   #{Utils.placement_docs(@placement)}
    169 
    170   ## Examples
    171 
    172   Basic definition:
    173 
    174   ```
    175   object :car do
    176     # ...
    177   end
    178   ```
    179 
    180   Providing a custom name:
    181 
    182   ```
    183   object :car, name: "CarType" do
    184     # ...
    185   end
    186   ```
    187   """
    188   @reserved_identifiers ~w(query mutation subscription)a
    189   defmacro object(identifier, attrs \\ [], block)
    190 
    191   defmacro object(identifier, _attrs, _block) when identifier in @reserved_identifiers do
    192     raise Absinthe.Schema.Notation.Error,
    193           "Invalid schema notation: cannot create an `object` " <>
    194             "with reserved identifier `#{identifier}`"
    195   end
    196 
    197   defmacro object(identifier, attrs, do: block) do
    198     block =
    199       for {identifier, args} <- build_directives(attrs) do
    200         quote do
    201           directive(unquote(identifier), unquote(args))
    202         end
    203       end ++ block
    204 
    205     {attrs, block} =
    206       case Keyword.pop(attrs, :meta) do
    207         {nil, attrs} ->
    208           {attrs, block}
    209 
    210         {meta, attrs} ->
    211           meta_ast =
    212             quote do
    213               meta unquote(meta)
    214             end
    215 
    216           block = [meta_ast, block]
    217           {attrs, block}
    218       end
    219 
    220     __CALLER__
    221     |> recordable!(:object, @placement[:object])
    222     |> record!(
    223       Schema.ObjectTypeDefinition,
    224       identifier,
    225       attrs |> Keyword.update(:description, nil, &wrap_in_unquote/1),
    226       block
    227     )
    228   end
    229 
    230   @placement {:interfaces, [under: [:object, :interface]]}
    231   @doc """
    232   Declare implemented interfaces for an object.
    233 
    234   See also `interface/1`, which can be used for one interface,
    235   and `interface/3`, used to define interfaces themselves.
    236 
    237   ## Placement
    238 
    239   #{Utils.placement_docs(@placement)}
    240 
    241   ## Examples
    242 
    243   ```
    244   object :car do
    245     interfaces [:vehicle, :branded]
    246     # ...
    247   end
    248   ```
    249   """
    250   defmacro interfaces(ifaces) when is_list(ifaces) do
    251     __CALLER__
    252     |> recordable!(:interfaces, @placement[:interfaces])
    253     |> record_interfaces!(ifaces)
    254   end
    255 
    256   @placement {:deprecate, [under: [:field]]}
    257   @doc """
    258   Mark a field as deprecated
    259 
    260   In most cases you can simply pass the deprecate: "message" attribute. However
    261   when using the block form of a field it can be nice to also use this macro.
    262 
    263   ## Placement
    264 
    265   #{Utils.placement_docs(@placement)}
    266 
    267   ## Examples
    268   ```
    269   field :foo, :string do
    270     deprecate "Foo will no longer be supported"
    271   end
    272   ```
    273 
    274   This is how to deprecate other things
    275   ```
    276   field :foo, :string do
    277     arg :bar, :integer, deprecate: "This isn't supported either"
    278   end
    279 
    280   enum :colors do
    281     value :red
    282     value :blue, deprecate: "This isn't supported"
    283   end
    284   ```
    285   """
    286   defmacro deprecate(msg) do
    287     __CALLER__
    288     |> recordable!(:deprecate, @placement[:deprecate])
    289     |> record_deprecate!(msg)
    290   end
    291 
    292   @doc """
    293   Declare an implemented interface for an object.
    294 
    295   Adds an `Absinthe.Type.Interface` to your schema.
    296 
    297   See also `interfaces/1`, which can be used for multiple interfaces,
    298   and `interface/3`, used to define interfaces themselves.
    299 
    300   ## Examples
    301 
    302   ```
    303   object :car do
    304     interface :vehicle
    305     # ...
    306   end
    307   ```
    308   """
    309   @placement {:interface_attribute, [under: [:object, :interface]]}
    310   defmacro interface(identifier) do
    311     __CALLER__
    312     |> recordable!(:interface_attribute, @placement[:interface_attribute])
    313     |> record_interface!(identifier)
    314   end
    315 
    316   # INTERFACES
    317 
    318   @placement {:interface, [toplevel: true]}
    319   @doc """
    320   Define an interface type.
    321 
    322   Adds an `Absinthe.Type.Interface` to your schema.
    323 
    324   Also see `interface/1` and `interfaces/1`, which declare
    325   that an object implements one or more interfaces.
    326 
    327   ## Placement
    328 
    329   #{Utils.placement_docs(@placement)}
    330 
    331   ## Examples
    332 
    333   ```
    334   interface :vehicle do
    335     field :wheel_count, :integer
    336   end
    337 
    338   object :rally_car do
    339     field :wheel_count, :integer
    340     interface :vehicle
    341   end
    342   ```
    343   """
    344   defmacro interface(identifier, attrs \\ [], do: block) do
    345     __CALLER__
    346     |> recordable!(:interface, @placement[:interface])
    347     |> record!(Schema.InterfaceTypeDefinition, identifier, attrs, block)
    348   end
    349 
    350   @placement {:resolve_type, [under: [:interface, :union]]}
    351   @doc """
    352   Define a type resolver for a union or interface.
    353 
    354   See also:
    355   * `Absinthe.Type.Interface`
    356   * `Absinthe.Type.Union`
    357 
    358   ## Placement
    359 
    360   #{Utils.placement_docs(@placement)}
    361 
    362   ## Examples
    363 
    364   ```
    365   interface :entity do
    366     # ...
    367     resolve_type fn
    368       %{employee_count: _},  _ ->
    369         :business
    370       %{age: _}, _ ->
    371         :person
    372     end
    373   end
    374   ```
    375   """
    376   defmacro resolve_type(func_ast) do
    377     __CALLER__
    378     |> recordable!(:resolve_type, @placement[:resolve_type])
    379     |> record_resolve_type!(func_ast)
    380   end
    381 
    382   defp handle_field_attrs(attrs, caller) do
    383     block =
    384       for {identifier, arg_attrs} <- Keyword.get(attrs, :args, []) do
    385         quote do
    386           arg unquote(identifier), unquote(arg_attrs)
    387         end
    388       end
    389 
    390     block =
    391       for {identifier, args} <- build_directives(attrs) do
    392         quote do
    393           directive(unquote(identifier), unquote(args))
    394         end
    395       end ++ block
    396 
    397     block =
    398       case Keyword.get(attrs, :meta) do
    399         nil ->
    400           block
    401 
    402         meta ->
    403           meta_ast =
    404             quote do
    405               meta unquote(meta)
    406             end
    407 
    408           [meta_ast, block]
    409       end
    410 
    411     {func_ast, attrs} = Keyword.pop(attrs, :resolve)
    412 
    413     block =
    414       if func_ast do
    415         [
    416           quote do
    417             resolve unquote(func_ast)
    418           end
    419         ]
    420       else
    421         []
    422       end ++ block
    423 
    424     attrs =
    425       attrs
    426       |> expand_ast(caller)
    427       |> Keyword.delete(:deprecate)
    428       |> Keyword.delete(:directives)
    429       |> Keyword.delete(:args)
    430       |> Keyword.delete(:meta)
    431       |> Keyword.update(:description, nil, &wrap_in_unquote/1)
    432       |> Keyword.update(:default_value, nil, &wrap_in_unquote/1)
    433 
    434     {attrs, block}
    435   end
    436 
    437   # FIELDS
    438   @placement {:field, [under: [:input_object, :interface, :object]]}
    439   @doc """
    440   Defines a GraphQL field
    441 
    442   See `field/4`
    443   """
    444 
    445   defmacro field(identifier, attrs) when is_list(attrs) do
    446     {attrs, block} = handle_field_attrs(attrs, __CALLER__)
    447 
    448     __CALLER__
    449     |> recordable!(:field, @placement[:field])
    450     |> record!(Schema.FieldDefinition, identifier, attrs, block)
    451   end
    452 
    453   defmacro field(identifier, type) do
    454     {attrs, block} = handle_field_attrs([type: type], __CALLER__)
    455 
    456     __CALLER__
    457     |> recordable!(:field, @placement[:field])
    458     |> record!(Schema.FieldDefinition, identifier, attrs, block)
    459   end
    460 
    461   @doc """
    462   Defines a GraphQL field
    463 
    464   See `field/4`
    465   """
    466   defmacro field(identifier, attrs, do: block) when is_list(attrs) do
    467     {attrs, more_block} = handle_field_attrs(attrs, __CALLER__)
    468     block = more_block ++ List.wrap(block)
    469 
    470     __CALLER__
    471     |> recordable!(:field, @placement[:field])
    472     |> record!(Schema.FieldDefinition, identifier, attrs, block)
    473   end
    474 
    475   defmacro field(identifier, type, do: block) do
    476     {attrs, _} = handle_field_attrs([type: type], __CALLER__)
    477 
    478     __CALLER__
    479     |> recordable!(:field, @placement[:field])
    480     |> record!(Schema.FieldDefinition, identifier, attrs, block)
    481   end
    482 
    483   defmacro field(identifier, type, attrs) do
    484     {attrs, block} = handle_field_attrs(Keyword.put(attrs, :type, type), __CALLER__)
    485 
    486     __CALLER__
    487     |> recordable!(:field, @placement[:field])
    488     |> record!(Schema.FieldDefinition, identifier, attrs, block)
    489   end
    490 
    491   @doc """
    492   Defines a GraphQL field.
    493 
    494   ## Placement
    495 
    496   #{Utils.placement_docs(@placement)}
    497 
    498   `query`, `mutation`, and `subscription` are
    499   all objects under the covers, and thus you'll find `field` definitions under
    500   those as well.
    501 
    502   ## Examples
    503   ```
    504   field :id, :id
    505   field :age, :integer, description: "How old the item is"
    506   field :name, :string do
    507     description "The name of the item"
    508   end
    509   field :location, type: :location
    510   ```
    511   """
    512   defmacro field(identifier, type, attrs, do: block) do
    513     attrs = Keyword.put(attrs, :type, type)
    514     {attrs, more_block} = handle_field_attrs(attrs, __CALLER__)
    515     block = more_block ++ List.wrap(block)
    516 
    517     __CALLER__
    518     |> recordable!(:field, @placement[:field])
    519     |> record!(Schema.FieldDefinition, identifier, attrs, block)
    520   end
    521 
    522   @placement {:resolve, [under: [:field]]}
    523   @doc """
    524   Defines a resolve function for a field
    525 
    526   Specify a 2 or 3 arity function to call when resolving a field.
    527 
    528   You can either hard code a particular anonymous function, or have a function
    529   call that returns a 2 or 3 arity anonymous function. See examples for more information.
    530 
    531   Note that when using a hard coded anonymous function, the function will not
    532   capture local variables.
    533 
    534   ### 3 Arity Functions
    535 
    536   The first argument to the function is the parent entity.
    537   ```
    538   {
    539     user(id: 1) {
    540       name
    541     }
    542   }
    543   ```
    544   A resolution function on the `name` field would have the result of the `user(id: 1)` field
    545   as its first argument. Top level fields have the `root_value` as their first argument.
    546   Unless otherwise specified, this defaults to an empty map.
    547 
    548   The second argument to the resolution function is the field arguments. The final
    549   argument is an `Absinthe.Resolution` struct, which includes information like
    550   the `context` and other execution data.
    551 
    552   ### 2 Arity Function
    553 
    554   Exactly the same as the 3 arity version, but without the first argument (the parent entity)
    555 
    556   ## Placement
    557 
    558   #{Utils.placement_docs(@placement)}
    559 
    560   ## Examples
    561   ```
    562   query do
    563     field :person, :person do
    564       resolve &Person.resolve/2
    565     end
    566   end
    567   ```
    568 
    569   ```
    570   query do
    571     field :person, :person do
    572       resolve fn %{id: id}, _ ->
    573         {:ok, Person.find(id)}
    574       end
    575     end
    576   end
    577   ```
    578 
    579   ```
    580   query do
    581     field :person, :person do
    582       resolve lookup(:person)
    583     end
    584   end
    585 
    586   def lookup(:person) do
    587     fn %{id: id}, _ ->
    588       {:ok, Person.find(id)}
    589     end
    590   end
    591   ```
    592   """
    593   defmacro resolve(func_ast) do
    594     __CALLER__
    595     |> recordable!(:resolve, @placement[:resolve])
    596 
    597     quote do
    598       middleware Absinthe.Resolution, unquote(func_ast)
    599     end
    600   end
    601 
    602   @placement {:complexity, [under: [:field]]}
    603   @doc """
    604   Set the complexity of a field
    605 
    606   For a field, the first argument to the function you supply to `complexity/1` is the user arguments -- just as a field's resolver can use user arguments to resolve its value, the complexity function that you provide can use the same arguments to calculate the field's complexity.
    607 
    608   The second argument passed to your complexity function is the sum of all the complexity scores of all the fields nested below the current field.
    609 
    610   An optional third argument is passed an `Absinthe.Complexity` struct, which includes information
    611   like the context passed to `Absinthe.run/3`.
    612 
    613   ## Placement
    614 
    615   #{Utils.placement_docs(@placement)}
    616 
    617   ## Examples
    618   ```
    619   query do
    620     field :people, list_of(:person) do
    621       arg :limit, :integer, default_value: 10
    622       complexity fn %{limit: limit}, child_complexity ->
    623         # set complexity based on maximum number of items in the list and
    624         # complexity of a child.
    625         limit * child_complexity
    626       end
    627     end
    628   end
    629   ```
    630   """
    631   defmacro complexity(func_ast) do
    632     __CALLER__
    633     |> recordable!(:complexity, @placement[:complexity])
    634     |> record_complexity!(func_ast)
    635   end
    636 
    637   @placement {:middleware, [under: [:field]]}
    638   defmacro middleware(new_middleware, opts \\ []) do
    639     __CALLER__
    640     |> recordable!(:middleware, @placement[:middleware])
    641     |> record_middleware!(new_middleware, opts)
    642   end
    643 
    644   @placement {:is_type_of, [under: [:object]]}
    645   @doc """
    646 
    647   ## Placement
    648 
    649   #{Utils.placement_docs(@placement)}
    650   """
    651   defmacro is_type_of(func_ast) do
    652     __CALLER__
    653     |> recordable!(:is_type_of, @placement[:is_type_of])
    654     |> record_is_type_of!(func_ast)
    655   end
    656 
    657   @placement {:arg, [under: [:directive, :field]]}
    658   # ARGS
    659   @doc """
    660   Add an argument.
    661 
    662   ## Placement
    663 
    664   #{Utils.placement_docs(@placement)}
    665 
    666   ## Examples
    667 
    668   ```
    669   field do
    670     arg :size, :integer
    671     arg :name, non_null(:string), description: "The desired name"
    672     arg :public, :boolean, default_value: true
    673   end
    674   ```
    675   """
    676   defmacro arg(identifier, type, attrs) do
    677     {attrs, block} = handle_arg_attrs(identifier, type, attrs)
    678 
    679     __CALLER__
    680     |> recordable!(:arg, @placement[:arg])
    681     |> record!(Schema.InputValueDefinition, identifier, attrs, block)
    682   end
    683 
    684   @doc """
    685   Add an argument.
    686 
    687   See `arg/3`
    688   """
    689   defmacro arg(identifier, attrs) when is_list(attrs) do
    690     {attrs, block} = handle_arg_attrs(identifier, nil, attrs)
    691 
    692     __CALLER__
    693     |> recordable!(:arg, @placement[:arg])
    694     |> record!(Schema.InputValueDefinition, identifier, attrs, block)
    695   end
    696 
    697   defmacro arg(identifier, type) do
    698     {attrs, block} = handle_arg_attrs(identifier, type, [])
    699 
    700     __CALLER__
    701     |> recordable!(:arg, @placement[:arg])
    702     |> record!(Schema.InputValueDefinition, identifier, attrs, block)
    703   end
    704 
    705   # SCALARS
    706 
    707   @placement {:scalar, [toplevel: true]}
    708   @doc """
    709   Define a scalar type
    710 
    711   A scalar type requires `parse/1` and `serialize/1` functions.
    712 
    713   ## Placement
    714 
    715   #{Utils.placement_docs(@placement)}
    716 
    717   ## Examples
    718   ```
    719   scalar :time, description: "ISOz time" do
    720     parse &Timex.parse(&1.value, "{ISOz}")
    721     serialize &Timex.format!(&1, "{ISOz}")
    722   end
    723   ```
    724   """
    725   defmacro scalar(identifier, attrs, do: block) do
    726     __CALLER__
    727     |> recordable!(:scalar, @placement[:scalar])
    728     |> record_scalar!(identifier, attrs, block)
    729   end
    730 
    731   @doc """
    732   Defines a scalar type
    733 
    734   See `scalar/3`
    735   """
    736   defmacro scalar(identifier, do: block) do
    737     __CALLER__
    738     |> recordable!(:scalar, @placement[:scalar])
    739     |> record_scalar!(identifier, [], block)
    740   end
    741 
    742   defmacro scalar(identifier, attrs) do
    743     __CALLER__
    744     |> recordable!(:scalar, @placement[:scalar])
    745     |> record_scalar!(identifier, attrs, nil)
    746   end
    747 
    748   @placement {:serialize, [under: [:scalar]]}
    749   @doc """
    750   Defines a serialization function for a `scalar` type
    751 
    752   The specified `serialize` function is used on outgoing data. It should simply
    753   return the desired external representation.
    754 
    755   ## Placement
    756 
    757   #{Utils.placement_docs(@placement)}
    758   """
    759   defmacro serialize(func_ast) do
    760     __CALLER__
    761     |> recordable!(:serialize, @placement[:serialize])
    762     |> record_serialize!(func_ast)
    763   end
    764 
    765   @placement {:private,
    766               [under: [:field, :object, :input_object, :enum, :scalar, :interface, :union]]}
    767   @doc false
    768   defmacro private(owner, key, value) do
    769     __CALLER__
    770     |> recordable!(:private, @placement[:private])
    771     |> record_private!(owner, [{key, value}])
    772   end
    773 
    774   @placement {:meta,
    775               [under: [:field, :object, :input_object, :enum, :scalar, :interface, :union]]}
    776   @doc """
    777   Defines a metadata key/value pair for a custom type.
    778 
    779   For more info see `meta/1`
    780 
    781   ### Examples
    782 
    783   ```
    784   meta :cache, false
    785   ```
    786 
    787   ## Placement
    788 
    789   #{Utils.placement_docs(@placement)}
    790   """
    791   defmacro meta(key, value) do
    792     __CALLER__
    793     |> recordable!(:meta, @placement[:meta])
    794     |> record_private!(:meta, [{key, value}])
    795   end
    796 
    797   @doc """
    798   Defines list of metadata's key/value pair for a custom type.
    799 
    800   This is generally used to facilitate libraries that want to augment Absinthe
    801   functionality
    802 
    803   ## Examples
    804 
    805   ```
    806   object :user do
    807     meta cache: true, ttl: 22_000
    808   end
    809 
    810   object :user, meta: [cache: true, ttl: 22_000] do
    811     # ...
    812   end
    813   ```
    814 
    815   The meta can be accessed via the `Absinthe.Type.meta/2` function.
    816 
    817   ```
    818   user_type = Absinthe.Schema.lookup_type(MyApp.Schema, :user)
    819 
    820   Absinthe.Type.meta(user_type, :cache)
    821   #=> true
    822 
    823   Absinthe.Type.meta(user_type)
    824   #=> [cache: true, ttl: 22_000]
    825   ```
    826 
    827   ## Placement
    828 
    829   #{Utils.placement_docs(@placement)}
    830   """
    831   defmacro meta(keyword_list) do
    832     __CALLER__
    833     |> recordable!(:meta, @placement[:meta])
    834     |> record_private!(:meta, keyword_list)
    835   end
    836 
    837   @placement {:parse, [under: [:scalar]]}
    838   @doc """
    839   Defines a parse function for a `scalar` type
    840 
    841   The specified `parse` function is used on incoming data to transform it into
    842   an elixir datastructure.
    843 
    844   It should return `{:ok, value}` or `:error`
    845 
    846   ## Placement
    847 
    848   #{Utils.placement_docs(@placement)}
    849   """
    850   defmacro parse(func_ast) do
    851     __CALLER__
    852     |> recordable!(:parse, @placement[:parse])
    853     |> record_parse!(func_ast)
    854   end
    855 
    856   # DIRECTIVES
    857 
    858   @placement {:directive, [toplevel: true]}
    859   @placement {:applied_directive,
    860               [
    861                 under: [
    862                   :arg,
    863                   :enum,
    864                   :field,
    865                   :input_object,
    866                   :interface,
    867                   :object,
    868                   :scalar,
    869                   :union,
    870                   :value
    871                 ]
    872               ]}
    873 
    874   @doc """
    875   Defines or applies a directive
    876 
    877   ## Defining a directive
    878   ### Placement
    879 
    880   #{Utils.placement_docs(@placement, :directive)}
    881 
    882   ### Examples
    883 
    884   ```elixir
    885   directive :mydirective do
    886     arg :if, non_null(:boolean), description: "Skipped when true."
    887     on [:field, :fragment_spread, :inline_fragment]
    888 
    889     expand fn
    890       %{if: true}, node ->
    891         Blueprint.put_flag(node, :skip, __MODULE__)
    892       _, node ->
    893         node
    894     end
    895   end
    896   ```
    897 
    898   ## Applying a type system directive
    899   Directives can be applied in your schema. E.g. by default the `@deprecated`
    900   directive is available to be applied to fields and enum values.
    901 
    902   You can define your own type system directives. See `Absinthe.Schema.Prototype`
    903   for more information.
    904 
    905   ### Placement
    906 
    907   #{Utils.placement_docs(@placement, :applied_directive)}
    908 
    909   ### Examples
    910 
    911   When you have a type system directive named `:feature` it can be applied as
    912   follows:
    913 
    914   ```elixir
    915   object :post do
    916     directive :feature, name: ":object"
    917 
    918     field :name, :string do
    919       deprecate "Bye"
    920     end
    921   end
    922 
    923   scalar :sweet_scalar do
    924     directive :feature, name: ":scalar"
    925     parse &Function.identity/1
    926     serialize &Function.identity/1
    927   end
    928   ```
    929   """
    930   defmacro directive(identifier, attrs, do: block) when is_list(attrs) when not is_nil(block) do
    931     __CALLER__
    932     |> recordable!(:directive, @placement[:directive])
    933     |> record_directive!(identifier, attrs, block)
    934   end
    935 
    936   defmacro directive(identifier, do: block) when not is_nil(block) do
    937     __CALLER__
    938     |> recordable!(:directive, @placement[:directive])
    939     |> record_directive!(identifier, [], block)
    940   end
    941 
    942   defmacro directive(identifier, attrs) when is_list(attrs) do
    943     __CALLER__
    944     |> recordable!(:directive, @placement[:applied_directive])
    945     |> record_applied_directive!(identifier, attrs)
    946   end
    947 
    948   defmacro directive(identifier) do
    949     __CALLER__
    950     |> recordable!(:directive, @placement[:applied_directive])
    951     |> record_applied_directive!(identifier, [])
    952   end
    953 
    954   @placement {:on, [under: [:directive]]}
    955   @doc """
    956   Declare a directive as operating an a AST node type
    957 
    958   See `directive/2`
    959 
    960   ## Placement
    961 
    962   #{Utils.placement_docs(@placement)}
    963   """
    964   defmacro on(ast_node) do
    965     __CALLER__
    966     |> recordable!(:on, @placement[:on])
    967     |> record_locations!(ast_node)
    968   end
    969 
    970   @placement {:expand, [under: [:directive]]}
    971   @doc """
    972   Define the expansion for a directive
    973 
    974   ## Placement
    975 
    976   #{Utils.placement_docs(@placement)}
    977   """
    978   defmacro expand(func_ast) do
    979     __CALLER__
    980     |> recordable!(:expand, @placement[:expand])
    981     |> record_expand!(func_ast)
    982   end
    983 
    984   @placement {:repeatable, [under: [:directive]]}
    985   @doc """
    986   Set whether the directive can be applied multiple times
    987   an entity.
    988 
    989   If omitted, defaults to `false`
    990 
    991   ## Placement
    992 
    993   #{Utils.placement_docs(@placement)}
    994   """
    995   defmacro repeatable(bool) do
    996     __CALLER__
    997     |> recordable!(:repeatable, @placement[:repeatable])
    998     |> record_repeatable!(bool)
    999   end
   1000 
   1001   # INPUT OBJECTS
   1002 
   1003   @placement {:input_object, [toplevel: true]}
   1004   @doc """
   1005   Defines an input object
   1006 
   1007   See `Absinthe.Type.InputObject`
   1008 
   1009   ## Placement
   1010 
   1011   #{Utils.placement_docs(@placement)}
   1012 
   1013   ## Examples
   1014   ```
   1015   input_object :contact_input do
   1016     field :email, non_null(:string)
   1017   end
   1018   ```
   1019   """
   1020   defmacro input_object(identifier, attrs \\ [], do: block) do
   1021     __CALLER__
   1022     |> recordable!(:input_object, @placement[:input_object])
   1023     |> record!(
   1024       Schema.InputObjectTypeDefinition,
   1025       identifier,
   1026       attrs |> Keyword.update(:description, nil, &wrap_in_unquote/1),
   1027       block
   1028     )
   1029   end
   1030 
   1031   # UNIONS
   1032 
   1033   @placement {:union, [toplevel: true]}
   1034   @doc """
   1035   Defines a union type
   1036 
   1037   See `Absinthe.Type.Union`
   1038 
   1039   ## Placement
   1040 
   1041   #{Utils.placement_docs(@placement)}
   1042 
   1043   ## Examples
   1044   ```
   1045   union :search_result do
   1046     description "A search result"
   1047 
   1048     types [:person, :business]
   1049     resolve_type fn
   1050       %Person{}, _ -> :person
   1051       %Business{}, _ -> :business
   1052     end
   1053   end
   1054   ```
   1055   """
   1056   defmacro union(identifier, attrs \\ [], do: block) do
   1057     __CALLER__
   1058     |> recordable!(:union, @placement[:union])
   1059     |> record!(
   1060       Schema.UnionTypeDefinition,
   1061       identifier,
   1062       attrs |> Keyword.update(:description, nil, &wrap_in_unquote/1),
   1063       block
   1064     )
   1065   end
   1066 
   1067   @placement {:types, [under: [:union]]}
   1068   @doc """
   1069   Defines the types possible under a union type
   1070 
   1071   See `union/3`
   1072 
   1073   ## Placement
   1074 
   1075   #{Utils.placement_docs(@placement)}
   1076   """
   1077   defmacro types(types) do
   1078     __CALLER__
   1079     |> recordable!(:types, @placement[:types])
   1080     |> record_types!(types)
   1081   end
   1082 
   1083   # ENUMS
   1084 
   1085   @placement {:enum, [toplevel: true]}
   1086   @doc """
   1087   Defines an enum type
   1088 
   1089   ## Placement
   1090 
   1091   #{Utils.placement_docs(@placement)}
   1092 
   1093   ## Examples
   1094 
   1095   Handling `RED`, `GREEN`, `BLUE` values from the query document:
   1096 
   1097   ```
   1098   enum :color do
   1099     value :red
   1100     value :green
   1101     value :blue
   1102   end
   1103   ```
   1104 
   1105   A given query document might look like:
   1106 
   1107   ```graphql
   1108   {
   1109     foo(color: RED)
   1110   }
   1111   ```
   1112 
   1113   Internally you would get an argument in elixir that looks like:
   1114 
   1115   ```elixir
   1116   %{color: :red}
   1117   ```
   1118 
   1119   If your return value is an enum, it will get serialized out as:
   1120 
   1121   ```json
   1122   {"color": "RED"}
   1123   ```
   1124 
   1125   You can provide custom value mappings. Here we use `r`, `g`, `b` values:
   1126 
   1127   ```
   1128   enum :color do
   1129     value :red, as: "r"
   1130     value :green, as: "g"
   1131     value :blue, as: "b"
   1132   end
   1133   ```
   1134 
   1135   """
   1136   defmacro enum(identifier, attrs, do: block) do
   1137     attrs = handle_enum_attrs(attrs, __CALLER__)
   1138 
   1139     __CALLER__
   1140     |> recordable!(:enum, @placement[:enum])
   1141     |> record!(Schema.EnumTypeDefinition, identifier, attrs, block)
   1142   end
   1143 
   1144   @doc """
   1145   Defines an enum type
   1146 
   1147   See `enum/3`
   1148   """
   1149   defmacro enum(identifier, do: block) do
   1150     __CALLER__
   1151     |> recordable!(:enum, @placement[:enum])
   1152     |> record!(Schema.EnumTypeDefinition, identifier, [], block)
   1153   end
   1154 
   1155   defmacro enum(identifier, attrs) do
   1156     attrs = handle_enum_attrs(attrs, __CALLER__)
   1157 
   1158     __CALLER__
   1159     |> recordable!(:enum, @placement[:enum])
   1160     |> record!(Schema.EnumTypeDefinition, identifier, attrs, [])
   1161   end
   1162 
   1163   defp handle_enum_attrs(attrs, env) do
   1164     attrs
   1165     |> expand_ast(env)
   1166     |> Keyword.update(:values, [], &[wrap_in_unquote(&1)])
   1167     |> Keyword.update(:description, nil, &wrap_in_unquote/1)
   1168   end
   1169 
   1170   @placement {:value, [under: [:enum]]}
   1171   @doc """
   1172   Defines a value possible under an enum type
   1173 
   1174   See `enum/3`
   1175 
   1176   ## Placement
   1177 
   1178   #{Utils.placement_docs(@placement)}
   1179   """
   1180   defmacro value(identifier, raw_attrs \\ []) do
   1181     attrs = expand_ast(raw_attrs, __CALLER__)
   1182 
   1183     __CALLER__
   1184     |> recordable!(:value, @placement[:value])
   1185     |> record_value!(identifier, attrs)
   1186   end
   1187 
   1188   # GENERAL ATTRIBUTES
   1189 
   1190   @placement {:description, [toplevel: false]}
   1191   @doc """
   1192   Defines a description
   1193 
   1194   This macro adds a description to any other macro which takes a block.
   1195 
   1196   Note that you can also specify a description by using `@desc` above any item
   1197   that can take a description attribute.
   1198 
   1199   ## Placement
   1200 
   1201   #{Utils.placement_docs(@placement)}
   1202   """
   1203   defmacro description(text) do
   1204     __CALLER__
   1205     |> recordable!(:description, @placement[:description])
   1206     |> record_description!(text)
   1207   end
   1208 
   1209   # TYPE UTILITIES
   1210   @doc """
   1211   Marks a type reference as non null
   1212 
   1213   See `field/3` for examples
   1214   """
   1215 
   1216   defmacro non_null({:non_null, _, _}) do
   1217     raise Absinthe.Schema.Notation.Error,
   1218           "Invalid schema notation: `non_null` must not be nested"
   1219   end
   1220 
   1221   defmacro non_null(type) do
   1222     %Absinthe.Blueprint.TypeReference.NonNull{of_type: expand_ast(type, __CALLER__)}
   1223   end
   1224 
   1225   @doc """
   1226   Marks a type reference as a list of the given type
   1227 
   1228   See `field/3` for examples
   1229   """
   1230   defmacro list_of(type) do
   1231     %Absinthe.Blueprint.TypeReference.List{of_type: expand_ast(type, __CALLER__)}
   1232   end
   1233 
   1234   @placement {:import_fields, [under: [:input_object, :interface, :object]]}
   1235   @doc """
   1236   Import fields from another object
   1237 
   1238   ## Example
   1239   ```
   1240   object :news_queries do
   1241     field :all_links, list_of(:link)
   1242     field :main_story, :link
   1243   end
   1244 
   1245   object :admin_queries do
   1246     field :users, list_of(:user)
   1247     field :pending_posts, list_of(:post)
   1248   end
   1249 
   1250   query do
   1251     import_fields :news_queries
   1252     import_fields :admin_queries
   1253   end
   1254   ```
   1255 
   1256   Import fields can also be used on objects created inside other modules that you
   1257   have used import_types on.
   1258 
   1259   ```
   1260   defmodule MyApp.Schema.NewsTypes do
   1261     use Absinthe.Schema.Notation
   1262 
   1263     object :news_queries do
   1264       field :all_links, list_of(:link)
   1265       field :main_story, :link
   1266     end
   1267   end
   1268   defmodule MyApp.Schema.Schema do
   1269     use Absinthe.Schema
   1270 
   1271     import_types MyApp.Schema.NewsTypes
   1272 
   1273     query do
   1274       import_fields :news_queries
   1275       # ...
   1276     end
   1277   end
   1278   ```
   1279   """
   1280   defmacro import_fields(source_criteria, opts \\ []) do
   1281     source_criteria = expand_ast(source_criteria, __CALLER__)
   1282 
   1283     put_attr(__CALLER__.module, {:import_fields, {source_criteria, opts}})
   1284   end
   1285 
   1286   @placement {:import_types, [toplevel: true]}
   1287   @doc """
   1288   Import types from another module
   1289 
   1290   Very frequently your schema module will simply have the `query` and `mutation`
   1291   blocks, and you'll want to break out your other types into other modules. This
   1292   macro imports those types for use the current module.
   1293 
   1294   To selectively import types you can use the `:only` and `:except` opts.
   1295 
   1296   ## Placement
   1297 
   1298   #{Utils.placement_docs(@placement)}
   1299 
   1300   ## Examples
   1301   ```
   1302   import_types MyApp.Schema.Types
   1303 
   1304   import_types MyApp.Schema.Types.{TypesA, TypesB}
   1305 
   1306   import_types MyApp.Schema.Types, only: [:foo]
   1307 
   1308   import_types MyApp.Schema.Types, except: [:bar]
   1309   ```
   1310   """
   1311   defmacro import_types(type_module_ast, opts \\ []) do
   1312     env = __CALLER__
   1313 
   1314     type_module_ast
   1315     |> Macro.expand(env)
   1316     |> do_import_types(env, opts)
   1317   end
   1318 
   1319   @placement {:import_sdl, [toplevel: true]}
   1320   @type import_sdl_option :: {:path, String.t() | Macro.t()}
   1321   @doc """
   1322   Import types defined using the Schema Definition Language (SDL).
   1323 
   1324   TODO: Explain handlers
   1325 
   1326   ## Placement
   1327 
   1328   #{Utils.placement_docs(@placement)}
   1329 
   1330   ## Examples
   1331 
   1332   Directly embedded SDL:
   1333 
   1334   ```
   1335   import_sdl \"""
   1336   type Query {
   1337     posts: [Post]
   1338   }
   1339 
   1340   type Post {
   1341     title: String!
   1342     body: String!
   1343   }
   1344   \"""
   1345   ```
   1346 
   1347   Loaded from a file location (supporting recompilation on change):
   1348 
   1349   ```
   1350   import_sdl path: "/path/to/sdl.graphql"
   1351   ```
   1352 
   1353   TODO: Example for dynamic loading during init
   1354   """
   1355   @spec import_sdl([import_sdl_option(), ...]) :: Macro.t()
   1356   defmacro import_sdl(opts) when is_list(opts) do
   1357     __CALLER__
   1358     |> do_import_sdl(nil, opts)
   1359   end
   1360 
   1361   @spec import_sdl(String.t() | Macro.t(), [import_sdl_option()]) :: Macro.t()
   1362   defmacro import_sdl(sdl, opts \\ []) do
   1363     __CALLER__
   1364     |> do_import_sdl(sdl, opts)
   1365   end
   1366 
   1367   defmacro values(values) do
   1368     __CALLER__
   1369     |> record_values!(values)
   1370   end
   1371 
   1372   ### Recorders ###
   1373   #################
   1374 
   1375   @scoped_types [
   1376     Schema.ObjectTypeDefinition,
   1377     Schema.FieldDefinition,
   1378     Schema.ScalarTypeDefinition,
   1379     Schema.EnumTypeDefinition,
   1380     Schema.EnumValueDefinition,
   1381     Schema.InputObjectTypeDefinition,
   1382     Schema.InputValueDefinition,
   1383     Schema.UnionTypeDefinition,
   1384     Schema.InterfaceTypeDefinition,
   1385     Schema.DirectiveDefinition
   1386   ]
   1387 
   1388   def record!(env, type, identifier, attrs, block) when type in @scoped_types do
   1389     attrs = expand_ast(attrs, env)
   1390     scoped_def(env, type, identifier, attrs, block)
   1391   end
   1392 
   1393   defp build_directives(attrs) do
   1394     if attrs[:deprecate] do
   1395       directive = {:deprecated, reason(attrs[:deprecate])}
   1396 
   1397       directives = Keyword.get(attrs, :directives, [])
   1398       [directive | directives]
   1399     else
   1400       Keyword.get(attrs, :directives, [])
   1401     end
   1402   end
   1403 
   1404   defp reason(true), do: []
   1405   defp reason(msg) when is_binary(msg), do: [reason: msg]
   1406   defp reason(msg), do: raise(ArgumentError, "Invalid reason: #{msg}")
   1407 
   1408   def handle_arg_attrs(identifier, type, raw_attrs) do
   1409     block =
   1410       for {identifier, args} <- build_directives(raw_attrs) do
   1411         quote do
   1412           directive(unquote(identifier), unquote(args))
   1413         end
   1414       end
   1415 
   1416     attrs =
   1417       raw_attrs
   1418       |> Keyword.put_new(:name, to_string(identifier))
   1419       |> Keyword.put_new(:type, type)
   1420       |> Keyword.delete(:directives)
   1421       |> Keyword.delete(:deprecate)
   1422       |> Keyword.update(:description, nil, &wrap_in_unquote/1)
   1423       |> Keyword.update(:default_value, nil, &wrap_in_unquote/1)
   1424 
   1425     {attrs, block}
   1426   end
   1427 
   1428   @doc false
   1429   # Record a directive expand function in the current scope
   1430   def record_expand!(env, func_ast) do
   1431     put_attr(env.module, {:expand, func_ast})
   1432   end
   1433 
   1434   @doc false
   1435   def record_repeatable!(env, bool) do
   1436     put_attr(env.module, {:repeatable, bool})
   1437   end
   1438 
   1439   @doc false
   1440   # Record directive AST nodes in the current scope
   1441   def record_locations!(env, locations) do
   1442     locations = expand_ast(locations, env)
   1443     put_attr(env.module, {:locations, List.wrap(locations)})
   1444   end
   1445 
   1446   @doc false
   1447   # Record a directive
   1448   def record_directive!(env, identifier, attrs, block) do
   1449     attrs =
   1450       attrs
   1451       |> Keyword.put(:identifier, identifier)
   1452       |> Keyword.put_new(:name, to_string(identifier))
   1453       |> Keyword.update(:description, nil, &wrap_in_unquote/1)
   1454 
   1455     scoped_def(env, Schema.DirectiveDefinition, identifier, attrs, block)
   1456   end
   1457 
   1458   @doc false
   1459   # Record a parse function in the current scope
   1460   def record_parse!(env, fun_ast) do
   1461     put_attr(env.module, {:parse, fun_ast})
   1462   end
   1463 
   1464   @doc false
   1465   # Record private values
   1466   def record_private!(env, owner, keyword_list) when is_list(keyword_list) do
   1467     keyword_list = expand_ast(keyword_list, env)
   1468 
   1469     put_attr(env.module, {:__private__, [{owner, keyword_list}]})
   1470   end
   1471 
   1472   @doc false
   1473   # Record a serialize function in the current scope
   1474   def record_serialize!(env, fun_ast) do
   1475     put_attr(env.module, {:serialize, fun_ast})
   1476   end
   1477 
   1478   @doc false
   1479   # Record a type checker in the current scope
   1480   def record_is_type_of!(env, func_ast) do
   1481     put_attr(env.module, {:is_type_of, func_ast})
   1482     # :ok
   1483   end
   1484 
   1485   @doc false
   1486   # Record a complexity analyzer in the current scope
   1487   def record_complexity!(env, func_ast) do
   1488     put_attr(env.module, {:complexity, func_ast})
   1489     # :ok
   1490   end
   1491 
   1492   @doc false
   1493   # Record a type resolver in the current scope
   1494   def record_resolve_type!(env, func_ast) do
   1495     put_attr(env.module, {:resolve_type, func_ast})
   1496     # :ok
   1497   end
   1498 
   1499   @doc false
   1500   # Record an implemented interface in the current scope
   1501   def record_interface!(env, identifier) do
   1502     put_attr(env.module, {:interface, identifier})
   1503     # Scope.put_attribute(env.module, :interfaces, identifier, accumulate: true)
   1504     # Scope.recorded!(env.module, :attr, :interface)
   1505     # :ok
   1506   end
   1507 
   1508   @doc false
   1509   # Record a deprecation in the current scope
   1510   def record_deprecate!(env, msg) do
   1511     msg = expand_ast(msg, env)
   1512 
   1513     record_applied_directive!(env, :deprecated, reason: msg)
   1514   end
   1515 
   1516   @doc false
   1517   # Record a list of implemented interfaces in the current scope
   1518   def record_interfaces!(env, ifaces) do
   1519     Enum.each(ifaces, &record_interface!(env, &1))
   1520   end
   1521 
   1522   @doc false
   1523   # Record a list of member types for a union in the current scope
   1524   def record_types!(env, types) do
   1525     put_attr(env.module, {:types, types})
   1526   end
   1527 
   1528   @doc false
   1529   # Record an enum type
   1530   def record_enum!(env, identifier, attrs, block) do
   1531     attrs = expand_ast(attrs, env)
   1532     attrs = Keyword.put(attrs, :identifier, identifier)
   1533     scoped_def(env, :enum, identifier, attrs, block)
   1534   end
   1535 
   1536   @doc false
   1537   # Record a description in the current scope
   1538   def record_description!(env, text_block) do
   1539     text = wrap_in_unquote(text_block)
   1540 
   1541     put_attr(env.module, {:desc, text})
   1542   end
   1543 
   1544   @doc false
   1545   # Record a scalar
   1546   def record_scalar!(env, identifier, attrs, block_or_nil) do
   1547     record!(
   1548       env,
   1549       Schema.ScalarTypeDefinition,
   1550       identifier,
   1551       attrs |> Keyword.update(:description, nil, &wrap_in_unquote/1),
   1552       block_or_nil
   1553     )
   1554   end
   1555 
   1556   def handle_enum_value_attrs(identifier, raw_attrs, env) do
   1557     value = Keyword.get(raw_attrs, :as, identifier)
   1558 
   1559     block =
   1560       for {identifier, args} <- build_directives(raw_attrs) do
   1561         quote do
   1562           directive(unquote(identifier), unquote(args))
   1563         end
   1564       end
   1565 
   1566     attrs =
   1567       raw_attrs
   1568       |> expand_ast(env)
   1569       |> Keyword.delete(:directives)
   1570       |> Keyword.put(:identifier, identifier)
   1571       |> Keyword.put(:value, wrap_in_unquote(value))
   1572       |> Keyword.put_new(:name, String.upcase(to_string(identifier)))
   1573       |> Keyword.delete(:as)
   1574       |> Keyword.delete(:deprecate)
   1575       |> Keyword.update(:description, nil, &wrap_in_unquote/1)
   1576 
   1577     {attrs, block}
   1578   end
   1579 
   1580   @doc false
   1581   # Record an enum value in the current scope
   1582   def record_value!(env, identifier, raw_attrs) do
   1583     {attrs, block} = handle_enum_value_attrs(identifier, raw_attrs, env)
   1584     record!(env, Schema.EnumValueDefinition, identifier, attrs, block)
   1585   end
   1586 
   1587   @doc false
   1588   # Record an enum value in the current scope
   1589   def record_values!(env, values) do
   1590     values =
   1591       values
   1592       |> expand_ast(env)
   1593       |> wrap_in_unquote
   1594 
   1595     put_attr(env.module, {:values, values})
   1596   end
   1597 
   1598   def record_config!(env, fun_ast) do
   1599     put_attr(env.module, {:config, fun_ast})
   1600   end
   1601 
   1602   def record_trigger!(env, mutations, attrs) do
   1603     for mutation <- mutations do
   1604       put_attr(env.module, {:trigger, {mutation, attrs}})
   1605     end
   1606   end
   1607 
   1608   def record_applied_directive!(env, name, attrs) do
   1609     name = Atom.to_string(name)
   1610 
   1611     attrs =
   1612       attrs
   1613       |> expand_ast(env)
   1614       |> build_directive_arguments(env)
   1615       |> Keyword.put(:name, name)
   1616       |> put_reference(env)
   1617 
   1618     directive = struct!(Absinthe.Blueprint.Directive, attrs)
   1619     put_attr(env.module, {:directive, directive})
   1620   end
   1621 
   1622   defp build_directive_arguments(attrs, env) do
   1623     arguments =
   1624       attrs
   1625       |> Enum.map(fn {name, value} ->
   1626         value = expand_ast(value, env)
   1627 
   1628         attrs = [
   1629           name: Atom.to_string(name),
   1630           value: value,
   1631           input_value: Absinthe.Blueprint.Input.Value.build(value),
   1632           source_location: Absinthe.Blueprint.SourceLocation.at(env.line, 0)
   1633         ]
   1634 
   1635         struct!(Absinthe.Blueprint.Input.Argument, attrs)
   1636       end)
   1637 
   1638     [arguments: arguments]
   1639   end
   1640 
   1641   def record_middleware!(env, new_middleware, opts) do
   1642     new_middleware =
   1643       case expand_ast(new_middleware, env) do
   1644         {module, fun} ->
   1645           {:{}, [], [{module, fun}, opts]}
   1646 
   1647         atom when is_atom(atom) ->
   1648           case Atom.to_string(atom) do
   1649             "Elixir." <> _ ->
   1650               {:{}, [], [{atom, :call}, opts]}
   1651 
   1652             _ ->
   1653               {:{}, [], [{env.module, atom}, opts]}
   1654           end
   1655 
   1656         val ->
   1657           val
   1658       end
   1659 
   1660     put_attr(env.module, {:middleware, [new_middleware]})
   1661   end
   1662 
   1663   # We wrap the value (from the user) in an `unquote` call, so that when the schema `blueprint` is
   1664   # placed into `__absinthe_blueprint__` via `unquote(Macro.escape(blueprint, unquote: true))` the
   1665   # value gets unquoted. This allows us to evaluate function calls in the scope of the schema
   1666   # module.
   1667   defp wrap_in_unquote(value) do
   1668     {:unquote, [], [value]}
   1669   end
   1670 
   1671   # ------------------------------
   1672 
   1673   @doc false
   1674   defmacro pop() do
   1675     module = __CALLER__.module
   1676     popped = pop_stack(module, :absinthe_scope_stack_stash)
   1677     push_stack(module, :absinthe_scope_stack, popped)
   1678     put_attr(__CALLER__.module, :pop)
   1679   end
   1680 
   1681   @doc false
   1682   defmacro stash() do
   1683     module = __CALLER__.module
   1684     popped = pop_stack(module, :absinthe_scope_stack)
   1685     push_stack(module, :absinthe_scope_stack_stash, popped)
   1686     put_attr(module, :stash)
   1687   end
   1688 
   1689   @doc false
   1690   defmacro close_scope() do
   1691     put_attr(__CALLER__.module, :close)
   1692     pop_stack(__CALLER__.module, :absinthe_scope_stack)
   1693   end
   1694 
   1695   def put_reference(attrs, env) do
   1696     Keyword.put(attrs, :__reference__, build_reference(env))
   1697   end
   1698 
   1699   def build_reference(env) do
   1700     %{
   1701       module: env.module,
   1702       location: %{
   1703         file: env.file,
   1704         line: env.line
   1705       }
   1706     }
   1707   end
   1708 
   1709   @scope_map %{
   1710     Schema.ObjectTypeDefinition => :object,
   1711     Schema.FieldDefinition => :field,
   1712     Schema.ScalarTypeDefinition => :scalar,
   1713     Schema.EnumTypeDefinition => :enum,
   1714     Schema.EnumValueDefinition => :value,
   1715     Schema.InputObjectTypeDefinition => :input_object,
   1716     Schema.InputValueDefinition => :arg,
   1717     Schema.UnionTypeDefinition => :union,
   1718     Schema.InterfaceTypeDefinition => :interface,
   1719     Schema.DirectiveDefinition => :directive
   1720   }
   1721   defp scoped_def(caller, type, identifier, attrs, body) do
   1722     attrs =
   1723       attrs
   1724       |> Keyword.put(:identifier, identifier)
   1725       |> Keyword.put_new(:name, default_name(type, identifier))
   1726       |> Keyword.put(:module, caller.module)
   1727       |> put_reference(caller)
   1728 
   1729     definition = struct!(type, attrs)
   1730 
   1731     ref = put_attr(caller.module, definition)
   1732 
   1733     push_stack(caller.module, :absinthe_scope_stack, Map.fetch!(@scope_map, type))
   1734 
   1735     [
   1736       get_desc(ref),
   1737       body,
   1738       quote(do: unquote(__MODULE__).close_scope())
   1739     ]
   1740   end
   1741 
   1742   defp get_desc(ref) do
   1743     quote do
   1744       unquote(__MODULE__).put_desc(__MODULE__, unquote(ref))
   1745     end
   1746   end
   1747 
   1748   defp push_stack(module, key, val) do
   1749     stack = Module.get_attribute(module, key)
   1750     stack = [val | stack]
   1751     Module.put_attribute(module, key, stack)
   1752   end
   1753 
   1754   defp pop_stack(module, key) do
   1755     [popped | stack] = Module.get_attribute(module, key)
   1756     Module.put_attribute(module, key, stack)
   1757     popped
   1758   end
   1759 
   1760   def put_attr(module, thing) do
   1761     ref = :erlang.unique_integer()
   1762     Module.put_attribute(module, :absinthe_blueprint, {ref, thing})
   1763     ref
   1764   end
   1765 
   1766   defp default_name(Schema.FieldDefinition, identifier) do
   1767     identifier
   1768     |> Atom.to_string()
   1769   end
   1770 
   1771   defp default_name(_, identifier) do
   1772     identifier
   1773     |> Atom.to_string()
   1774     |> Absinthe.Utils.camelize()
   1775   end
   1776 
   1777   defp do_import_types({{:., _, [{:__MODULE__, _, _}, :{}]}, _, modules_ast_list}, env, opts) do
   1778     for {_, _, leaf} <- modules_ast_list do
   1779       type_module = Module.concat([env.module | leaf])
   1780 
   1781       do_import_types(type_module, env, opts)
   1782     end
   1783   end
   1784 
   1785   defp do_import_types(
   1786          {{:., _, [{:__aliases__, _, [{:__MODULE__, _, _} | tail]}, :{}]}, _, modules_ast_list},
   1787          env,
   1788          opts
   1789        ) do
   1790     root_module = Module.concat([env.module | tail])
   1791 
   1792     for {_, _, leaf} <- modules_ast_list do
   1793       type_module = Module.concat([root_module | leaf])
   1794 
   1795       do_import_types(type_module, env, opts)
   1796     end
   1797   end
   1798 
   1799   defp do_import_types({{:., _, [{:__aliases__, _, root}, :{}]}, _, modules_ast_list}, env, opts) do
   1800     root_module = Module.concat(root)
   1801     root_module_with_alias = Keyword.get(env.aliases, root_module, root_module)
   1802 
   1803     for {_, _, leaf} <- modules_ast_list do
   1804       type_module = Module.concat([root_module_with_alias | leaf])
   1805 
   1806       do_import_types(type_module, env, opts)
   1807     end
   1808   end
   1809 
   1810   defp do_import_types(module, env, opts) do
   1811     Module.put_attribute(env.module, :__absinthe_type_imports__, [
   1812       {module, opts} | Module.get_attribute(env.module, :__absinthe_type_imports__) || []
   1813     ])
   1814 
   1815     []
   1816   end
   1817 
   1818   @spec do_import_sdl(Macro.Env.t(), nil | String.t() | Macro.t(), [import_sdl_option()]) ::
   1819           Macro.t()
   1820   defp do_import_sdl(env, nil, opts) do
   1821     case Keyword.fetch(opts, :path) do
   1822       {:ok, path} ->
   1823         [
   1824           quote do
   1825             @__absinthe_import_sdl_path__ unquote(path)
   1826           end,
   1827           do_import_sdl(
   1828             env,
   1829             quote do
   1830               File.read!(@__absinthe_import_sdl_path__)
   1831             end,
   1832             opts
   1833           ),
   1834           quote do
   1835             @external_resource @__absinthe_import_sdl_path__
   1836           end
   1837         ]
   1838 
   1839       :error ->
   1840         raise Absinthe.Schema.Notation.Error,
   1841               "Must provide `:path` option to `import_sdl` unless passing a raw SDL string as the first argument"
   1842     end
   1843   end
   1844 
   1845   defp do_import_sdl(env, sdl, opts) do
   1846     ref = build_reference(env)
   1847 
   1848     quote do
   1849       with {:ok, definitions} <-
   1850              unquote(__MODULE__).SDL.parse(
   1851                unquote(sdl),
   1852                __MODULE__,
   1853                unquote(Macro.escape(ref)),
   1854                unquote(Macro.escape(opts))
   1855              ) do
   1856         @__absinthe_sdl_definitions__ definitions ++
   1857                                         (Module.get_attribute(
   1858                                            __MODULE__,
   1859                                            :__absinthe_sdl_definitions__
   1860                                          ) || [])
   1861       else
   1862         {:error, error} ->
   1863           raise Absinthe.Schema.Notation.Error, "`import_sdl` could not parse SDL:\n#{error}"
   1864       end
   1865     end
   1866   end
   1867 
   1868   def put_desc(module, ref) do
   1869     Module.put_attribute(module, :absinthe_desc, {ref, Module.get_attribute(module, :desc)})
   1870     Module.put_attribute(module, :desc, nil)
   1871   end
   1872 
   1873   def noop(_desc) do
   1874     :ok
   1875   end
   1876 
   1877   defmacro __before_compile__(env) do
   1878     module_attribute_descs =
   1879       env.module
   1880       |> Module.get_attribute(:absinthe_desc)
   1881       |> Map.new()
   1882 
   1883     attrs =
   1884       env.module
   1885       |> Module.get_attribute(:absinthe_blueprint)
   1886       |> List.insert_at(0, :close)
   1887       |> reverse_with_descs(module_attribute_descs)
   1888 
   1889     imports =
   1890       (Module.get_attribute(env.module, :__absinthe_type_imports__) || [])
   1891       |> Enum.uniq()
   1892       |> Enum.map(fn
   1893         module when is_atom(module) -> {module, []}
   1894         other -> other
   1895       end)
   1896 
   1897     schema_def = %Schema.SchemaDefinition{
   1898       imports: imports,
   1899       module: env.module,
   1900       __reference__: %{
   1901         location: %{file: env.file, line: 0}
   1902       }
   1903     }
   1904 
   1905     blueprint =
   1906       attrs
   1907       |> List.insert_at(1, schema_def)
   1908       |> Absinthe.Blueprint.Schema.build()
   1909 
   1910     # TODO: handle multiple schemas
   1911     [schema] = blueprint.schema_definitions
   1912 
   1913     {schema, functions} = lift_functions(schema, env.module)
   1914 
   1915     sdl_definitions =
   1916       (Module.get_attribute(env.module, :__absinthe_sdl_definitions__) || [])
   1917       |> List.flatten()
   1918       |> Enum.map(fn definition ->
   1919         Absinthe.Blueprint.prewalk(definition, fn
   1920           %{module: _} = node ->
   1921             %{node | module: env.module}
   1922 
   1923           node ->
   1924             node
   1925         end)
   1926       end)
   1927 
   1928     {sdl_directive_definitions, sdl_type_definitions} =
   1929       Enum.split_with(sdl_definitions, fn
   1930         %Absinthe.Blueprint.Schema.DirectiveDefinition{} ->
   1931           true
   1932 
   1933         _ ->
   1934           false
   1935       end)
   1936 
   1937     schema =
   1938       schema
   1939       |> Map.update!(:type_definitions, &(sdl_type_definitions ++ &1))
   1940       |> Map.update!(:directive_definitions, &(sdl_directive_definitions ++ &1))
   1941 
   1942     blueprint = %{blueprint | schema_definitions: [schema]}
   1943 
   1944     quote do
   1945       unquote(__MODULE__).noop(@desc)
   1946 
   1947       def __absinthe_blueprint__ do
   1948         unquote(Macro.escape(blueprint, unquote: true))
   1949       end
   1950 
   1951       unquote_splicing(functions)
   1952     end
   1953   end
   1954 
   1955   def lift_functions(schema, origin) do
   1956     Absinthe.Blueprint.prewalk(schema, [], &lift_functions(&1, &2, origin))
   1957   end
   1958 
   1959   def lift_functions(node, acc, origin) do
   1960     {node, ast} = functions_for_type(node, origin)
   1961     {node, ast ++ acc}
   1962   end
   1963 
   1964   defp functions_for_type(%Schema.FieldDefinition{} = type, origin) do
   1965     grab_functions(
   1966       origin,
   1967       type,
   1968       {Schema.FieldDefinition, type.function_ref},
   1969       Schema.functions(Schema.FieldDefinition)
   1970     )
   1971   end
   1972 
   1973   defp functions_for_type(%module{identifier: identifier} = type, origin) do
   1974     grab_functions(origin, type, {module, identifier}, Schema.functions(module))
   1975   end
   1976 
   1977   defp functions_for_type(type, _) do
   1978     {type, []}
   1979   end
   1980 
   1981   def grab_functions(origin, type, identifier, attrs) do
   1982     {ast, type} =
   1983       Enum.flat_map_reduce(attrs, type, fn attr, type ->
   1984         value = Map.fetch!(type, attr)
   1985 
   1986         ast =
   1987           quote do
   1988             def __absinthe_function__(unquote(identifier), unquote(attr)) do
   1989               unquote(value)
   1990             end
   1991           end
   1992 
   1993         ref = {:ref, origin, identifier}
   1994 
   1995         type =
   1996           Map.update!(type, attr, fn
   1997             value when is_list(value) ->
   1998               [ref]
   1999 
   2000             _ ->
   2001               ref
   2002           end)
   2003 
   2004         {[ast], type}
   2005       end)
   2006 
   2007     {type, ast}
   2008   end
   2009 
   2010   @doc false
   2011   def __ensure_middleware__([], _field, %{identifier: :subscription}) do
   2012     [Absinthe.Middleware.PassParent]
   2013   end
   2014 
   2015   def __ensure_middleware__([], %{identifier: identifier}, _) do
   2016     [{Absinthe.Middleware.MapGet, identifier}]
   2017   end
   2018 
   2019   # Don't install Telemetry middleware for Introspection fields
   2020   @introspection [Absinthe.Phase.Schema.Introspection, Absinthe.Type.BuiltIns.Introspection]
   2021   def __ensure_middleware__(middleware, %{definition: definition}, _object)
   2022       when definition in @introspection do
   2023     middleware
   2024   end
   2025 
   2026   # Install Telemetry middleware
   2027   def __ensure_middleware__(middleware, _field, _object) do
   2028     [{Absinthe.Middleware.Telemetry, []} | middleware]
   2029   end
   2030 
   2031   defp reverse_with_descs(attrs, descs, acc \\ [])
   2032 
   2033   defp reverse_with_descs([], _descs, acc), do: acc
   2034 
   2035   defp reverse_with_descs([{ref, attr} | rest], descs, acc) do
   2036     if desc = Map.get(descs, ref) do
   2037       reverse_with_descs(rest, descs, [attr, {:desc, desc} | acc])
   2038     else
   2039       reverse_with_descs(rest, descs, [attr | acc])
   2040     end
   2041   end
   2042 
   2043   defp reverse_with_descs([attr | rest], descs, acc) do
   2044     reverse_with_descs(rest, descs, [attr | acc])
   2045   end
   2046 
   2047   defp expand_ast(ast, env) do
   2048     Macro.prewalk(ast, fn
   2049       # We don't want to expand `@bla` into `Module.get_attribute(module, @bla)` because this
   2050       # function call will fail if the module is already compiled. Remember that the ast gets put
   2051       # into a generated `__absinthe_blueprint__` function which is called at "__after_compile__"
   2052       # time. This will be after a module has been compiled if there are multiple modules in the
   2053       # schema (in the case of an `import_types`).
   2054       #
   2055       # Also see test "test/absinthe/type/import_types_test.exs"
   2056       # "__absinthe_blueprint__ is callable at runtime even if there is a module attribute"
   2057       # and it's comment for more information
   2058       {:@, _, _} = node ->
   2059         node
   2060 
   2061       {_, _, _} = node ->
   2062         Macro.expand(node, env)
   2063 
   2064       node ->
   2065         node
   2066     end)
   2067   end
   2068 
   2069   @doc false
   2070   # Ensure the provided operation can be recorded in the current environment,
   2071   # in the current scope context
   2072   def recordable!(env, usage, placement) do
   2073     [scope | _] = Module.get_attribute(env.module, :absinthe_scope_stack)
   2074 
   2075     unless recordable?(placement, scope) do
   2076       raise Absinthe.Schema.Notation.Error, invalid_message(placement, usage, scope)
   2077     end
   2078 
   2079     env
   2080   end
   2081 
   2082   defp recordable?([under: under], scope), do: scope in under
   2083   defp recordable?([toplevel: true], scope), do: scope == :schema
   2084   defp recordable?([toplevel: false], scope), do: scope != :schema
   2085 
   2086   defp invalid_message([under: under], usage, scope) do
   2087     allowed = under |> Enum.map(&"`#{&1}`") |> Enum.join(", ")
   2088 
   2089     "Invalid schema notation: `#{usage}` must only be used within #{allowed}. #{used_in(scope)}"
   2090   end
   2091 
   2092   defp invalid_message([toplevel: true], usage, scope) do
   2093     "Invalid schema notation: `#{usage}` must only be used toplevel. #{used_in(scope)}"
   2094   end
   2095 
   2096   defp invalid_message([toplevel: false], usage, scope) do
   2097     "Invalid schema notation: `#{usage}` must not be used toplevel. #{used_in(scope)}"
   2098   end
   2099 
   2100   defp used_in(scope) do
   2101     scope = Atom.to_string(scope)
   2102     "Was used in `#{scope}`."
   2103   end
   2104 end