I would need GraphQLUnionType to work also as an input type. I need to pass a list of different conditions to the server.
Example (start processing products where (A or B and C) - order is important):
mutation RootMutation {
startProcessing({
kind: "product",
filter: [
{kind: "A", period: "1 min"}, // type1
{operand: "or", kind: "B", tag: "smart"}, // type2
{operand: "and", kind: "C", tag: "small"} // type2
]
}){kind}
}
Interesting - I'd like @dschafer to see this as well.
I know at FB we've stayed away from this sort of input flexibility as it leads to more complex and easy to mess up server side implementations and in some cases can lead to ambiguities where it's not possible to determine which type in the input you meant, but I definitely understand why this could be valuable.
Great!
Yeah, we've definitely seen cases where this would be useful. We've represented these as input objects that basically simulate tagged unions, and have helper methods internally that validate the inputs (that only one field on the tagged union is set).
@lolopinto has thought a lot about this, I'd like to get his thoughts, but this seems like an idea we should pursue. My initial thought is that a GraphQLUnionType that only contains input types would be considered a valid input type?
Btw, having the interfaces concept for GraphQLInputObjectType would be great.
We have a use case for this as well. We are building a generic CRUD GraphQL api. one of the operations we are supporting is linking two objects like this:
mutation {
createUser(name: "carl", cityId: "cityId")
}
We would like to support creating the city inline like this:
mutation {
createUser(name: "carl", city: {name: "Berlin", knownFor: "cool GraphQL people"})
}
Ideally we would like the type of the city input field to be {id!} || {name!, knownFor}. It seems like input Union Types would support this.
I've ran into another use case where unions for input types would be useful.
I'm adding a GraphQL mutation in Reindex for authenticating with a social access token (Facebook Login, Twitter etc). The problem is that different providers require different input parameters:
These difference mean that without union input types, I would have to create separate mutation for each login provider we support and plan to support in the future. This also wouldn't be very good for code reuse on the client-side, e.g. each provider would require a separate Relay mutation.
@leebyron mentioned union types can lead to ambiguities where it's not possible to determine the type. This gave me an idea: what if GraphQL input types would only support disjoint unions (like those supported in Flow)? This would avoid the potential ambiguities, yet allow expressing the input type for cases like I've described above like this:
union LoginInput = FacebookLoginInput | GithubLoginInput | TwitterLoginInput
input FacebookLoginInput = {
provider: "facebook"
accessToken: String
}
input GithubLoginInput = {
provider: "github"
accessToken: String
}
input TwitterLoginInput = {
provider: "twitter"
accessToken: String
accessTokenSecret: String
userID: String
}
It's possible to emulate these with nested input objects and runtime checks, as @dschafer described above, but being able to express them in the type system would enable better type checking and make the API more self-documenting than relying on out-of-band information ("always set only one of these fields").
@leebyron Any chance this would be considered in the future?
Yes, I still think this idea is interesting, though the tricky part is definitely coming up with the right semantics and validation for disambiguating within input unions.
e.g.:
union MyUnion = Person | Place | Thing
input Person {
name: String
birthdate: Int
}
input Place {
name: String
lon: Float
lat: Float
}
input Thing {
name: String
weight: Float
}
Should this be legal? If so, then what should GraphQL do with { name: "Santiago" }? Should that be legal? If so, what type does it use for coercion?
Also, one of the design principles for GraphQL is to bias towards simplicity. Any time an idea can be expressed using less concepts we should use less concepts, even if that means being slightly more awkward.
Biasing towards simplicity lowers the barrier to entry for other implementers or adopters of GraphQL, the value of which should not be underestimated.
For example, @fson's example above is perhaps a fine example of how input unions could be useful, however I could also represent the same thing with:
input LoginInput {
provider: String! # or maybe an enum
accessToken: String
accessTokenSecret: String
userID: String
}
Does that make it a bit harder to describe that userID is only necessary when provider is "twitter"? Yes, I think so. But is it simpler? Yes, definitely! One type instead of four and one fewer concept.
Part of the reason why this issue has not been a priority is there still is not a set of truly compelling examples where input unions would unlock the ability for GraphQL to express APIs that it cannot yet express today.
It's easy to come up with example use cases for any given idea, but as long as each of those examples can be reasonably represented with the existing features then they're not that compelling an argument for adding complexity.
Thanks Lee!
I appreciate the strive for simplicity and see how allowing unions of types that can not be disambiguated is a problem.
The primary value union input types would unlock for us is being able to express in the typesystem that exactly one of two fields has to be supplied. As is the best we can do is make both fields optional and return a runtime error if none or both is supplied. (as in my example above)
It might be that union input types is not the best way to express this and I would be happy to consider other existing or potential constructs we could use to achieve the same .
+1
I have different types of filters, like string, number, date, regexp, set and so on. And I want to be able to have an Input Union Type across all of these types to pass them as arguments to my query.
I'd also like to see this happen. For now, I am using https://github.com/Cardinal90/graphql-union-input-type as a temporary solution.
I was considering this approach for supporting different types of pagination. We have customers that have varying technical capability and needs so the idea was to accept a couple types like OffsetConfig | CursorConfig | TimeConfig for a PaginationConfig field.
Though doable the way @leebyron describes it seems like it would be pretty messy and make validation more difficult to have all the possible fields in one config object.
I understand the simplicity argument, but I think you could argue its more simple to have a smaller set of types work universally across inputs and responses, etc. I went forward doing it without a second thought and was surprised when it didn't accept it.
I'm relatively new to GraphQL so forgive my naivety but I don't understand why we have to explicitly define 'InputTypes' versus being able to set an 'isInputType' (boolean) property on any type definition (be it ObjectType, UnionType, etc.). It would be simple(r) all round and shouldn't mess up parsing & validation.
@emrul it's nicely explained http://graphql.org/graphql-js/mutations-and-input-types/
Hi @leebyron
Biasing towards simplicity
Understood, but couldn't you apply this principle to not having unions in the first place? (And your argument to merge @fson's login types above)?
Unions are useful -- and of course the thread applies to interfaces as well!
I'm using https://github.com/Cardinal90/graphql-union-input-type (thanks @fubhy for the link) but it adds considerable complexity to rely on third-party extensions.
there still is not a set of truly compelling examples...
Suppose you have heterogeneous objects that can be fields of a common container data type.
union DataUnion = Contact | Event | Org | Task | Project | Document
type SearchResult {
title: String!
data: DataUnion!
}
How would I create a common mutation to create a search result? Our current approach is to convert all of the union subtypes to be Nodes and pass the ID as part of the SearchResult? But is this example any more compelling.
Thanks very much.
Part of the reason why this issue has not been a priority is there still is not a set of truly compelling examples where input unions would unlock the ability for GraphQL to express APIs that it cannot yet express today.
I don't know if this is compelling or not, but one use-case I have is to be able to define a union type for an input argument used for filtering results.
The reason a union input type would be useful is because I have implemented advanced operators besides just a simple equality match, but this has required me to make simple equalities more complicated. How I've done this is allow the user to specify an object where each key is an operator and the value is the value to apply to that operator. For example:
{
"filter": {
"numFlags": { "gt": 0 }
}
}
This would tell the query to search for items that have a numFlags field that is greater than 0 (the syntax is similar to MongoDB). To accommodate simple equalities, I've added an eq key:
{
"filter": {
"numFlags": { "eq": 0 }
}
}
This makes doing simple equalities a little more verbose than being able to simply specify:
{
"filter": {
"numFlags": 0
}
}
Therefore, preferably, I'd be able to specify the numFlags argument as a union type of: GraphQLInt | ComplexFilter, where ComplexFilter is a GraphQLInputObjectType that contains the various operators that can be applied to that field.
This isn't a showstopper for me, as I do have the workaround of using the eq operator for simple equalities. But it'd be nicer to be able to support a simple key/value pair for the most common use-case of simple equalities. I've read some other proposals to handle operators, such as defining additional fields such as numFlagsGreaterThan and such, but I don't really like polluting my argument list with a bunch of these operator-named arguments.
I'd love to see this feature. Here's my use case:
What I'm building is conceptually similar to IFTTT. Imagine trying to create IFTTT Applets, which can be for any number of services, all of which take different options and associate with different entities in the system. As I understand it, my current choices are:
options, and then going through a complicated process on the backend to ensure that the options provided are correct for the specific type of service being integrated with.The issue isn't just on creation of these entities. I'd also need redundant APIs for update mutations. On queries, I can model this perfectly using Interface and/or Union types. But there's a huge mismatch between the types that the server can render and those that it can accept as input.
I'd really like to see another option where I can have a single API endpoint that is properly typed, and the input args can just be required to fit a certain Interface type, or at least a Union type.
I know there's a desire to avoid additional syntax for specifying which type of input is actually being provided. The best answer I could come up with there is for the query to specify the concrete type being provided rather than the interface type. But that does mean client queries are no longer completely static in my case, or I'd need a separate query for every type of input I might provide. Maybe others have ideas.
I would also love to see support for unions in input types, and I think tagged/discriminated unions would remove the disambiguation problem.
Also feel the need for such features. But reading through this thread, isn't the best option to get rid of the input type?
Part of the reason why this issue has not been a priority is there still is not a set of truly compelling examples where input unions would unlock the ability for GraphQL to express APIs that it cannot yet express today.
Pollution of schema introspection
If I want to accept Int or String for input arg, GraphQL does not allow to do it. For solving this case we should create two args, eg: argAsInt and argAsString. So we are forced to pollute our schema, instead of one argument, we created two.
Leak in schema introspection
If some argument can be different types eg. string or string[]. We can use JSONGraphQLType for avoiding creation of several args and keeping our schema shapely (without fat). So we allowing to client provide any JSON as value for the argument but lose client validation that can be accepted only string or string[].
Ambiguity in filtering for different graphql API providers
Eg. we have a price argument which can be searched by exact value. So if we want to add ability search by range or operators >, <, >=, <= or may be something complex price === 100 || (price > 400 && price < 500). We can not just extend input type for price, we are forced to create new argument priceByOperators.
Ok, what if client make such request
find({ price: 100, priceByOperators: { gt: 400, lt: 500 }}
some server implementations may:
a) throw error
b) use OR
c) use AND to perform the query.
GraphQL spec does not cover this ambiguity, but can.
Union input type with type field or resolveType function
Realy I can not imagine how it can be described in the introspection json file, eg. for validation in Graphiql or other tool. How can be validated such input args on the client (not server side)?
Ok, with type field (eg provider in @fson example) it somehow can be done.
But with resolveType function (temporary solution from @fubhy) it is impossible.
Disambiguating within input unions with different fields
From @leebyron comment what should GraphQL do with { name: "Santiago" } provided for MyUnion?
union MyUnion = Person | Thing
input Person { name: String, birthdate: Int }
input Thing { name: String, weight: Float }
From one side it should throw an error, that can not determine the exact type of arg. But as you can see all fields is not required. So no error, or we should make disjoint fields required? Hm, too tricky question...
From another side, in such case, we may return MyUnion without determining exact type. Let developer's code decide what to do. Again "tricks with sticks".
@calebmer interfaces for input fields will have the same problems.
InputMapBy data types that present in JSON quite easy to make coercion on server/tools implementation. Also easy to put this info to introspection.
In the GraphQL schema language it can be expressed in such way:
inputMap PriceInputMap {
asNumber: Int
asString: PriceAsStringWithCustomCoercionScalar
asBoolean: Boolean
asArray: ListOfPricesInput
asObject: PriceFilterByOperatorsInput
asNull: RandomPriceCoercionScalar
}
In js it can look like:
const PriceInputMap = new GraphQLInputMapType({
name: 'PriceInputMap',
asNumber: GraphQLInt,
asObject: new GraphQLInputObjectType({ name: 'PriceFilterByOperatorsInput', fields }),
// if map for other native types is not provided, then such values are not permitted
});
I think that suchGraphQLInputMapType can solve problems which I describe above for client-side and server/tool-side.
Thoughts?
Joining the š here o/
I'm currently using graphql as a front for several backends (I believe this is common ground anyway), and for SQL I'd like to delegate some work to the ORM I am using (namely: sequelize).
I'm trying to find a good solution to use the where clause from the ORM but every field has to be named explicitely like so:
Post.findAll({
where: {
authorId: 2
}
});
I'd like my users to be able to do something like :
{
posts(where : { authorId : 42 })
}
It would be simple here as I could simply sign the method like that : posts(authorId: Int!) : [Post].
But if I want more complex cases (like comparing values or wanting intervals of values like mentioned in the previous comment) it's going to make my graphql schema unecessarily tedious and big.
I'm joining this discussion as it seems to be exactly the same problem, or am I doing something wrong?
@leebyron, here's my compelling example.
Let's say i'm building a page editor with widgets. I have about 30 widgets all
sharig the Widget interface. I.e. TextWidget, ImageWidget.
I want to add widgets to the page. Ideally i want to persist the whole tree at once, because the page is a JSON document anyway. This is also super simple with react to mutate. Just a simple json tree.
Saving a page:
savePage(pageId: 5, widgets: [
{text: "A lot of text"}
{image: "http://omgcats.com/cat.png", title: "a cat"}
{image: "http://omgcats.com/cat.png", title: "a cat"}
{title: "A simple form", inputs: [
{text: "Fill in your name?"}
{number: "What is your favourite number?"}
]}
])
I need to make individual calls instead of just sending of a big state tree. This makes it very hard to make a simple editor for it because i now need to track things. Else i can just make a simple JSON tree send that over and let the graphql validator do its magic.
formId = addForm(pageId: 5, input: {title: "Asimple form"})
addFormText(id: formId, text:"Fill in your name")
addFormNumber(id: formId, number: "Fill in your name")
saveTextWidget(pageId:5,input: {text: "A lot of text"})
addImageWidget(pageId:5,input: {image: "http://omgcats.com/cat.png", title: "a cat"})
addImageWidget(pageId:5,input: {image: "http://omgcats.com/cat.png", title: "a cat"})
saveFormWidget(pageId:4, formId: formId)
I can ofcourse make a map. Whereas WidgetInput has every single possible input listed. But isn't really that pretty.
WidgetInput {
text: TextWidget
image: ImageWidget
#the other 30 wigets..
}
Does this clarify things/makes sense?
I remember seeing this ticket awhile back but just thought about it again now as I've been using flowtype and the excellent apollo-codegen to automatically generate type definitions for queries/variables.
They've recently landed the ability to use __typename to be able to refine the union/interface exact-type'd definitions and it got me thinking maybe the answer here is as simple as requiring that __typename be used in the same way but on the input for any case where a union or interface is used.
So to @leebyron's sample schema above:
union MyUnion = Person | Place | Thing
input Person {
name: String
birthdate: Int
}
input Place {
name: String
lon: Float
lat: Float
}
input Thing {
name: String
weight: Float
}
mutation ExampleMutation($input: MyUnion) {
someValue
}
Any of the following would be valid inputs:
{__typename: 'Person', name: 'Tim', birthdate: 26}
{__typename: 'Place', lat: 40.71, long: 74.00, name: 'NYC'}
{__typename: 'Thing', weight: 30, name: 'Cat'}
It's not pretty, but it's explicit, simple to type check and flexible.
I would love the ability to have Union Input types - it would let me make a mutation schema that is analogous to my query schema. However, I am concerned that if FB have no need for this in their own setup there'll be little impetus to provide this type of support. On the other hand, FB clearly don't have this problem so I'd love to know what elegant method they employ themselves.
I'd love union input types as well, the typing of graphql is really important for us to have a concrete API and union input types would go a long way in providing more semantic meaning.
This is definitely an issue for us too, with almost exactly the same use case (widgets for a page builder / headless CMS). We ended up deciding to use GraphQL for querying but generic REST-ish endpoints for mutations to work around this limitation.
Handling unions on Inputs would be great indeed. I wouldn't be against Input interfaces too !
I'd like to testify a use case to further this discussion.
When querying an object, we can allow users to pass a filter object like so:
{
Person(filters : {
city : "New York",
occupation : "student"
}) {
name
}
}
Here we use the String type. But it would also be nice to accept a List of Strings for multiple possible matching values.
{
Person(filters : {
city : ["New York", "San Diego"],
occupation : "student"
}) {
name
}
}
We can enforce using a List in the case of a single input, but that is non-intuitive. Providing both the option of a String or List of Strings via a Union would be helpful. Let me know if anyone has a clever way to otherwise implement this.
@nightCapLounge Your use case already handled by GraphQL.
http://facebook.github.io/graphql/October2016/#sec-Lists
If the value passed as an input to a list type is not a list and not the null value, it should be coerced as though the input was a list of size one, where the value passed is the only item in the list. This is to allow inputs that accept a āvar argsā to declare their input type as a list; if only one argument is passed (a common case), the client can just pass that value rather than constructing the list.
That means if you define city as [String] both queries will work:
{ Person(filters : { city : "New York", occupation : "student" }) { name } }
{ Person(filters : { city : ["New York", "San Diego"], occupation : "student" }) { name } }
Not sure why there's so much resistance on this issue. Here's another use case where Union input would be desired:
Application allows you to define a SET of users, based on different criteria.
user_set = [
{user: 'some_userid'}, // a specific user
{group: 'some_group_id'}, // all users belonging to a group
{manager: 'some_userid'}, // all users who report to this manager
{filter: 'a search filter'} // users who meet this search filter
]
The contents of the user_set array could be any number of those items.
Here are my own particular use cases for where this would be handy; hopefully it's of some value to the team in evaluating the usefulness of this.
I've been working on generating a graphQL endpoint for Mongo https://github.com/arackaf/mongo-graphql-starter
and I frequently run into places where I need one of two forms for an input. For example, to sort by a single field, I can do
SORT: { name: 1 }
but if I need to sort by multiple fields I unfortunately can't do
SORT: { name: 1, birthday: -1 }
since the fields don't seem to reliably deserialize over the wire into an object with the keys added in the correct order (how Mongo / BSON handles this) so I wound up working around by also supporting a
SORTS: [{name: 1}, {birthday: -1}]
note the plural spelling - it would be great to have a single argument that can either support an object, or an array of objects.
Similarly, for mutations I have things like
comments_PUSH: {text: "C2"}
which pushes an object onto a nested array in Mongo. It would be great if that field could also accept an array of those same objects. Instead I had to again go with a separate input field, which only bloats the API
comments_CONCAT: [{text: "C2"}, {text: "C3"}]
For anyone else struggling with this, as a workaround, I used a JSON scalar type (https://github.com/taion/graphql-type-json)
Custom scalars are definitely a valid workaround, but make sure they donāt invalidate any security assumptions youāve made (check out this great post on potential security pitfalls).
Hey all! I've got a similar situation on my hands where a union input type would be handy. In pseudocode using no particular syntax, the schema of the object I wish to upload via mutation looks something like this:
Mutation: {
dueDate: date,
commodity: string,
weight: float,
legs: List<RailLeg | TruckingLeg>,
}
RailLeg: {
port: ...,
inlandPort: ...,
}
TruckingLeg: {
port: ...,
deliveryAddress: ...,
}
Basically, I've got a list of heterogeneous entries that together form a sensible collection, but whose internal structure differs. I'd like to use GraphQL to enforce that each member of that list can be typed according to one branch of the union type, but without union type support as inputs, I cannot.
However, I've found a good substitute that may be useful to other Ruby backend users. I wrote the GraphQL input type as the union of all possible keys for all possible union type branches (currently 13 distinct keys covering 5 different branches in my real project), and I'm running each member of the list through a dry-struct type, one struct type per branch, to enforce schema compliance. It's not ideal because I have to list the struct members twice ā once as the GraphQL input type and once as the dry-type ā but it's been working quite well.
It seems like the pattern for the web at large is to have more controlled, structured inputs for html content, but thereās not really any good way to validate blocks of html input right now. GraphQL input unions would allow for structured inputs AND true server side validation on the āblocksā as they are stored and converted into something like html.
WordPress, for example, is in the process of building a new Block Based editor, āGutenbergā and I would love to be able to have WPGraphQL (which I maintain) integrate with the new block-based Gutenberg editor.
The blocks in Gutenberg can be registered with various inputs, and at the moment all block input gets converted to one big string and is saved, with no real block level validation or real way to interact with a single block.
Ideally, with a block based editor like Gutenberg, each block should have its own schema for querying, but _also_ for mutating.
The query part is easy with unions, as each block that was saved is of a specific block type. . .but without input unions, the mutation becomes extremely difficult.
The client knows which type (__typename) each block is, so when mutating it would be easy for the client to pass back the blockās typename along with whatever other input goes along with the block.
Ideally, for WPGraphQL + Gutenberg (and it seems like many other projects) it would be sweet to be able to do mutations with a listOf InputUnions.
Looking forward to what comes of this!
Just opened a PR proposing an inputUnion type - would love to get feedback from anyone on the idea!
@tgriesser it looks like there's a related issue on the GraphQL spec but no actual RFC. I'll bet that any insight you gained while working on your implementation would be helpful over there as well!
Why cannot we use resolveType or isTypeOf method to resolve the input polymorphism, similar to output polymorphism? I think that the most relevant way should be giving chance to the developer to decide which type would they cast.
resolveType?: (value: any, info?: GraphQLResolveInfo) => ?GraphQLObjectType;
In most of cases we use some meta-property for distinguish polymorphism, and usually we persist this property in database too, so a resolveType implementation would be easy, and even developers can choose a fallback strategy too:
resolveType: value => {
switch (value.type) {
case 'A': return A;
case 'B': return B;
default: return A;
}
}
or
isTypeOf: value => value.myFunnyType === 'A'
or
isTypeOf: value => 'xyz' in value
I think, GraphQL is really powerful at the really complex data models, and complex data models can be strongly polymorph. I love GraphQL, but the lack of this feature is disincentive for me to use GraphQL for my complex projects.
I end up solving this more like protobuf, where I make a mega-type that has all the children, then just fill in the one I care about:
enum RecordType {
User
App
Search
Experiment
}
union AdminItem = User | App | Search | Experiment
type AdminList {
results: [AdminItem]
stats: QueryStats
}
input AdminItemInput {
User: UserInput
App: AppInput
Search: SearchInput
Experiment: ExperimentInput
}
type Query {
# Get a single record.
adminGet(id: ID!, type: RecordType!): AdminItem
# List records of a specific type.
adminList(type: RecordType!, start: Int, count: Int): AdminList
}
type Mutation {
# Delete a record.
adminDelete(id: ID!, type: RecordType!): Boolean
# Create/update a record.
adminEdit(record: AdminItemInput!): AdminItem
}
Then in my resolvers, I do something like this:
export default {
Query: {
adminGet: (_, {id, type}, {models}) => models[type].get(id),
adminList: (_, {type, start, count}, {models}) => models[type].findAll({}, start, count)
},
Mutation: {
adminDelete: (_, {id, type}, {models}) => models[type].del(id),
adminEdit: async (_, {record}, {models}) => {
const k = Object.keys(record)
if (k.length > 1) {
throw new Error('You may only edit one record at a time')
}
const type = k.pop()
let oldRecord = {}
if (record[type].id) {
oldRecord = await models[type].get(record[type].id)
if (!oldRecord) {
throw new Error('Item not found')
}
}
const newRecord = {...oldRecord, ...record[type]}
await models[type].save(newRecord)
return newRecord
}
},
AdminItem: {
__resolveType: (obj, context, info) => {
if (obj.email) {
return info.schema.getType('User')
}
if (obj.term) {
return info.schema.getType('Search')
}
if (obj.storeID) {
return info.schema.getType('App')
}
if (obj.object) {
return info.schema.getType('Experiment')
}
return null
}
}
}
AdminItem.__resolveType can tell the difference between User | App | Search | Experiment, so it works ok. This seems like a bit of a hack, and it'd be nice to be able to just say "it's one of these types for input" like we say "it's one of these types for output". Also, if I need to go very deep, it gets super-messy, fast.
Note: I left out the User | App | Search | Experiment and UserInput | AppInput | SearchInput | ExperimentInput definitions
Also, it has a few assumptions that if not correct would screw everything up:
AdminItem from the field-values (this could also be a type field or something)models in context are named the same as the types (User | App | Search | Experiment)id fieldTo add another perspective to this thread, for me lack of input union support is less about enabling something that isn't possible with GraphQL via workarounds but more about removing objections to migrating the Salsify APIs from REST to GraphQL. One of the really appealing things about GraphQL is the type system and everything it enables - client side type checking, better API documentation, auto-complete in GraphQL editors, framework validation of incoming requests, etc. This has the potential to really improve our internal developer productivity and make it much easier for customer/partner developers to use our APIs in their preferred language/framework with minimal friction. Unfortunately we have several examples in our domain (product information management and syndication) where an entity has an attribute that is a collection of structured values with heterogeneous types and our save UX/desired API usage calls for saving the entire entity atomically.
Here's a simplified version of one such example representing the configuration of a product catalog that has an ordered collection of sections which can display an image carousel, a list of products or a spotlight for an individual product:
type Image {
# ...
}
type ImageCarousel {
images: [Image!]!
}
type ProductList {
productFilter: String!
productProperties: [Property!]!
productsPerPage: Int!
}
type ProductSpotlight {
product: Product!
productProperties: [Property!]!
numDisplayedImages: Int!
}
union Section = ImageCarousel | ProductList | ProductSpotlight
type Catalog {
id: String!
name: String!
sections: [Section!]!
}
type Query {
catalog(id: String!): Catalog
}
input ImageCarouselInput {
imageIds: [String!]!
}
input ProductListInput {
productFilter: String!
productPropertyIds: [String!]!
productsPerPage: Int!
}
input ProductSpotlightInput {
productId: String!
productPropertyIds: [String!]!
numDisplayedImages: Int!
}
inputUnion SectionInput = ImageCarouselInput | ProductListInput | ProductSpotlightInput
type CatalogInput {
id: String!
name: String!
sections: [SectionInput!]!
}
type Mutation {
updateCatalog(catalog: CatalogInput): Catalog
}
schema {
query: Query
mutation: Mutation
}
Reading through this issue it looks like we've got a few potential workarounds:
1) Make SectionInput a tagged union with fields for imageCarousel, productList and productSpotlight
2) Make SectionInput a union of all the fields on ImageCarouselInput, ProductListInput and ProductSpotlightInput
3) Don't try to model SectionInput with the GraphQL type system and use a JSON literal instead
4) Use schema directives to simulate input unions
5) Relax our requirement that all sections in a catalog can be updated atomically and expose mutations like setImageCarouselSection(catalogId: String!, sectionIndex: Int!, imageCarousel: ImageCarouselInput!)
Option 5 is a non-starter since it doesn't fit with how our consumers actually want to use our API.
Option 4 is intriguing but we'll have minimal control over the tool chain used by 3rd party developers which makes adopting anything non-standard less appealing.
Options 1, 2 and 3 boil down to the same problem: the GraphQL type system wouldn't actually describe what's possible with our API, which is one of the main reasons we were considering adopting GraphQL to begin with. Anything in the tool chain that relies on schema introspection (e.g. auto-complete in GraphQL editors, linting of client queries, etc.) won't represent the "real" type system of our API which will hurt the productivity of both internal developers and may lead to 3rd party developers not using our APIs. Furthermore we're stuck with a difficult decision when it comes to queries and mutations: we're either inconsistent between the two (beyond the usual GraphQL conversion of nested entities to foreign keys), or we're consistent between them, avoid the use of unions on the query side, and then we have the same set of issues with developer ergonomics and the client toolchain there as well.
At the end of day, it's going to be much harder for me to sell a GraphQL migration internally unless I can demonstrate a big improvement in developer experience over our current REST APIs, which is going to be tough without input unions to model some really important constructs in our domain.
Here you guys are facing to a huge design misconception | internal flaw to the assumption made by the GraphQL authors:
validation = typing
All the workarounds posted here are ugly.
You can't patch|fix a bad language design by code at least at the price to get some even uglier and unmaintainable code base.
Maybe a code generation tool for GraphlQL will be OK to handle & hide this.
But handwritten GraphQL server will not.
GraphQL authors: validation = typing
agreed on this one.. simplicity helps adoption but it has its limits.
This discussion is very important not only for graphql-js but for the entire GraphQL ecosystem so it should be discussed in the context of GraphQL specification.
Please discuss it in: https://github.com/facebook/graphql/issues/488
Most helpful comment
I've ran into another use case where unions for input types would be useful.
I'm adding a GraphQL mutation in Reindex for authenticating with a social access token (Facebook Login, Twitter etc). The problem is that different providers require different input parameters:
These difference mean that without union input types, I would have to create separate mutation for each login provider we support and plan to support in the future. This also wouldn't be very good for code reuse on the client-side, e.g. each provider would require a separate Relay mutation.
@leebyron mentioned union types can lead to ambiguities where it's not possible to determine the type. This gave me an idea: what if GraphQL input types would only support disjoint unions (like those supported in Flow)? This would avoid the potential ambiguities, yet allow expressing the input type for cases like I've described above like this:
It's possible to emulate these with nested input objects and runtime checks, as @dschafer described above, but being able to express them in the type system would enable better type checking and make the API more self-documenting than relying on out-of-band information ("always set only one of these fields").