Skip to content

Relax SameResponseShape algorithm to be compatible with covariant fields#883

Closed
IvanGoncharov wants to merge 1 commit intomainfrom
sameResponseShapeRelax
Closed

Relax SameResponseShape algorithm to be compatible with covariant fields#883
IvanGoncharov wants to merge 1 commit intomainfrom
sameResponseShapeRelax

Conversation

@IvanGoncharov
Copy link
Member

@IvanGoncharov IvanGoncharov commented Sep 2, 2021

At the moment covariance rules for interfaces are incompatible with
validation rule for overlapping fragments.
For example, if we have schema like this:

interface AddressInterface {
  country_code: String
}

type Port implements AddressInterface {
  country_code: String!
}

type Warehouse implements AddressInterface {
  country_code: String
}

type Query {
  addressInterface: AddressInterface
}

This query is valid:

query {
  addressInterface {
    country_code
  }
}

But if you expand interface field into inline fragments will cause a
validation error:

query {
  addressInterface {
    ... on Port {
      country_code
    }
    ... on Warehouse {
      country_code
    }
  }
}

This PR fixes this issue.

At the moment covariance rules for interfaces are incompatible with
validation rule for overlapping fragments.
For example, if we have schema like this:

```graphql
interface AddressInterface {
  country_code: String
}

type Port implements AddressInterface {
  country_code: String!
}

type Warehouse implements AddressInterface {
  country_code: String
}

type Query {
  addressInterface: AddressInterface
}
```

This query is valid:
```graphql
query {
  addressInterface {
    country_code
  }
}
```

But if you expand interface field into inline fragments will cause a
validation error:
```graphql
query {
  addressInterface {
    ... on Port {
      country_code
    }
    ... on Warehouse {
      country_code
    }
  }
}
```

This PR fixes this issue.
@leebyron
Copy link
Collaborator

leebyron commented Sep 2, 2021

Thanks for the detailed example, I'd love to see this fixed, looking forward to discussing this!

Comment on lines +432 to +435
* If {typeA} is Non-Null.
* Let {typeA} be the unwrapped nullable type of {typeA}
* If {typeB} is Non-Null.
* Let {typeB} be the unwrapped nullable type of {typeB}
Copy link
Collaborator

Choose a reason for hiding this comment

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

is this sufficient? what about more complex covariance scenarios?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this is what we need to compare against. Two valid branches into https://spec.graphql.org/draft/#IsValidImplementationFieldType()

Copy link
Contributor

@andimarek andimarek Sep 5, 2021

Choose a reason for hiding this comment

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

I think that is correct: we would have to consider the same logic as linked above.

Edit: Actually as showed in another comment covariant interface situations are already allowed. At least in some situations at least.( I am not sure at the moment what else is missing.)

But I want to call out that this change doesn't take any hierarchy into account. So this would not only relax the overlapping situation for covariant field but would allow to be merged which are not covariant related in any way.

@leebyron
Copy link
Collaborator

leebyron commented Sep 2, 2021

(Notes from in person discussion in WG)

My counter-argument is that this is actually working as intended, even though it's unintuitive.

While there have been some other past challenges to the purpose of SameResponseShape() so far we've agreed that it is a valuable constraint.

The example here is an edge case for sure, but still seems to be introducing two fields that not going to return the "same" type, even though they are covariant to the interface they both implement, and so the constraint should still apply.


Three options:

  1. Agree this is unintuitive but works as advertised and make no change
  2. Determine "SameResponseShape" is too strict and loosen the restriction specifically to null (essentially what this PR does)
  3. Add a specific case to "SameResponseShape" that allows interface implemented fields to be considered "Same" even though they are not the "same" type (but covariant) - targeting specifically this example issue.

My sense is that 3 is not really viable in that it has a similar impact to 2 but more complexity. I loosely think 1 is the right path given our constraints are working as intended, but if we do decide to pursue 2 (what's implemented here) then we should zoom out and better understand the impact of that decision

@leebyron
Copy link
Collaborator

leebyron commented Sep 2, 2021

An interesting RFC to compare this to is the one introducing ! forced non-null fields. We might rely on a similar rule (or this one) to determine where we allow merging such fields and where we require aliases

@IvanGoncharov
Copy link
Member Author

@IvanGoncharov IvanGoncharov added 💡 Proposal (RFC 1) RFC Stage 1 (See CONTRIBUTING.md) and removed 💭 Strawman (RFC 0) RFC Stage 0 (See CONTRIBUTING.md) labels Sep 4, 2021
@andimarek
Copy link
Contributor

I want to document for completeness sense that not all covariant fields are restricted in this way. You can actually query two different covariant fields IF the covariant type is not a Scalar/Enum.
For example:

 type Query {
    pets: [Pet]
  }
  interface Pet {
    name: String
    owner: PetOwner
  }
  type Dog implements Pet {
    name: String
    owner: DogOwner
  }
  type Cat implements Pet {
    name: String
    owner: CatOwner
  }
  interface PetOwner {
    name: String
  }
  type DogOwner implements PetOwner {
    name: String
  }
  type CatOwner implements PetOwner {
    name: String
  }

Here owner is covariant for Cat and Dog.
This query is valid:

{
  pets {
    ... on Dog {
      name
      owner {
        name
      }
    }
    ... on Cat {
      name
      owner {
        name
      }
    }
  }
}

Just to be clear: the reason is actually not that covariant is specifically handled, but the specific composed type returned doesn't matter when fields overlap.

@IvanGoncharov
Copy link
Member Author

Closing this proposal in favor of #895
Since it allows to implement of this transformation if add ? to every field.

@leebyron leebyron deleted the sameResponseShapeRelax branch July 1, 2022 18:00
@benjie benjie added 🗑 Rejected (RFC X) RFC Stage X (See CONTRIBUTING.md) 💡 Proposal (RFC 1) RFC Stage 1 (See CONTRIBUTING.md) and removed 💡 Proposal (RFC 1) RFC Stage 1 (See CONTRIBUTING.md) labels Jun 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💡 Proposal (RFC 1) RFC Stage 1 (See CONTRIBUTING.md) 🗑 Rejected (RFC X) RFC Stage X (See CONTRIBUTING.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants