-
Notifications
You must be signed in to change notification settings - Fork 527
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Subscription priming/ordinals #1168
base: main
Are you sure you want to change the base?
Changes from all commits
d40b6d9
96e803c
27441d0
ddfa4ee
b0cd04e
6601cc5
5512978
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
defmodule Absinthe.Blueprint.Continuation do | ||
@moduledoc false | ||
|
||
# Continuations allow further resolutions after the initial result is | ||
# returned | ||
|
||
alias Absinthe.Pipeline | ||
|
||
defstruct [ | ||
:phase_input, | ||
:pipeline | ||
] | ||
|
||
@type t :: %__MODULE__{ | ||
phase_input: Pipeline.data_t(), | ||
pipeline: Pipeline.t() | ||
} | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
defmodule Absinthe.Phase.Subscription.GetOrdinal do | ||
use Absinthe.Phase | ||
|
||
alias Absinthe.Phase.Subscription.SubscribeSelf | ||
|
||
@moduledoc false | ||
|
||
alias Absinthe.Blueprint | ||
|
||
@spec run(any, Keyword.t()) :: {:ok, Blueprint.t()} | ||
def run(blueprint, _options \\ []) do | ||
with %{type: :subscription, selections: [field]} <- Blueprint.current_operation(blueprint), | ||
{:ok, config} = SubscribeSelf.get_config(field, blueprint.execution.context, blueprint), | ||
{_, ordinal_fun} when is_function(ordinal_fun, 1) <- {:ordinal_fun, config[:ordinal]}, | ||
{_, ordinal_compare_fun} when is_function(ordinal_compare_fun, 2) <- | ||
{:ordinal_compare_fun, | ||
Keyword.get(config, :ordinal_compare, &default_ordinal_compare/2)} do | ||
ordinal = ordinal_fun.(blueprint.execution.root_value) | ||
|
||
result = | ||
blueprint.result | ||
|> Map.put(:ordinal, ordinal) | ||
|> Map.put(:ordinal_compare_fun, ordinal_compare_fun) | ||
|
||
{:ok, %{blueprint | result: result}} | ||
else | ||
{:ordinal_fun, f} when is_function(f) -> | ||
IO.write( | ||
:stderr, | ||
"Ordinal function must be 1-arity" | ||
) | ||
|
||
{:ok, blueprint} | ||
|
||
{:ordinal_compare_fun, f} when is_function(f) -> | ||
IO.write( | ||
:stderr, | ||
"Ordinal compare function must be 2-arity" | ||
) | ||
|
||
{:ok, blueprint} | ||
|
||
_ -> | ||
{:ok, blueprint} | ||
end | ||
end | ||
|
||
defp default_ordinal_compare(nil, new_ordinal), do: {true, new_ordinal} | ||
|
||
defp default_ordinal_compare(old_ordinal, new_ordinal) when old_ordinal < new_ordinal, | ||
do: {true, new_ordinal} | ||
|
||
defp default_ordinal_compare(old_ordinal, _new_ordinal), do: {false, old_ordinal} | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
defmodule Absinthe.Phase.Subscription.Prime do | ||
@moduledoc false | ||
|
||
alias Absinthe.Blueprint.Continuation | ||
alias Absinthe.Phase | ||
|
||
@spec run(any(), Keyword.t()) :: Absinthe.Phase.result_t() | ||
def run(blueprint, prime_result: prime_result) do | ||
{:ok, put_in(blueprint.execution.root_value, prime_result)} | ||
end | ||
|
||
def run(blueprint, prime_fun: prime_fun, resolution_options: options) do | ||
{:ok, prime_results} = prime_fun.(blueprint.execution) | ||
|
||
case prime_results do | ||
[first | rest] -> | ||
blueprint = put_in(blueprint.execution.root_value, first) | ||
blueprint = maybe_add_continuations(blueprint, rest, options) | ||
{:ok, blueprint} | ||
|
||
[] -> | ||
blueprint = put_in(blueprint.result, :no_more_results) | ||
{:replace, blueprint, []} | ||
end | ||
end | ||
|
||
defp maybe_add_continuations(blueprint, [], _options), do: blueprint | ||
|
||
defp maybe_add_continuations(blueprint, remaining_results, options) do | ||
continuations = | ||
Enum.map( | ||
remaining_results, | ||
&%Continuation{ | ||
phase_input: blueprint, | ||
pipeline: [ | ||
{__MODULE__, [prime_result: &1]}, | ||
{Phase.Document.Execution.Resolution, options}, | ||
Phase.Subscription.GetOrdinal, | ||
Phase.Document.Result | ||
] | ||
} | ||
) | ||
|
||
put_in(blueprint.result, %{continuations: continuations}) | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,10 +5,40 @@ defmodule Absinthe.Phase.Subscription.Result do | |
# subscription | ||
|
||
alias Absinthe.Blueprint | ||
alias Absinthe.Blueprint.Continuation | ||
alias Absinthe.Phase | ||
|
||
@spec run(any, Keyword.t()) :: {:ok, Blueprint.t()} | ||
def run(blueprint, topic: topic) do | ||
result = %{"subscribed" => topic} | ||
def run(blueprint, options) do | ||
bernardd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
topic = Keyword.fetch!(options, :topic) | ||
prime = Keyword.get(options, :prime) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thoughts on a more descriptive name like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FWIW, I'm fine with prime, just there may be something more apt/intuitive here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure. |
||
|
||
result = maybe_add_prime(%{"subscribed" => topic}, prime, blueprint, options) | ||
|
||
{:ok, put_in(blueprint.result, result)} | ||
end | ||
|
||
def maybe_add_prime(result, nil, _blueprint, _options), do: result | ||
|
||
def maybe_add_prime(result, prime_fun, blueprint, options) when is_function(prime_fun, 1) do | ||
continuation = %Continuation{ | ||
phase_input: blueprint, | ||
pipeline: [ | ||
{Phase.Subscription.Prime, [prime_fun: prime_fun, resolution_options: options]}, | ||
{Phase.Document.Execution.Resolution, options}, | ||
Phase.Subscription.GetOrdinal, | ||
Phase.Document.Result | ||
] | ||
} | ||
|
||
Map.put(result, :continuations, [continuation]) | ||
end | ||
|
||
def maybe_add_prime(_result, prime_fun, _blueprint, _options) do | ||
raise """ | ||
Invalid prime function. Must be a function of arity 1. | ||
|
||
#{inspect(prime_fun)} | ||
""" | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I take it the purpose of the
{:ok, }
tuple wrapping for now is to leave room for other return tuples in the future? Is it conceivable thatprime
would return some non:ok
value?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally, I think we should have the prime function return the tuple, similar to resolutions. Otherwise you could conceivably have a
{:ok, {:error, :my_error_state}}
returned here or something equally wonky.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My thinking was mostly just to keep it consistent with return types in the other resolution callback functions. You're right that it doesn't serve much purpose at the moment, but I think at the time I was thinking we may want to allow error conditions to be propagated up like in resolutions. I'm not quite sure how that would be presented at a graphQL level though.