From ae94fcde545d8372db540dca47554f7699a9c011 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Wed, 13 Mar 2024 12:10:30 -0400 Subject: [PATCH] improvement: make pagination more explicitly configurable --- lib/resource/query.ex | 8 ++++ lib/resource/resource.ex | 88 ++++++++++++++++++++++++++++++++-------- 2 files changed, 79 insertions(+), 17 deletions(-) diff --git a/lib/resource/query.ex b/lib/resource/query.ex index 110463d7..42eec5d3 100644 --- a/lib/resource/query.ex +++ b/lib/resource/query.ex @@ -12,6 +12,7 @@ defmodule AshGraphql.Resource.Query do hide_inputs: [], metadata_names: [], metadata_types: [], + paginate_with: :keyset, show_metadata: nil, type_name: nil, relay?: false @@ -106,6 +107,13 @@ defmodule AshGraphql.Resource.Query do doc: """ If true, the graphql queries/resolvers for this resource will be built to honor the relay specification. See [the relay guide](/documentation/topics/relay.html) for more. """ + ], + paginate_with: [ + type: {:one_of, [:keyset, :offset, nil]}, + default: :keyset, + doc: """ + Determine the pagination strategy to use, if multiple are available. If `nil`, no pagination is applied, otherwise the given strategy is used. + """ ] ] |> Spark.OptionsHelpers.merge_schemas(@query_schema, "Shared Query Options") diff --git a/lib/resource/resource.ex b/lib/resource/resource.ex index 5d96ac27..16ba64c8 100644 --- a/lib/resource/resource.ex +++ b/lib/resource/resource.ex @@ -531,7 +531,15 @@ defmodule AshGraphql.Resource do %Absinthe.Blueprint.Schema.FieldDefinition{ arguments: - args(query.type, resource, query_action, schema, query.identity, query.hide_inputs), + args( + query.type, + resource, + query_action, + schema, + query.identity, + query.hide_inputs, + query + ), identifier: query.name, middleware: action_middleware ++ @@ -1237,7 +1245,7 @@ defmodule AshGraphql.Resource do defp query_type(%{type: :list, relay?: relay?} = query, _resource, action, type) do type = query.type_name || type - if action.pagination do + if pagination_strategy(query, action) do cond do relay? -> String.to_atom("#{type}_connection") @@ -1265,6 +1273,43 @@ defmodule AshGraphql.Resource do maybe_wrap_non_null(type, not query.allow_nil?) end + defp pagination_strategy(nil, _) do + nil + end + + defp pagination_strategy(%{paginate_with: strategy}, action) do + if !action.pagination do + nil + else + strategies = + if !action.pagination.required? do + [nil] + else + [] + end + + strategies = + if action.pagination.keyset? do + [:keyset | strategies] + else + strategies + end + + strategies = + if action.pagination.offset? do + [:offset | strategies] + else + strategies + end + + if strategy in strategies do + strategy + else + Enum.at(strategies, 0) + end + end + end + defp maybe_wrap_non_null({:non_null, type}, true) do %Absinthe.Blueprint.TypeReference.NonNull{ of_type: type @@ -1342,14 +1387,22 @@ defmodule AshGraphql.Resource do end) end - defp args(action_type, resource, action, schema, identity \\ nil, hide_inputs \\ []) + defp args( + action_type, + resource, + action, + schema, + identity \\ nil, + hide_inputs \\ [], + query \\ nil + ) - defp args(:get, resource, action, schema, nil, hide_inputs) do + defp args(:get, resource, action, schema, nil, hide_inputs, _query) do get_fields(resource) ++ read_args(resource, action, schema, hide_inputs) end - defp args(:get, resource, action, schema, identity, hide_inputs) do + defp args(:get, resource, action, schema, identity, hide_inputs, _query) do if identity do resource |> Ash.Resource.Info.identities() @@ -1374,7 +1427,7 @@ defmodule AshGraphql.Resource do |> Enum.concat(read_args(resource, action, schema, hide_inputs)) end - defp args(:read_one, resource, action, schema, _, hide_inputs) do + defp args(:read_one, resource, action, schema, _, hide_inputs, _query) do args = if AshGraphql.Resource.Info.derive_filter?(resource) do case resource_filter_fields(resource, schema) do @@ -1399,7 +1452,7 @@ defmodule AshGraphql.Resource do args ++ read_args(resource, action, schema, hide_inputs) end - defp args(:list, resource, action, schema, _, hide_inputs) do + defp args(:list, resource, action, schema, _, hide_inputs, query) do args = if AshGraphql.Resource.Info.derive_filter?(resource) do case resource_filter_fields(resource, schema) do @@ -1445,10 +1498,10 @@ defmodule AshGraphql.Resource do args end - args ++ pagination_args(action) ++ read_args(resource, action, schema, hide_inputs) + args ++ pagination_args(query, action) ++ read_args(resource, action, schema, hide_inputs) end - defp args(:list_related, resource, action, schema, identity, hide_inputs) do + defp args(:list_related, resource, action, schema, identity, hide_inputs, _) do args(:list, resource, action, schema, identity, hide_inputs) ++ [ %Absinthe.Blueprint.Schema.InputValueDefinition{ @@ -1468,7 +1521,7 @@ defmodule AshGraphql.Resource do ] end - defp args(:one_related, resource, action, schema, _identity, hide_inputs) do + defp args(:one_related, resource, action, schema, _identity, hide_inputs, _) do read_args(resource, action, schema, hide_inputs) end @@ -1492,15 +1545,16 @@ defmodule AshGraphql.Resource do end) end - defp pagination_args(action) do - if action.pagination do - if action.pagination.keyset? do + defp pagination_args(query, action) do + case pagination_strategy(query, action) do + nil -> + [] + + :keyset -> keyset_pagination_args(action) - else + + :offset -> offset_pagination_args(action) - end - else - [] end end