Skip to content

Commit 8a0043e

Browse files
authored
Rearrange directive collision rules (#205)
1 parent 346bd32 commit 8a0043e

File tree

1 file changed

+133
-103
lines changed

1 file changed

+133
-103
lines changed

spec/Section 4 -- Composition.md

Lines changed: 133 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -562,11 +562,11 @@ type Product {
562562
}
563563
```
564564

565-
#### External Collision with Another Directive
565+
#### External Override Collision
566566

567567
**Error Code**
568568

569-
`EXTERNAL_COLLISION_WITH_ANOTHER_DIRECTIVE`
569+
`EXTERNAL_OVERRIDE_COLLISION`
570570

571571
**Severity**
572572

@@ -575,61 +575,113 @@ ERROR
575575
**Formal Specification**
576576

577577
- Let {schema} be the source schema to validate.
578-
- Let {types} be the set of all composite types in {schema}.
578+
- Let {types} be the set of all {INTERFACE} and {OBJECT} types in {schema}.
579579
- For each {type} in {types}:
580580
- Let {fields} be the set of fields on {type}.
581581
- For each {field} in {fields}:
582582
- If {field} is annotated with `@external`:
583-
- For each {argument} in {field}:
584-
- {argument} must **not** be annotated with `@require`
585-
- {field} must **not** be annotated with `@provides`
583+
- {field} must **not** be annotated with `@override`
586584

587585
**Explanatory Text**
588586

589587
The `@external` directive indicates that a field is **defined** in a different
590588
source schema, and the current schema merely references it. Therefore, a field
591589
marked with `@external` must **not** simultaneously carry directives that assume
592-
local ownership or resolution responsibility, such as:
590+
local ownership or resolution responsibility, such as `@override`, which
591+
transfers ownership of the field's definition from one schema to another, and is
592+
incompatible with an already-external field definition.
593+
594+
**Examples**
595+
596+
In this scenario, `User.fullName` is defined in **Schema A** but overridden in
597+
**Schema B**. Since `@override` is **not** combined with `@external` on the same
598+
field, no collision occurs.
599+
600+
```graphql example
601+
# Source Schema A
602+
type User {
603+
id: ID!
604+
fullName: String
605+
}
606+
607+
# Source Schema B
608+
type User {
609+
id: ID!
610+
fullName: String @override(from: "SchemaA")
611+
}
612+
```
613+
614+
Here, `amount` is marked with both `@override` and `@external`. This violates
615+
the rule because the field is simultaneously labeled asoverride from another
616+
schema” and “external” in the local schema, producing an
617+
`EXTERNAL_OVERRIDE_COLLISION` error.
618+
619+
```graphql counter-example
620+
# Source Schema A
621+
type Payment {
622+
id: ID!
623+
amount: Int
624+
}
625+
626+
# Source Schema B
627+
type Payment {
628+
id: ID!
629+
amount: Int @override(from: "SchemaA") @external
630+
}
631+
```
593632

594-
- **`@provides`**: Declares that the field can supply additional nested fields
595-
from the local schema, which conflicts with the notion of an external field
596-
whose definition resides elsewhere.
633+
#### External Provides Collision
597634

598-
- **`@require`**: Specifies dependencies on other fields to resolve this field.
599-
Since `@external` fields are not locally resolved, there is no need for
600-
`@require`.
635+
**Error Code**
636+
637+
`EXTERNAL_PROVIDES_COLLISION`
638+
639+
**Severity**
640+
641+
ERROR
642+
643+
**Formal Specification**
644+
645+
- Let {schema} be the source schema to validate.
646+
- Let {types} be the set of all {INTERFACE} and {OBJECT} types in {schema}.
647+
- For each {type} in {types}:
648+
- Let {fields} be the set of fields on {type}.
649+
- For each {field} in {fields}:
650+
- If {field} is annotated with `@external`:
651+
- {field} must **not** be annotated with `@provides`
601652

602-
- **`@override`**: Transfers ownership of the field's definition from one schema
603-
to another, which is incompatible with an already-external field definition.
604-
Yet this is covered by the `OVERRIDE_COLLISION_WITH_ANOTHER_DIRECTIVE` rule.
653+
**Explanatory Text**
605654

606-
Any combination of `@external` with either `@provides` or `@require` on the same
607-
field results in inconsistent semantics. In such scenarios, an
608-
`EXTERNAL_COLLISION_WITH_ANOTHER_DIRECTIVE` error is raised.
655+
The `@external` directive indicates that a field is **defined** in a different
656+
source schema, and the current schema merely references it. Therefore, a field
657+
marked with `@external` must **not** simultaneously carry directives that assume
658+
local ownership or resolution responsibility, such as `@provides`, which
659+
declares that the field can supply additional nested fields from the local
660+
schema, conflicting with the notion of an external field whose definition
661+
resides elsewhere.
609662

610663
**Examples**
611664

612-
In this example, `method` is **only** annotated with `@external` in Schema B,
613-
without any other directive. This usage is valid.
665+
In this example, `description` is **only** annotated with `@provides` in Schema
666+
B, without any other directive. This usage is valid.
614667

615668
```graphql example
616669
# Source Schema A
617-
type Payment {
670+
type Invoice {
618671
id: ID!
619-
method: String
672+
description: String
620673
}
621674

622675
# Source Schema B
623-
type Payment {
676+
type Invoice {
624677
id: ID!
625-
# This field is external, defined in Schema A.
626-
method: String @external
678+
description: String @provides(fields: "length")
627679
}
628680
```
629681

630682
In this counter-example, `description` is annotated with `@external` and also
631683
with `@provides`. Because `@external` and `@provides` cannot co-exist on the
632-
same field, an `EXTERNAL_COLLISION_WITH_ANOTHER_DIRECTIVE` error is produced.
684+
same field, an `EXTERNAL_PROVIDES_COLLISION` error is produced.
633685

634686
```graphql counter-example
635687
# Source Schema A
@@ -645,9 +697,59 @@ type Invoice {
645697
}
646698
```
647699

648-
The following example is invalid, since `title` is marked with both `@external`
649-
and has an argument that is annotated with `@require`. This conflict leads to an
650-
`EXTERNAL_COLLISION_WITH_ANOTHER_DIRECTIVE` error.
700+
#### External Require Collision
701+
702+
**Error Code**
703+
704+
`EXTERNAL_REQUIRE_COLLISION`
705+
706+
**Severity**
707+
708+
ERROR
709+
710+
**Formal Specification**
711+
712+
- Let {schema} be the source schema to validate.
713+
- Let {types} be the set of all {INTERFACE} and {OBJECT} types in {schema}.
714+
- For each {type} in {types}:
715+
- Let {fields} be the set of fields on {type}.
716+
- For each {field} in {fields}:
717+
- If {field} is annotated with `@external`:
718+
- For each {argument} in {field}:
719+
- {argument} must **not** be annotated with `@require`
720+
721+
**Explanatory Text**
722+
723+
The `@external` directive indicates that a field is **defined** in a different
724+
source schema, and the current schema merely references it. Therefore, a field
725+
marked with `@external` must **not** simultaneously carry directives that assume
726+
local ownership or resolution responsibility, such as `@require`, which
727+
specifies dependencies on other fields to resolve this field. Since `@external`
728+
fields are not locally resolved, there is no need for `@require`.
729+
730+
**Examples**
731+
732+
In this example, `title` has arguments annotated with `@require` in Schema B,
733+
but is not marked as `@external`. This usage is valid.
734+
735+
```graphql example
736+
# Source Schema A
737+
type Book {
738+
id: ID!
739+
title: String
740+
subtitle: String
741+
}
742+
743+
# Source Schema B
744+
type Book {
745+
id: ID!
746+
title(subtitle: String @require(field: "subtitle")): String
747+
}
748+
```
749+
750+
The following example is invalid, since `title` is marked with `@external` and
751+
has an argument that is annotated with `@require`. This conflict leads to an
752+
`EXTERNAL_REQUIRE_COLLISION` error.
651753

652754
```graphql counter-example
653755
# Source Schema A
@@ -660,7 +762,7 @@ type Book {
660762
# Source Schema B
661763
type Book {
662764
id: ID!
663-
title(subtitle: String @require(field: "subtitle")) @external
765+
title(subtitle: String @require(field: "subtitle")): String @external
664766
}
665767
```
666768

@@ -3436,78 +3538,6 @@ type Product {
34363538

34373539
### Validate Override Directives
34383540

3439-
#### Override Collision with Another Directive
3440-
3441-
**Error Code**
3442-
3443-
`OVERRIDE_COLLISION_WITH_ANOTHER_DIRECTIVE`
3444-
3445-
**Severity**
3446-
3447-
ERROR
3448-
3449-
**Formal Specification**
3450-
3451-
- Let {schemas} be the set of all source schemas to be composed.
3452-
- For each {schema} in {schemas}:
3453-
- Let {types} be the set of all composite types in {schema}.
3454-
- For each {type} in {types}:
3455-
- Let {fields} be the set of fields on {type}.
3456-
- For each {field} in {fields}:
3457-
- If {field} is annotated with `@override`:
3458-
- {field} must **not** be annotated with `@external`
3459-
3460-
**Explanatory Text**
3461-
3462-
The `@override` directive designates that ownership of a field is transferred
3463-
from one source schema to another in the resulting composite schema. When such a
3464-
transfer occurs, that field **cannot** also be annotated `@external`. A field
3465-
declared as `@external` is originally defined in a **different** source schema.
3466-
Overriding a field and simultaneously claiming it is external to the local
3467-
schema is contradictory.
3468-
3469-
In this case composition fails with an
3470-
`OVERRIDE_COLLISION_WITH_ANOTHER_DIRECTIVE` error.
3471-
3472-
**Examples**
3473-
3474-
In this scenario, `User.fullName` is defined in **Schema A** but overridden in
3475-
**Schema B**. Since `@override` is **not** combined with any of `@external` on
3476-
the same field, no collision occurs.
3477-
3478-
```graphql example
3479-
# Source Schema A
3480-
type User {
3481-
id: ID!
3482-
fullName: String
3483-
}
3484-
3485-
# Source Schema B
3486-
type User {
3487-
id: ID!
3488-
fullName: String @override(from: "SchemaA")
3489-
}
3490-
```
3491-
3492-
Here, `amount` is marked with both `@override` and `@external`. This violates
3493-
the rule because the field is simultaneously labeled asoverride from another
3494-
schema” and “external” in the local schema, producing an
3495-
`OVERRIDE_COLLISION_WITH_ANOTHER_DIRECTIVE` error.
3496-
3497-
```graphql counter-example
3498-
# Source Schema A
3499-
type Payment {
3500-
id: ID!
3501-
amount: Int
3502-
}
3503-
3504-
# Source Schema B
3505-
type Payment {
3506-
id: ID!
3507-
amount: Int @override(from: "SchemaA") @external
3508-
}
3509-
```
3510-
35113541
#### Override Source Has Override
35123542

35133543
**Error Code**

0 commit comments

Comments
 (0)