Skip to content

Hydrate enum values dynamically #859

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

Merged

Conversation

binaryseed
Copy link
Contributor

  • raise when calling a function in enum value :as
  • enable hydrating an enum value to configure it directly

@benwilson512 open to feedback on the approach for this one...

closes #843

@binaryseed
Copy link
Contributor Author

@benwilson512 Last few issues blocking next 1.5 rc...

Copy link
Contributor

@benwilson512 benwilson512 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems good, but does it let us actually add values to the enum or merely adjust the values that are there?

@binaryseed
Copy link
Contributor Author

binaryseed commented Feb 4, 2020

Yeah good point, this just lets you edit an existing value - Perhaps I should just put a :values hydration on the enum type instead, and folks can define any programatic values that way

What do you think of a hydration like this:

{:values, [Blueprint.Schema.EnumValueDefinition.t()]}

It seems like constructing the full Blueprint.Schema.EnumValueDefinition with all the right values might be complex.

I could split this up and get the explicit exception raised first then tackle dynamic enum values in another PR

@binaryseed
Copy link
Contributor Author

@benwilson512 do you have any thoughts on this?

It seems like most other hydration takes pretty basic data types (strings, functions, etc), whereas setting the full values involves setting up the correct blueprint structs, which is quite a bit more complex...

@benwilson512
Copy link
Contributor

That's a good point. I think it's fair to say that if you want to mess with the actual type structs then you should use a pipeline modifier to add a new schema phase. We definitely could add some docs around that to make sure it feels intuitive.

I think we can merge what is here as in this case, and promote general schema manipulation strategies as the way to add whole values.

Copy link
Contributor

@benwilson512 benwilson512 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@binaryseed binaryseed merged commit 1495a09 into absinthe-graphql:master Feb 20, 2020
@binaryseed binaryseed deleted the hydrate-dynamic-values branch February 20, 2020 22:06
Comment on lines +19 to +21
def hydrate(%Absinthe.Blueprint.Schema.EnumValueDefinition{identifier: identifier}, [])
when identifier in [:red, :blue, :green] do
{:as, color_map(identifier)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it correct to use the :identifier key here, or should we instead be using :value? For example, I have this enum definition:

enum :purchase_payment_status do
  value(:paid, as: :purchase_payment_status_paid)
end

In that example, :paid corresponds to the external value of the enum, "PAID". With my chosen value as: :purchase_payment_status_paid, I assumed I was supposed to provide something totally unique that I could reference during hydration. However, when hydrate gets called, it gets called with:

[2] %Elixir.Absinthe.Blueprint.Schema.EnumValueDefinition{
    __reference__: %{
      location: %{
        file: "/MyApp/web/graphql/schema_types/purchase.ex"
        line: 12
      }
      module: :Elixir.MyApp.GraphQL.Schema.Types.Purchase
    }
    deprecation: nil
    description: "The purchase has not yet been paid"
    directives: []
    errors: []
    flags: %{}
    identifier: :not_paid
    module: :Elixir.MyApp.GraphQL.Schema.Types.Purchase
    name: "NOT_PAID"
    source_location: nil
    value: :purchase_payment_status_not_paid
  }

In that example it is :value that I actually want to match against in order to convert to its true internal value, "Not Paid", which I pull in from a function in another module during hydrate:

def hydrate(%Absinthe.Blueprint.Schema.EnumValueDefinition{value: value} = enum, [])
    when value in [:purchase_payment_status_not_paid] do
  {:as, MyApp.GraphQL.Schema.Types.Purchase.payment_status(value)}
end

Asking here for a few reasons:

  1. In case we might want to alter the test to reflect different recommended usage
  2. In case we want to alter any docs to reflect this
  3. In case somebody else runs into a similar issue and this example is helpful to them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just an example, people could do it whatever way works best for them...

Though the whole point is to assign the :as value, so already having one set will just overwrite it (ie: why set that at all in the schema?)

Seems like you have 3 "layers" of enum values going on here, which I don't think would be so common?

Copy link
Contributor

@bgentry bgentry Feb 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I can see how it might be less common. In this case, the external value :paid/"PAID" is not something that I would be confident would be unique across all of our enums, so I don't feel confident matching on that in hydrate because it might conflict with another value (say the same value on Order status, or Invoice status).

I haven't seen any GraphQL convention that advocates making all of your enums have globally unique values, so this seems like what's necessary to avoid a potential collision 🤷‍♂ Anyway, will assume this is a rarer use case and leave it at that, thanks @binaryseed ✌️

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't seen any GraphQL convention that advocates making all of your enums have globally unique values, so this seems like what's necessary to avoid a potential collision

The normal solution here is that the hydrate function should be passed a list as the second arg of the parent values indicating where in the schema "tree" we are. This would allow you to pattern match for a particular enum value definition found under a specific enum definition. However @binaryseed I'm noticing that the second arg in your test case is an empty list. Am if misremembering how hydrate works or are we losing context for some reason?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I noticed that too - it works currently, but i'm making a PR that matches on the ancestors since thats what we should be doing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1.5.0-rc.2: Calling functions in enum value 'as:' option results in compile error.
3 participants