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

fix: Broken fragment spreads #300

Merged
merged 1 commit into from
Nov 20, 2021

Conversation

budde377
Copy link
Collaborator

@budde377 budde377 commented Nov 7, 2021

#199 Introduced a new way to resolve fragments.

#223 Broke that integration, the motivation being that it seemed too strict in the case where the newly introduced possibleTypes weren't defined. The solution was making a distinction between how fragment spreads and inline fragment spreads were handled, which is fundamentally wrong. The test which should have caught this was basically disabled because of bad naming. I've renamed this to possible_types_test.dart

This PR allows us to detect if the possibleTypes map isn't provided and in this case optimistically spread every field, regardless of it being an inline fragment spread or fragment spread. This is a proposal to a fallback logic.

I've also updated the union_type_inline_fragments_test.dart test. The current logic is wrong. Without possible types, there's no way for us to know if Author is an implementation of Book, or as is probably intended here a union type.

@budde377 budde377 force-pushed the fix-broken-fragments branch 3 times, most recently from 82bea52 to e6a82af Compare November 7, 2021 11:30
Copy link
Member

@smkhalsa smkhalsa left a comment

Choose a reason for hiding this comment

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

Thanks for your efforts on this. Please see my comments.

'id': '123',
'__typename': 'Book',
'title': 'My awesome blog post',
'name': null
Copy link
Member

@smkhalsa smkhalsa Nov 9, 2021

Choose a reason for hiding this comment

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

The normalized map shouldn't include fields that don't exist in the source data (i.e. 'name': null on Book).

A field with a value of null is not semantically identical to the absence of the field. In this case, if our Book type doesn't include a name field in the schema, it doesn't make sense for it to be included in the normalized data.

Furthermore, if Book does have a name field which is non-nullable, but the query that fetched this data didn't ask for it, we'd now have an invalid state. This would cause an error when the user reads a query from the cache that does include the name field, resulting in a null value for a non-nullable field.

Copy link
Collaborator Author

@budde377 budde377 Nov 9, 2021

Choose a reason for hiding this comment

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

Bear in mind that this is the same behavior as you've introduced in #223 for named fragments. I would love to hear your opinion on the best way to handle the fallback behavior in a uniform way for inline fragments and named fragments.

The problem is that without possibleTypes we can't know if the schame is either

union BookAndAuthor = Book | Author

or

interface Book {
  title: String
}

type Author implements Book {
  title: String
  name: String
}

or

interface Author {
  name: String
}

type Book implements Author {
  title: String
  name: String
}

or

interface Author {
  name: String
  title: String
}

type Book implements Author {
  title: String
  name: String
}

The current solution optimistically expands every field, when the possibleTypes is not defined. This is bound to be wrong in some cases.

The logic implemented in #199 was more stringent and required the typename and type condition to exactly match if the possibleTypes weren't defined, which was a safe bet. I don't know the motivation for its removal in #223.

Copy link
Member

@smkhalsa smkhalsa Nov 9, 2021

Choose a reason for hiding this comment

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

What if we expand only the fields that include data in the response? If the server is sending data (including an explicit null), we can assume that it's a valid field on the underlying type for the given query.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@smkhalsa, I've had a look over this. The problem with this approach, as I see it, is that if we want to filter the fields by data, then we'll be allowing fetching partial data. E.g., consider the query

query Q1 {
  vehicle {
    ... on Vehicle { driver { __typename id name } } 
    ... on Car { driver { __typename id age } } 
  }
} 

against schema (1)

type Person { id: ID! name: String age: Int }

interface Vehicle {
  id: ID!
  driver: Person
}

type Car implements Vehicle {
  id: ID!
  driver: Person
}

and the schema (2)

type Driver { id: ID! name: String age: Int }

type Vehicle {
  id: ID!
  driver: Person
}

type Car  {
  id: ID!
  driver: Person
}

Against the first schema the data should be:

{ 
  ...
  "Driver:1": { "name": "Allan", "age": 30 }
  ...
}

and the second

{ 
  ...
  "Driver:2": { "name": "Allan" }
  ... 
}

or

{ 
  ...
  "Driver:2": { "age": "30" }
  ...
}

so this will be a problem for all selection set under a fragment.

I'm curious about the reason for the aversion around only expanding fragments on exact matches, which was the logic implemented in #199. It still seems to me like the best approach. Sure, we can go in and make all kinds of convoluted rules to expand these fragments, but ultimately it'll make the logic harder to understand and never be perfect. Why can't we expect the users to provide possibleTypes if their fragments aren't expanding properly?

Copy link
Member

Choose a reason for hiding this comment

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

Ok, I think I agree with you. While following the shape of the data should work during normalization, there's no way to know what fields must be selected during denormalization, so only expanding fragments on exact matches seems like the only way to ensure correctness.

I'd like to keep the API as simple as possible with minimal configuration. To that end, I've done some initial work (untested) to generate the possibleTypes from the schema which the user can include when creating a client.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Awesome. I've updated the PR to revert to the stricter check originally part of #199.

@budde377
Copy link
Collaborator Author

budde377 commented Nov 10, 2021 via email

@budde377 budde377 force-pushed the fix-broken-fragments branch from e6a82af to 8066992 Compare November 20, 2021 10:17
Copy link
Member

@smkhalsa smkhalsa left a comment

Choose a reason for hiding this comment

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

LGTM. Thanks for your work on this.

@budde377
Copy link
Collaborator Author

@smkhalsa, we're getting some issue reports on GraphQL Client on fragments and we'll need to communicate these changes out to our users. IIUC, this hasn't been released yet, what is your plan for release, so I can coordinate comms?

@smkhalsa
Copy link
Member

@budde377 I just published normalize 0.6.0

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.

2 participants