From b161700177297aae695b6cb2d0f697a575f67f5c Mon Sep 17 00:00:00 2001 From: Riccardo Binetti Date: Thu, 18 Jan 2024 01:16:16 +0100 Subject: [PATCH] feat: add support for relay global IDs Part 1 of what is needed for #99. The other half is implementing the node query. --- documentation/topics/relay.md | 11 +++ lib/api/api.ex | 17 ++-- lib/ash_graphql.ex | 34 +++++-- lib/graphql/resolver.ex | 43 +++++---- lib/resource/resource.ex | 98 +++++++++++++++----- test/relay_ids_test.exs | 111 +++++++++++++++++++++++ test/support/relay_ids/api.ex | 13 +++ test/support/relay_ids/registry.ex | 9 ++ test/support/relay_ids/resources/post.ex | 46 ++++++++++ test/support/relay_ids/resources/user.ex | 28 ++++++ test/support/relay_ids/schema.ex | 30 ++++++ 11 files changed, 383 insertions(+), 57 deletions(-) create mode 100644 test/relay_ids_test.exs create mode 100644 test/support/relay_ids/api.ex create mode 100644 test/support/relay_ids/registry.ex create mode 100644 test/support/relay_ids/resources/post.ex create mode 100644 test/support/relay_ids/resources/user.ex create mode 100644 test/support/relay_ids/schema.ex diff --git a/documentation/topics/relay.md b/documentation/topics/relay.md index 476b8009..938a6055 100644 --- a/documentation/topics/relay.md +++ b/documentation/topics/relay.md @@ -14,3 +14,14 @@ Use the following option when calling `use AshGraphql` ```elixir use AshGraphql, define_relay_types?: false ``` + +## Relay Global IDs + +Use the following option to generate Relay Global IDs (see +[here](https://relay.dev/graphql/objectidentification.htm)). + +```elixir +use AshGraphql, relay_ids?: true +``` + +This allows refetching a node using the `node` query and passing its global ID. diff --git a/lib/api/api.ex b/lib/api/api.ex index 6f678afe..5cc496c3 100644 --- a/lib/api/api.ex +++ b/lib/api/api.ex @@ -72,28 +72,33 @@ defmodule AshGraphql.Api do defdelegate debug?(api), to: AshGraphql.Api.Info @doc false - def queries(api, resources, action_middleware, schema) do - Enum.flat_map(resources, &AshGraphql.Resource.queries(api, &1, action_middleware, schema)) + def queries(api, resources, action_middleware, schema, relay_ids?) do + Enum.flat_map( + resources, + &AshGraphql.Resource.queries(api, &1, action_middleware, schema, relay_ids?) + ) end @doc false - def mutations(api, resources, action_middleware, schema) do + def mutations(api, resources, action_middleware, schema, relay_ids?) do resources |> Enum.filter(fn resource -> AshGraphql.Resource in Spark.extensions(resource) end) - |> Enum.flat_map(&AshGraphql.Resource.mutations(api, &1, action_middleware, schema)) + |> Enum.flat_map( + &AshGraphql.Resource.mutations(api, &1, action_middleware, schema, relay_ids?) + ) end @doc false - def type_definitions(api, resources, schema, env, first?, define_relay_types?) do + def type_definitions(api, resources, schema, env, first?, define_relay_types?, relay_ids?) do resource_types = resources |> Enum.reject(&Ash.Resource.Info.embedded?/1) |> Enum.flat_map(fn resource -> if AshGraphql.Resource in Spark.extensions(resource) && AshGraphql.Resource.Info.type(resource) do - AshGraphql.Resource.type_definitions(resource, api, schema) ++ + AshGraphql.Resource.type_definitions(resource, api, schema, relay_ids?) ++ AshGraphql.Resource.mutation_types(resource, schema) else AshGraphql.Resource.no_graphql_types(resource, schema) diff --git a/lib/ash_graphql.ex b/lib/ash_graphql.ex index 1d57c9ac..9f83c329 100644 --- a/lib/ash_graphql.ex +++ b/lib/ash_graphql.ex @@ -36,7 +36,8 @@ defmodule AshGraphql do apis: opts[:apis], api: opts[:api], action_middleware: opts[:action_middleware] || [], - define_relay_types?: Keyword.get(opts, :define_relay_types?, true) + define_relay_types?: Keyword.get(opts, :define_relay_types?, true), + relay_ids?: Keyword.get(opts, :relay_ids?, false) ], generated: true do require Ash.Api.Info @@ -138,14 +139,24 @@ defmodule AshGraphql do blueprint_with_queries = api - |> AshGraphql.Api.queries(unquote(resources), action_middleware, __MODULE__) + |> AshGraphql.Api.queries( + unquote(resources), + action_middleware, + __MODULE__, + unquote(relay_ids?) + ) |> Enum.reduce(blueprint, fn query, blueprint -> Absinthe.Blueprint.add_field(blueprint, "RootQueryType", query) end) blueprint_with_mutations = api - |> AshGraphql.Api.mutations(unquote(resources), action_middleware, __MODULE__) + |> AshGraphql.Api.mutations( + unquote(resources), + action_middleware, + __MODULE__, + unquote(relay_ids?) + ) |> Enum.reduce(blueprint_with_queries, fn mutation, blueprint -> Absinthe.Blueprint.add_field(blueprint, "RootMutationType", mutation) end) @@ -155,7 +166,11 @@ defmodule AshGraphql do apis = unquote(Enum.map(apis, &elem(&1, 0))) embedded_types = - AshGraphql.get_embedded_types(unquote(ash_resources), unquote(schema)) + AshGraphql.get_embedded_types( + unquote(ash_resources), + unquote(schema), + unquote(relay_ids?) + ) global_enums = AshGraphql.global_enums(unquote(ash_resources), unquote(schema), __ENV__) @@ -171,7 +186,8 @@ defmodule AshGraphql do unquote(schema), __ENV__, true, - unquote(define_relay_types?) + unquote(define_relay_types?), + unquote(relay_ids?) ) ++ global_enums ++ global_unions ++ @@ -185,7 +201,8 @@ defmodule AshGraphql do unquote(schema), __ENV__, false, - false + false, + unquote(relay_ids?) ) end @@ -491,7 +508,7 @@ defmodule AshGraphql do end # sobelow_skip ["DOS.BinToAtom"] - def get_embedded_types(all_resources, schema) do + def get_embedded_types(all_resources, schema, relay_ids?) do all_resources |> Enum.flat_map(fn resource -> resource @@ -566,7 +583,8 @@ defmodule AshGraphql do AshGraphql.Resource.type_definition( embedded_type, Module.concat(embedded_type, ShadowApi), - schema + schema, + relay_ids? ), AshGraphql.Resource.embedded_type_input( source_resource, diff --git a/lib/graphql/resolver.ex b/lib/graphql/resolver.ex index 6a8c6d47..5b83fae4 100644 --- a/lib/graphql/resolver.ex +++ b/lib/graphql/resolver.ex @@ -130,7 +130,7 @@ defmodule AshGraphql.Graphql.Resolver do action: action, identity: identity, modify_resolution: modify - } = gql_query} + } = gql_query, relay_ids?} ) do case handle_arguments(resource, action, arguments) do {:ok, arguments} -> @@ -159,7 +159,7 @@ defmodule AshGraphql.Graphql.Resolver do tenant: Map.get(context, :tenant) ] - filter = identity_filter(identity, resource, arguments) + filter = identity_filter(identity, resource, arguments, relay_ids?) query = resource @@ -268,7 +268,7 @@ defmodule AshGraphql.Graphql.Resolver do %{arguments: args, context: context} = resolution, {api, resource, %{name: query_name, type: :read_one, action: action, modify_resolution: modify} = - gql_query} + gql_query, _relay_ids?} ) do metadata = %{ api: api, @@ -361,7 +361,7 @@ defmodule AshGraphql.Graphql.Resolver do relay?: relay?, action: action, modify_resolution: modify - } = gql_query} + } = gql_query, _relay_ids?} ) do case handle_arguments(resource, action, args) do {:ok, args} -> @@ -979,7 +979,7 @@ defmodule AshGraphql.Graphql.Resolver do upsert?: upsert?, upsert_identity: upsert_identity, modify_resolution: modify - }} + }, _relay_ids?} ) do input = arguments[:input] || %{} @@ -1100,7 +1100,7 @@ defmodule AshGraphql.Graphql.Resolver do identity: identity, read_action: read_action, modify_resolution: modify - }} + }, relay_ids?} ) do read_action = read_action || Ash.Resource.Info.primary_action!(resource, :read).name input = arguments[:input] || %{} @@ -1131,7 +1131,7 @@ defmodule AshGraphql.Graphql.Resolver do :gql_mutation, mutation_name, metadata do - filter = identity_filter(identity, resource, arguments) + filter = identity_filter(identity, resource, arguments, relay_ids?) case filter do {:ok, filter} -> @@ -1260,7 +1260,7 @@ defmodule AshGraphql.Graphql.Resolver do identity: identity, read_action: read_action, modify_resolution: modify - }} + }, relay_ids?} ) do read_action = read_action || Ash.Resource.Info.primary_action!(resource, :read).name input = arguments[:input] || %{} @@ -1291,7 +1291,7 @@ defmodule AshGraphql.Graphql.Resolver do :gql_mutation, mutation_name, metadata do - filter = identity_filter(identity, resource, arguments) + filter = identity_filter(identity, resource, arguments, relay_ids?) case filter do {:ok, filter} -> @@ -1429,13 +1429,17 @@ defmodule AshGraphql.Graphql.Resolver do apply(m, f, [resolution | args] ++ a) end - def identity_filter(false, _resource, _arguments) do + def identity_filter(false, _resource, _arguments, _relay_ids?) do {:ok, nil} end - def identity_filter(nil, resource, arguments) do - if AshGraphql.Resource.Info.encode_primary_key?(resource) do - case AshGraphql.Resource.decode_primary_key(resource, Map.get(arguments, :id) || "") do + def identity_filter(nil, resource, arguments, relay_ids?) do + if relay_ids? or AshGraphql.Resource.Info.encode_primary_key?(resource) do + case AshGraphql.Resource.decode_id( + resource, + Map.get(arguments, :id) || "", + relay_ids? + ) do {:ok, value} -> {:ok, value} @@ -1461,7 +1465,7 @@ defmodule AshGraphql.Graphql.Resolver do end end - def identity_filter(identity, resource, arguments) do + def identity_filter(identity, resource, arguments, _relay_ids?) do {:ok, resource |> Ash.Resource.Info.identities() @@ -2364,9 +2368,12 @@ defmodule AshGraphql.Graphql.Resolver do def resolve_id( %{source: parent} = resolution, - {_resource, field} + {_resource, _field, relay_ids?} ) do - Absinthe.Resolution.put_result(resolution, {:ok, Map.get(parent, field)}) + Absinthe.Resolution.put_result( + resolution, + {:ok, AshGraphql.Resource.encode_id(parent, relay_ids?)} + ) end def resolve_union(%Absinthe.Resolution{state: :resolved} = resolution, _), @@ -2483,11 +2490,11 @@ defmodule AshGraphql.Graphql.Resolver do def resolve_composite_id( %{source: parent} = resolution, - {_resource, _fields} + {_resource, _fields, relay_ids?} ) do Absinthe.Resolution.put_result( resolution, - {:ok, AshGraphql.Resource.encode_primary_key(parent)} + {:ok, AshGraphql.Resource.encode_id(parent, relay_ids?)} ) end diff --git a/lib/resource/resource.ex b/lib/resource/resource.ex index d637de2f..a6e536ba 100644 --- a/lib/resource/resource.ex +++ b/lib/resource/resource.ex @@ -400,6 +400,14 @@ defmodule AshGraphql.Resource do %{module: __MODULE__, location: %{file: env.file, line: env.line}} end + def encode_id(record, relay_ids?) do + if relay_ids? do + encode_relay_id(record) + else + encode_primary_key(record) + end + end + def encode_primary_key(%resource{} = record) do case Ash.Resource.Info.primary_key(resource) do [field] -> @@ -417,6 +425,30 @@ defmodule AshGraphql.Resource do end end + def encode_relay_id(%resource{} = record) do + type = type(resource) + primary_key = encode_primary_key(record) + + "#{type}:#{primary_key}" + |> Base.encode64() + end + + def decode_id(resource, id, relay_ids?) do + type = type(resource) + + if relay_ids? do + case decode_relay_id(id) do + {:ok, {^type, primary_key}} -> + decode_primary_key(resource, primary_key) + + _ -> + {:error, "Invalid primary key"} + end + else + decode_primary_key(resource, id) + end + end + def decode_primary_key(resource, value) do case Ash.Resource.Info.primary_key(resource) do [field] -> @@ -434,8 +466,22 @@ defmodule AshGraphql.Resource do end end + def decode_relay_id(id) do + [type_string, primary_key] = + id + |> Base.decode64!() + |> String.split(":", parts: 2) + + type = String.to_existing_atom(type_string) + + {:ok, {type, primary_key}} + rescue + _ -> + {:error, "Invalid primary key"} + end + @doc false - def queries(api, resource, action_middleware, schema, as_mutations? \\ false) do + def queries(api, resource, action_middleware, schema, relay_ids?, as_mutations? \\ false) do type = AshGraphql.Resource.Info.type(resource) if type do @@ -475,7 +521,7 @@ defmodule AshGraphql.Resource do middleware: action_middleware ++ [ - {{AshGraphql.Graphql.Resolver, :resolve}, {api, resource, query}} + {{AshGraphql.Graphql.Resolver, :resolve}, {api, resource, query, relay_ids?}} ], complexity: {AshGraphql.Graphql.Resolver, :query_complexity}, module: schema, @@ -492,7 +538,7 @@ defmodule AshGraphql.Resource do # sobelow_skip ["DOS.StringToAtom"] @doc false - def mutations(api, resource, action_middleware, schema) do + def mutations(api, resource, action_middleware, schema, relay_ids?) do resource |> mutations() |> Enum.map(fn @@ -540,7 +586,7 @@ defmodule AshGraphql.Resource do raise "No such action #{mutation.action} for #{inspect(resource)}" if action.soft? do - update_mutation(resource, schema, mutation, schema, action_middleware, api) + update_mutation(resource, schema, mutation, schema, action_middleware, api, relay_ids?) else %Absinthe.Blueprint.Schema.FieldDefinition{ arguments: mutation_args(mutation, resource, schema), @@ -548,7 +594,7 @@ defmodule AshGraphql.Resource do middleware: action_middleware ++ [ - {{AshGraphql.Graphql.Resolver, :mutate}, {api, resource, mutation}} + {{AshGraphql.Graphql.Resolver, :mutate}, {api, resource, mutation, relay_ids?}} ], module: schema, name: to_string(mutation.name), @@ -591,7 +637,7 @@ defmodule AshGraphql.Resource do middleware: action_middleware ++ [ - {{AshGraphql.Graphql.Resolver, :mutate}, {api, resource, mutation}} + {{AshGraphql.Graphql.Resolver, :mutate}, {api, resource, mutation, relay_ids?}} ], module: schema, name: to_string(mutation.name), @@ -601,13 +647,13 @@ defmodule AshGraphql.Resource do } mutation -> - update_mutation(resource, schema, mutation, schema, action_middleware, api) + update_mutation(resource, schema, mutation, schema, action_middleware, api, relay_ids?) end) - |> Enum.concat(queries(api, resource, action_middleware, schema, true)) + |> Enum.concat(queries(api, resource, action_middleware, schema, relay_ids?, true)) end # sobelow_skip ["DOS.StringToAtom"] - defp update_mutation(resource, schema, mutation, schema, action_middleware, api) do + defp update_mutation(resource, schema, mutation, schema, action_middleware, api, relay_ids?) do action = Ash.Resource.Info.action(resource, mutation.action) || raise "No such action #{mutation.action} for #{inspect(resource)}" @@ -642,7 +688,7 @@ defmodule AshGraphql.Resource do middleware: action_middleware ++ [ - {{AshGraphql.Graphql.Resolver, :mutate}, {api, resource, mutation}} + {{AshGraphql.Graphql.Resolver, :mutate}, {api, resource, mutation, relay_ids?}} ], module: schema, name: to_string(mutation.name), @@ -1494,10 +1540,10 @@ defmodule AshGraphql.Resource do end @doc false - def type_definitions(resource, api, schema) do + def type_definitions(resource, api, schema, relay_ids?) do List.wrap(calculation_input(resource, schema)) ++ - List.wrap(type_definition(resource, api, schema)) ++ - List.wrap(query_type_definitions(resource, api, schema)) ++ + List.wrap(type_definition(resource, api, schema, relay_ids?)) ++ + List.wrap(query_type_definitions(resource, api, schema, relay_ids?)) ++ List.wrap(sort_input(resource, schema)) ++ List.wrap(filter_input(resource, schema)) ++ filter_field_types(resource, schema) ++ @@ -3308,7 +3354,7 @@ defmodule AshGraphql.Resource do type.identifier == :node end - def query_type_definitions(resource, api, schema) do + def query_type_definitions(resource, api, schema, relay_ids?) do resource_type = AshGraphql.Resource.Info.type(resource) resource @@ -3334,7 +3380,7 @@ defmodule AshGraphql.Resource do %Absinthe.Blueprint.Schema.ObjectTypeDefinition{ description: Ash.Resource.Info.description(resource), interfaces: interfaces, - fields: fields(resource, api, schema, query), + fields: fields(resource, api, schema, relay_ids?, query), identifier: query.type_name, module: schema, name: Macro.camelize(to_string(query.type_name)), @@ -3344,7 +3390,7 @@ defmodule AshGraphql.Resource do end) end - def type_definition(resource, api, schema) do + def type_definition(resource, api, schema, relay_ids?) do actual_resource = Ash.Type.NewType.subtype_of(resource) if generate_object?(resource) do @@ -3356,6 +3402,7 @@ defmodule AshGraphql.Resource do resource |> queries() |> Enum.any?(&Map.get(&1, :relay?)) + |> Kernel.or(relay_ids?) interfaces = if relay? do @@ -3374,7 +3421,7 @@ defmodule AshGraphql.Resource do %Absinthe.Blueprint.Schema.ObjectTypeDefinition{ description: Ash.Resource.Info.description(resource), interfaces: interfaces, - fields: fields(resource, api, schema), + fields: fields(resource, api, schema, relay_ids?), identifier: type, module: schema, name: Macro.camelize(to_string(type)), @@ -3384,8 +3431,8 @@ defmodule AshGraphql.Resource do end end - defp fields(resource, api, schema, query \\ nil) do - attributes(resource, api, schema) ++ + defp fields(resource, api, schema, relay_ids?, query \\ nil) do + attributes(resource, api, schema, relay_ids?) ++ metadata(query, resource, schema) ++ relationships(resource, api, schema) ++ aggregates(resource, api, schema) ++ @@ -3448,7 +3495,7 @@ defmodule AshGraphql.Resource do end end - defp attributes(resource, api, schema) do + defp attributes(resource, api, schema, relay_ids?) do attribute_names = AshGraphql.Resource.Info.field_names(resource) attributes = @@ -3490,15 +3537,15 @@ defmodule AshGraphql.Resource do } end) - if AshGraphql.Resource.Info.encode_primary_key?(resource) do - encoded_primary_key_attributes(resource, schema) ++ + if relay_ids? or AshGraphql.Resource.Info.encode_primary_key?(resource) do + encoded_id(resource, schema, relay_ids?) ++ attributes else attributes end end - defp encoded_primary_key_attributes(resource, schema) do + defp encoded_id(resource, schema, relay_ids?) do case Ash.Resource.Info.primary_key(resource) do [field] -> attribute = Ash.Resource.Info.attribute(resource, field) @@ -3514,7 +3561,7 @@ defmodule AshGraphql.Resource do name: "id", type: %Absinthe.Blueprint.TypeReference.NonNull{of_type: :id}, middleware: [ - {{AshGraphql.Graphql.Resolver, :resolve_id}, {resource, field}} + {{AshGraphql.Graphql.Resolver, :resolve_id}, {resource, field, relay_ids?}} ], __reference__: ref(__ENV__) } @@ -3530,7 +3577,8 @@ defmodule AshGraphql.Resource do name: "id", type: %Absinthe.Blueprint.TypeReference.NonNull{of_type: :id}, middleware: [ - {{AshGraphql.Graphql.Resolver, :resolve_composite_id}, {resource, fields}} + {{AshGraphql.Graphql.Resolver, :resolve_composite_id}, + {resource, fields, relay_ids?}} ], __reference__: ref(__ENV__) } diff --git a/test/relay_ids_test.exs b/test/relay_ids_test.exs new file mode 100644 index 00000000..30ed80ad --- /dev/null +++ b/test/relay_ids_test.exs @@ -0,0 +1,111 @@ +defmodule AshGraphql.RelayIdsTest do + use ExUnit.Case, async: false + + alias AshGraphql.Test.RelayIds.{Api, Post, Schema, User} + + setup do + on_exit(fn -> + AshGraphql.TestHelpers.stop_ets() + end) + end + + describe "relay global ID" do + test "can be used in get queries and is exposed correctly in relationships" do + user = + User + |> Ash.Changeset.for_create(:create, %{name: "fred"}) + |> Api.create!() + + post = + Post + |> Ash.Changeset.for_create( + :create, + %{ + author_id: user.id, + text: "foo", + published: true + } + ) + |> Api.create!() + + user_relay_id = AshGraphql.Resource.encode_relay_id(user) + post_relay_id = AshGraphql.Resource.encode_relay_id(post) + + resp = + """ + query GetPost($id: ID!) { + getPost(id: $id) { + text + author { + id + name + } + } + } + """ + |> Absinthe.run(Schema, + variables: %{ + "id" => post_relay_id + } + ) + + assert {:ok, result} = resp + + refute Map.has_key?(result, :errors) + + assert %{ + data: %{ + "getPost" => %{ + "text" => "foo", + "author" => %{"id" => ^user_relay_id, "name" => "fred"} + } + } + } = result + end + + test "returns error on invalid ID" do + resp = + """ + query GetPost($id: ID!) { + getPost(id: $id) { + text + } + } + """ + |> Absinthe.run(Schema, + variables: %{ + "id" => "invalid" + } + ) + + assert {:ok, result} = resp + assert result[:errors] != nil + end + + test "returns error on ID for wrong resource" do + user = + User + |> Ash.Changeset.for_create(:create, %{name: "fred"}) + |> Api.create!() + + user_relay_id = AshGraphql.Resource.encode_relay_id(user) + + resp = + """ + query GetPost($id: ID!) { + getPost(id: $id) { + text + } + } + """ + |> Absinthe.run(Schema, + variables: %{ + "id" => user_relay_id + } + ) + + assert {:ok, result} = resp + assert result[:errors] != nil + end + end +end diff --git a/test/support/relay_ids/api.ex b/test/support/relay_ids/api.ex new file mode 100644 index 00000000..47d455d2 --- /dev/null +++ b/test/support/relay_ids/api.ex @@ -0,0 +1,13 @@ +defmodule AshGraphql.Test.RelayIds.Api do + @moduledoc false + + use Ash.Api, + extensions: [ + AshGraphql.Api + ], + otp_app: :ash_graphql + + resources do + registry(AshGraphql.Test.RelayIds.Registry) + end +end diff --git a/test/support/relay_ids/registry.ex b/test/support/relay_ids/registry.ex new file mode 100644 index 00000000..4ed2182d --- /dev/null +++ b/test/support/relay_ids/registry.ex @@ -0,0 +1,9 @@ +defmodule AshGraphql.Test.RelayIds.Registry do + @moduledoc false + use Ash.Registry + + entries do + entry(AshGraphql.Test.RelayIds.Post) + entry(AshGraphql.Test.RelayIds.User) + end +end diff --git a/test/support/relay_ids/resources/post.ex b/test/support/relay_ids/resources/post.ex new file mode 100644 index 00000000..fb950e19 --- /dev/null +++ b/test/support/relay_ids/resources/post.ex @@ -0,0 +1,46 @@ +defmodule AshGraphql.Test.RelayIds.Post do + @moduledoc false + + use Ash.Resource, + data_layer: Ash.DataLayer.Ets, + extensions: [AshGraphql.Resource] + + require Ash.Query + + graphql do + type :post + + queries do + get :get_post, :read + list :post_library, :read + end + + mutations do + create :simple_create_post, :create + update :update_post, :update + destroy :delete_post, :destroy + end + end + + actions do + defaults([:update, :read, :destroy]) + + create :create do + primary?(true) + argument(:author_id, :uuid) + + change(set_attribute(:author_id, arg(:author_id))) + end + end + + attributes do + uuid_primary_key(:id) + attribute(:text, :string) + end + + relationships do + belongs_to(:author, AshGraphql.Test.RelayIds.User) do + attribute_writable?(true) + end + end +end diff --git a/test/support/relay_ids/resources/user.ex b/test/support/relay_ids/resources/user.ex new file mode 100644 index 00000000..a46b56f6 --- /dev/null +++ b/test/support/relay_ids/resources/user.ex @@ -0,0 +1,28 @@ +defmodule AshGraphql.Test.RelayIds.User do + @moduledoc false + + use Ash.Resource, + data_layer: Ash.DataLayer.Ets, + extensions: [AshGraphql.Resource] + + graphql do + type :user + + mutations do + create :create_user, :create + end + end + + actions do + defaults([:create, :update, :destroy, :read]) + end + + attributes do + uuid_primary_key(:id) + attribute(:name, :string) + end + + relationships do + has_many(:posts, AshGraphql.Test.RelayIds.Post, destination_attribute: :author_id) + end +end diff --git a/test/support/relay_ids/schema.ex b/test/support/relay_ids/schema.ex new file mode 100644 index 00000000..6b1cfabe --- /dev/null +++ b/test/support/relay_ids/schema.ex @@ -0,0 +1,30 @@ +defmodule AshGraphql.Test.RelayIds.Schema do + @moduledoc false + + use Absinthe.Schema + + @apis [AshGraphql.Test.RelayIds.Api] + + use AshGraphql, apis: @apis, relay_ids?: true + + query do + end + + mutation do + end + + object :foo do + field(:foo, :string) + field(:bar, :string) + end + + input_object :foo_input do + field(:foo, non_null(:string)) + field(:bar, non_null(:string)) + end + + enum :status do + value(:open, description: "The post is open") + value(:closed, description: "The post is closed") + end +end