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

Polymorphic input types #114

Closed
luisobo opened this issue Oct 29, 2015 · 18 comments
Closed

Polymorphic input types #114

luisobo opened this issue Oct 29, 2015 · 18 comments
Labels
👻 Needs Champion RFC Needs a champion to progress (See CONTRIBUTING.md) 💭 Strawman (RFC 0) RFC Stage 0 (See CONTRIBUTING.md)

Comments

@luisobo
Copy link

luisobo commented Oct 29, 2015

We have an interface Customization and around 20 types implementing this interface. The number of subtypes of Customization will grow over time, we are even considering adding subtypes dinamically.

Each concrete type has it own set of attributes.

On the query side this is easy to represent. The problem is at the mutation side. We want to have a mutation method to add a customization to another object

addCustomizationToNode(nodeId: ID, customizationInput: CustomizationInput)

but this is not currently possible to express since Input Objects cannot have interfaces, we would rather not have one mutation field per subtype, specially since those can change dynamically, potentially.

Do you have any suggestions? Would make sense to have InputObjects take interfaces?

Thank you!

@luisobo
Copy link
Author

luisobo commented Oct 29, 2015

Maybe some way of escaping the type system on input? Accepting a map os string to anything? as far as I can tell, everything in the input has to be type checked, am I missing something?

@OlegIlyenko
Copy link
Contributor

I also think that this feature would be a valuable addition to GraphQL. I have similar feature in our API.

We model our mutations as UpdateActions. So, for instance, we have a Product type. One can use update actions like SetName(newName), SetDescription(newDescription), AddPrice(price, currency), etc. to update the product. So Ideally I would like to do it with following mutation:

mutation UpdateProduct {
  updateProduct(id: "1234", version: 1, actions: [
    {type: "SetName", newName: "foo"},
    {type: "AddPrice", price: 123, currency: "USD"}
  ])
}

It is also important in our case that all these update actions come in one list, since we guarantee that they all would be applied atomically.

An ability to define an input Interface type would allow us to define ProductUpdateAction which then will have ObjectType children like SetName and AddPrice. I guess union type would also work here, but I would prefer to have an interface type in this particular case.

There is also a discussion around input UnionTypes: graphql/graphql-js#207

@xpepermint
Copy link
Contributor

+1

@siderakis
Copy link

+1

@leebyron
Copy link
Collaborator

leebyron commented Jan 9, 2016

This is something we want to consider. Interfaces may be less valuable because it becomes less clear what the semantics should be, though Unions are pretty interesting. This is all interesting area to explore further.

@GerryG
Copy link

GerryG commented Jan 19, 2016

I would think support for polymorphic types would be desired as a first class feature. I'm guessing from this issue and the comments that it isn't, yet.

@dylanahsmith
Copy link
Contributor

You could think of a union input type as an Input Object type which only allows a single field to be specified per input object value. Unfortunately that would need to be enforced with runtime checks rather than static validation.

A union input type could be represented in a similar way, where a possible type is used in place of a field, and static validation can ensure that only a single type is specified. E.g. { "MyInputObject": { "foo": "bar" } } could be used to represent the input object value using JSON or maybe ["MyInputObject", { "foo": "bar" }] if you think about it as a tuple. A GraphQL literal would have more options for how to represent the union type, e.g. MyInputObject({ foo: "bar" }) could be one option.

@bwaidelich
Copy link

There is another (pretty simple) use case for this feature: a required input parameter that can have one of two possible scalar values.
We could use this to retrieve a node from a tree either by it's path or by a unique identifier. Right now we need to use a string type and do the conversion in the resolving part or have two optional scalar parameters and return an error at runtime if both are missing..

I've seen that the SWAPI-Example suffers from the same issue (with id vs filmId)

@olange
Copy link

olange commented Apr 10, 2016

Same use case as @bwaidelich for me: I'm retrieving an entity from either one of its alternate identifiers.

Currently the eid argument is declared as a GraphQLID (a string merely) and I'm identifying the type of the entity identifier in the resolving part.

But I'd like to use a GraphQLUnionInputType for these eid arguments, that would have a resolveType() function to identify the type, which GraphQL could use to cast and validate the input value accordingly. It would allow to catch the cases where the entity id was not valid.

Current schema

    ...
    product:
      type: productType
      description: "Retrieves product details, given either a Portfolio or Produit entity id."
      args:
        eid:
          type: new GraphQLNonNull( GraphQLID)
          description: "Product entity identifier, or entity identifier of one of its constitutive portfolios."

Query:

query productByCanonicalEID {
  product( eid: "ENTITY-ID-IN-CANONICAL-FORM") { ...ProductFull }
}

query productByAlternateEID {
  product( eid: "ENTITY-ID-IN-ALTERNATE-FORM") { ...ProductFull }
}

Desired schema

    ...
    ProductEntityIDType = new GraphQLScalarType { ... }
    PortfolioEntityIDType = new GraphQLScalarType { ... }

    entityIDUnionInputType = new GraphQLUnionInputType {
      name: 'EntityID'
      types: [ ProductEntityIDType, PortfolioEntityIDType ]
      resolveType(value) {
        # Lets assume the entity identifiers obey a naming convention,
        # they start with specific letters, which we can derive the type from
        if value.startsWith( 'PO')
          return PortfolioEntityIDType
        if value.startsWith( 'PR') 
          return ProductEntityIDType
      }
    }
    ...
    product:
      type: productType
      description: "Retrieves product details, given either a Portfolio or Produit entity id."
      args:
        eid:
          type: new GraphQLNonNull( entityIDUnionInputType) # <– Desired change
          description: "Product entity identifier, or entity identifier of one of its constitutive portfolios."

Query: the same, but argument eid would now be validated and be cast to one of the known entity ID types.

@Cardinal90
Copy link

Cardinal90 commented Apr 27, 2016

Also was kinda surprised that it is not available. Though I understand why that is. Unlike with queries, this will probably have to be supported by syntax. Something like (using example above):

product( eid#alternate: "ENTITY-ID-IN-ALTERNATE-FORM") { ...ProductFull }

As I could not find a ready solution, I made my own with some workarounds using existing syntax: Union Input Type. You could probably also use it in place of an interface.

@01101101
Copy link

01101101 commented Jul 3, 2017

Any news on this? It will be really helpful for many people I think.

@ibigpapa
Copy link

Did this ever make it to the roadmap?

@leebyron leebyron added 👻 Needs Champion RFC Needs a champion to progress (See CONTRIBUTING.md) 💭 Strawman (RFC 0) RFC Stage 0 (See CONTRIBUTING.md) and removed enhancement labels Oct 2, 2018
@jodinathan
Copy link

I have a pagination input type:

{
  sortBy: 'someField',
  direction: 'ASC',
  after: 'someValue'
}

sortBy can be reference any type of field, thus, the after property must be a value of the referenced field.
Example for a list sorted by a creation date:

{
  sortBy: 'createdAt',
  direction: 'ASC',
  after: '2001-01-01 00:00'
}

I can't set the schema to validate after as a Date type because the sortBy can reference a String field or a number field.
An interface would help a lot here. A date pagination for example:

{
  __typename: 'DatePagination'
  sortBy: 'createdAt',
  direction: 'ASC',
  after: '2001-01-01 00:00'
}

With this, the after field can be validated.

@jodinathan
Copy link

jodinathan commented Oct 17, 2018

https://graphql.org/learn/pagination/

Especially if the cursors are opaque, either offset or ID-based pagination can be implemented using cursor-based pagination (by making the cursor the offset or the ID)

So the cursor can be an ID (lets presume some UUID) and an offset (a number).
This is an interface and it can not be validated by the schema.

@kmanev073
Copy link

It is 2019, we need this...

@benjie
Copy link
Member

benjie commented Apr 30, 2019

Could you see if the solution discussed in #395 (comment) would solve your needs? It's due to be discussed at the next WG.

@kmanev073
Copy link

@benjie It looks great, I think it would do the job.

@IvanGoncharov
Copy link
Member

We have begun building a single RFC document for this feature, please see:
https://github.com/graphql/graphql-spec/blob/master/rfcs/InputUnion.md

The goal of this document is to consolidate all related ideas to help move the proposal forward, so we decided to close all overlapping issues including this one.
Please see discussion in #627

If you want to add something to the discussion please feel free to submit PR against the RFC document.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
👻 Needs Champion RFC Needs a champion to progress (See CONTRIBUTING.md) 💭 Strawman (RFC 0) RFC Stage 0 (See CONTRIBUTING.md)
Projects
None yet
Development

No branches or pull requests