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

Only common values are being considered when merging enum or input types #1763

Closed
AakashSorathiya opened this issue Apr 22, 2022 · 4 comments · Fixed by #1839
Closed

Only common values are being considered when merging enum or input types #1763

AakashSorathiya opened this issue Apr 22, 2022 · 4 comments · Fixed by #1839
Assignees

Comments

@AakashSorathiya
Copy link

I am using federation v2. I have defined one enum type that is shared in two subgraphs. Now according to docs the composition of supergraph sdl should merge this enum from both subgraphs and it should contain all the values provided in subgraphs. But supergraph schema is having only common values between the two.

Input

subgraph schema 1

enum Color {
  RED
  GREEN
  BLUE
}

subgraph schema 2

enum Color {
  CYAN
  MAGENTA
  YELLOW
  RED
}

supergraph schema generated

enum {
  RED
}

supergraph schema expected

enum Color {
  CYAN
  MAGENTA
  YELLOW
  RED
  GREEN
  BLUE
}

And if there is no common value between the two then composition throws error like

None of the values of enum type "Color" are defined consistently in all the subgraphs defining that type. As only values common to all subgraphs are merged, this would result in an empty type.

Same is happening with input types also.

I am using InterospectAndCompose API for composing supergraph sdl.

Dependencies

"dependencies": {
    "@apollo/gateway": "^2.0.1",
    "apollo-server": "^3.6.7",
    "apollo-server-types": "^3.5.2",
    "express": "^4.17.1",
    "express-graphql": "^0.12.0",
    "graphql": "^16.0.1"
  }
@clenfest
Copy link
Contributor

Thanks for submitting @AakashSorathiya . It looks like this is a documentation bug as we slightly changed how enum types are merged during preview. While we're working on it, the details of the change are in this PR: #1672

The very short version is that output types are merged by union and input types are merged by intersection (which is the safest choice we can make).
Best,
Chris

@AakashSorathiya
Copy link
Author

Thanks for the update @clenfest. I have one question related to this only. I want to apply filter on the property which is present in subgraph 1 only and filters are defined using input types.

Example

subgraph 1

type Movie @key(fields: "id") {
    id: ID!
    name: String
}

input _MovieFilter {
    id_matches: String
    name_matches: String
}

type Query {
    movie(filter: _MovieFilter): [Movie]
}

subgraph 2

type Movie @key(fields: "id") {
    id: ID!
    title: String
}

input _MovieFilter {
    id_matches: String
    title_matches: String
}

supergraph schema will have intersection of input types

input _MovieFilter {
    id_matches: String
}

So here I will be able to apply filter on id only but I want to apply filter on name.
So is this possible in any way?

Dependencies are same as mentioned above.

@pcmanus
Copy link
Contributor

pcmanus commented Apr 26, 2022

So is this possible in any way?

There is no real way to split the definition of an input object type accross subgraph, at least not at the moment, no, because we wouldn't have a good way to handle it at runtime.

But in your example, I'm not entirely sure to understand how you'd like it to work.

That is, the movie(filter: _MovieFilter) query is defined in "Subgraph 1", so "Subgraph 1" is the one that will have to resolve that query at runtime. Let's assume that you could define _MovieFilter as in your example, this would mean that "Subgraph 1" might receive a request like:

{
  movie(filter: { title_matches: "Lord of the ring" })
}

For "Subgraph 1" to have a chance to handle such request, it needs to know that title_matches is a kind of "movie filter" and have logic to deal with it. If that's the case, then I'd argue that it make sense to define the title_matches in "Subgraph 1".

The same reasoning kind of apply to "Subgraph 2". More precisely, your example defines _MovieFilter in "Subgraph 2" but does not use it, and I assume you just shortened your example, but there is essentially 2 cases: either _MovieFilter is truly not used in "Subgraph 2", but then I'd suggest not declaring it at all there.

Or _MovieFilter is used by some other query that isn't shown in you example, meaning that "Subgraph 2" is more like:

type Movie @key(fields: "id") {
    id: ID!
    title: String
}

input _MovieFilter {
    id_matches: String
    title_matches: String
}

type Query {
  movie2(filter: _MovieFilter): [Movie]
}

In that case, there is again 2 subcases: either movie2(filter: _MovieFilter) should handle filters with name_matches, and in that case I would argue that name_matches should be listed in the definition of _MovieFilter in "Subgraph 2" (which means "_MovieFilter" should have the same definition in both subraphs, because both subgraphs ultimately handle all types of filtering).

Or, you mean movie to only handle filtering by "id" and "names", while movie2 only handle filtering by "id" and "title", and in that case, my suggestion would be to use 2 different types in each subgraphs. That is, the subgraphs would look like:

# Subgraph 1
type Movie @key(fields: "id") {
    id: ID!
    name: String
}

input _MovieFilter1 {
    id_matches: String
    name_matches: String
}

type Query {
    movie(filter: _MovieFilter1): [Movie]
}
# Subgraph 2
type Movie @key(fields: "id") {
    id: ID!
    title: String
}

input _MovieFilter2 {
    id_matches: String
    title_matches: String
}

type Query {
  movie2(filter: _MovieFilter2): [Movie]
}

Which while maybe a bit more verbose, make it clear in the API which queries are valid and which aren't.

Will all that said, maybe what you are ultimately trying to achieve is different, and you want to allow both "Subgraph 1" and "Subgraph 2" to resolve the movie(filter: _MovieFilter) field, but to have the gateway somehow "dispatch" the request to one of the subgraph based on whichever filter is send. That is,

{
  movie(filter: { name_matches: "foo" })
}

would be send to "Subgraph 1" but:

{
  movie(filter: { title_matches: "foo" })
}

would be send to "Subgraph 2".

If that's the case, the I'm afraid this isn't something federation currently can do. But I'll point out that it's not so simple for federation to do such a thing because, given the API exposed by your example, the query:

{
  movie(filter: { name_matches: "bar", title_matches: "foo" })
}

is a valid query, but how would the gateway dispatch such query? That's what I meant at the beginning of my comment when I said "we wouldn't have a good way to handle it at runtime".

Anyway, I hope all of this helps, but let us know if something is still unclear.

@AakashSorathiya
Copy link
Author

Thanks for the reply @pcmanus

Will all that said, maybe what you are ultimately trying to achieve is different, and you want to allow both "Subgraph 1" and "Subgraph 2" to resolve the movie(filter: _MovieFilter) field, but to have the gateway somehow "dispatch" the request to one of the subgraph based on whichever filter is send.

Yes I was trying to achieve this only. But as you mentioned this can not be handled at runtime with federation so will look for a work around to handle it on our end.

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