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