Skip to content
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

input_object should take a :one_of option. #1207

Open
seivan opened this issue Nov 14, 2022 · 13 comments
Open

input_object should take a :one_of option. #1207

seivan opened this issue Nov 14, 2022 · 13 comments

Comments

@seivan
Copy link
Contributor

seivan commented Nov 14, 2022

Expected behavior

Union input type as supported by GraphQL Spec.

Actual behavior

Does not exist, and doesn't allow for union input arguments.

This is valid SDL

type Query {
    item(by: ItemByInput!): Item
}

input ItemDetailsInput {
    name: String!
    color: String!

}

input ItemByInput @oneOf {
    id: ID!
    order: Int!
    details: ItemDetailsInput!
}

This allows for a query that looks like

query {
  item(by:{order: 4322})
}

or

query {
  item(by:{id: "asdasd-asd-asd"})
}

or even an object

query {
  item(by:{details: {color:"red",  name: "someName"}})
}

The example is a bit contrived, but I wanted to show case the options.

I don't mind adding support for this myself (assuming it is in fact missing) but I am bit lost on how that would work. If you could point me to the right direction, that would be great.

I am imagining the api to be something like

input_object :item_details_input do
  field :name, non_null(:string)
  field :color, non_null(:string)
end

input_object :item_by_input, :one_of do
  field :order, non_null(:integer)
  field :id, non_null(:id)
  field :value, non_null(:string)
end

Specifically, just a :one_of argument after input_object

@seivan
Copy link
Contributor Author

seivan commented Nov 14, 2022

If there is support for directives, then it might be solved via #1193 but I still don't see how the one_of would be added to the input_object

@benwilson512
Copy link
Contributor

Hi @seivan can you link to the relevant part of the spec? I am not seeing any any reference to this in https://spec.graphql.org/draft/ or the September 2021 release.

@benwilson512
Copy link
Contributor

I see graphql/graphql-spec#825 which to me indicates that this isn't actually in the spec yet. Supporting a @oneOf directive should be doable with schema compilation phases. Once it's part of the spec it will be appropriate to include it in Absinthe itself.

@benwilson512 benwilson512 closed this as not planned Won't fix, can't repro, duplicate, stale Nov 14, 2022
@seivan
Copy link
Contributor Author

seivan commented Nov 14, 2022

@benwilson512 My bad, I got excited and jumped the gun. graphql/graphql-spec#825

It has yet to be adopted by the spec, though has been merged into a lib we're using in a different project and seemingly many others including Ruby have implemented it.

The output in TS
image

I would still suggest adopting it but I understand if you have any reservations. It would remove significant amount of boilerplate and allow for more uniform queries.

@seivan
Copy link
Contributor Author

seivan commented Nov 14, 2022

@benwilson512 Any chance you could show me how schema compilation phases work, is there a way for us to add this to a absinthe project today?

@benwilson512
Copy link
Contributor

You can read more about compilation phases here https://hexdocs.pm/absinthe/Absinthe.Schema.html#module-custom-schema-manipulation-in-progress. I think I'd accept a PR for a one_of implementation if it is seeing adoption in other languages, but until it's part of the spec it isn't strictly an issue.

@benwilson512
Copy link
Contributor

After a bit of thought this is slightly more complicated than just a compilation phase because you need to BOTH annotate the schema node AND add runtime logic to validate queries.

Basically you want the schema level directive to annotate the schema node with metadata, and then add a pipeline phase to your Absinthe.Plug or similar to perform extra validation.

@maartenvanvliet
Copy link
Contributor

I wrote a post a while back on how to accomplish this https://maartenvanvliet.nl/2022/04/28/absinthe_input_union/ with Absinthe.

It can be accomplished right now without any changes to Absinthe itself.

@seivan
Copy link
Contributor Author

seivan commented Nov 15, 2022

I wrote a post a while back on how to accomplish this https://maartenvanvliet.nl/2022/04/28/absinthe_input_union/ with Absinthe.

It can be accomplished right now without any changes to Absinthe itself.

Wow that’s amazing. Also wasn’t aware that you could go Schema first, thanks for that as well. It’s a shame we can’t generate those hydrate calls with proper types and just fill in their bodies. I got quite used to that work flow with ts.

Thanks again!

@VictorGaiva
Copy link

@benwilson512 The group seems to be going forward with oneOf. Should we start working on bringing it to Absinthe itself?

@benwilson512
Copy link
Contributor

benwilson512 commented Jul 27, 2023

Sure, seems like it should mostly be as simple as including the parts of https://maartenvanvliet.nl/2022/04/28/absinthe_input_union/ into absinthe itself. PR welcome!

@benwilson512 benwilson512 reopened this Jul 27, 2023
@seivan
Copy link
Contributor Author

seivan commented Sep 13, 2023

@benwilson512 Hey, I just noticed you kept the ticket open, does that mean you're interested in getting this upstream?
I got the code ready, if you would just tell me where to put the various files and some other feedback.

OneOfPhase.ex

defmodule OneOfPhase do
  @behaviour Absinthe.Phase
 
  def pipeline(config, params) do
    config
    |> Absinthe.Plug.default_pipeline(params)
    |> Pipeline.insert_after(
      Absinthe.Phase.Document.Validation.OnlyOneSubscription,
      __MODULE__
    )
#...
  end

OneOfDirective.ex

defmodule AbsintheOneOf.OneOfDirective do
  use Absinthe.Schema.Prototype

  directive :one_of do
    on([:input_object])

    expand(fn
      _args, node ->
         # Which one? We need access on 
        # %Absinthe.Blueprint.Input.Object{
         #  schema_node: %{__private__: meta},
          # fields: fields
        # } = node
        # in the Phase walk. 
         # Blueprint.put_flag(node, :one_of, __MODULE__)
         # %{node | __private__: Keyword.put(node.__private__, :one_of, true)}
    end)
  end
end

@rudebono
Copy link

I faced the same issue and created

If anyone else is experiencing this problem, feel free to use.

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

No branches or pull requests

5 participants