Skip to content

Commit

Permalink
feat: add support for relay global IDs
Browse files Browse the repository at this point in the history
Part 1 of what is needed for ash-project#99.
The other half is implementing the node query.
  • Loading branch information
rbino committed Jan 22, 2024
1 parent 7225d5f commit eb5aea2
Show file tree
Hide file tree
Showing 11 changed files with 383 additions and 57 deletions.
11 changes: 11 additions & 0 deletions documentation/topics/relay.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
17 changes: 11 additions & 6 deletions lib/api/api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
34 changes: 26 additions & 8 deletions lib/ash_graphql.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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__)
Expand All @@ -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 ++
Expand All @@ -185,7 +201,8 @@ defmodule AshGraphql do
unquote(schema),
__ENV__,
false,
false
false,
unquote(relay_ids?)
)
end

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
43 changes: 25 additions & 18 deletions lib/graphql/resolver.ex
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,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} ->
Expand Down Expand Up @@ -165,7 +165,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
Expand Down Expand Up @@ -279,7 +279,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,
Expand Down Expand Up @@ -372,7 +372,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} ->
Expand Down Expand Up @@ -990,7 +990,7 @@ defmodule AshGraphql.Graphql.Resolver do
upsert?: upsert?,
upsert_identity: upsert_identity,
modify_resolution: modify
}}
}, _relay_ids?}
) do
input = arguments[:input] || %{}

Expand Down Expand Up @@ -1111,7 +1111,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] || %{}
Expand Down Expand Up @@ -1142,7 +1142,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} ->
Expand Down Expand Up @@ -1271,7 +1271,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] || %{}
Expand Down Expand Up @@ -1302,7 +1302,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} ->
Expand Down Expand Up @@ -1440,13 +1440,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}

Expand All @@ -1472,7 +1476,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()
Expand Down Expand Up @@ -2375,9 +2379,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, _),
Expand Down Expand Up @@ -2508,11 +2515,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

Expand Down
Loading

0 comments on commit eb5aea2

Please sign in to comment.