Skip to content

Commit 88259f9

Browse files
Update @shareable rules (#200)
Co-authored-by: Michael Staib <[email protected]>
1 parent 8e969bf commit 88259f9

File tree

1 file changed

+146
-151
lines changed

1 file changed

+146
-151
lines changed

spec/Section 4 -- Composition.md

Lines changed: 146 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -3301,6 +3301,121 @@ type Bill {
33013301
}
33023302
```
33033303

3304+
### Validate Shareable Directives
3305+
3306+
#### Invalid Field Sharing
3307+
3308+
**Error Code**
3309+
3310+
`INVALID_FIELD_SHARING`
3311+
3312+
**Severity**
3313+
3314+
ERROR
3315+
3316+
**Formal Specification**
3317+
3318+
- Let {typeNames} be the set of all object type names from all source schemas
3319+
that are not declared as `@internal`
3320+
- For each {typeName} in {typeNames}:
3321+
- Let {typeDefinitions} be the list of all object type definitions from all
3322+
source schemas with the name {typeName}.
3323+
- Let {fieldNames} be the set of all field names from all {typeDefinitions}
3324+
that are not declared as `@internal` or `@external`, part of a `@key`
3325+
directive, or overridden.
3326+
- For each {fieldName} in {fieldNames}:
3327+
- Let {fieldDefinitions} be the list of all field definitions from
3328+
{typeDefinitions} with the name {fieldName}.
3329+
- If {fieldDefinitions} has more than one element:
3330+
- For each {fieldDefinition} in {fieldDefinitions}:
3331+
- {fieldDefinition} must be annotated with `@shareable`.
3332+
3333+
**Explanatory Text**
3334+
3335+
A field in a federated GraphQL schema may be marked `@shareable`, indicating
3336+
that the same field can be resolved by multiple schemas without conflict. When a
3337+
field is **not** marked as `@shareable` (sometimes called "non-shareable"), it
3338+
cannot be provided by more than one schema.
3339+
3340+
Field definitions marked as `@external` and overridden fields are excluded when
3341+
validating whether a field is shareable. These annotations indicate specific
3342+
cases where field ownership lies with another schema or has been replaced.
3343+
3344+
**Examples**
3345+
3346+
In this example, the `User` type field `fullName` is marked as shareable in both
3347+
schemas, allowing them to serve consistent data for that field without conflict.
3348+
3349+
```graphql example
3350+
# Schema A
3351+
type User @key(fields: "id") {
3352+
id: ID!
3353+
username: String
3354+
fullName: String @shareable
3355+
}
3356+
3357+
# Schema B
3358+
type User @key(fields: "id") {
3359+
id: ID!
3360+
fullName: String @shareable
3361+
email: String
3362+
}
3363+
```
3364+
3365+
In the following example, `User.fullName` is overridden in one schema and
3366+
therefore the field can be defined in the other schema without being marked as
3367+
`@shareable`.
3368+
3369+
```graphql example
3370+
# Schema A
3371+
type User @key(fields: "id") {
3372+
id: ID!
3373+
fullName: String @override(from: "B")
3374+
}
3375+
3376+
# Schema B
3377+
type User @key(fields: "id") {
3378+
id: ID!
3379+
fullName: String
3380+
}
3381+
```
3382+
3383+
In the following example, `User.fullName` is marked as `@external` in one schema
3384+
and therefore the field can be defined in the other schema without being marked
3385+
as `@shareable`.
3386+
3387+
```graphql example
3388+
# Schema A
3389+
type User @key(fields: "id") {
3390+
id: ID!
3391+
fullName: String @external
3392+
}
3393+
3394+
# Schema B
3395+
type User @key(fields: "id") {
3396+
id: ID!
3397+
fullName: String
3398+
}
3399+
```
3400+
3401+
In the following counter-example, `User.fullName` is non-shareable but is
3402+
defined and resolved by two different schemas, resulting in an
3403+
`INVALID_FIELD_SHARING` error.
3404+
3405+
```graphql counter-example
3406+
# Schema A
3407+
type User @key(fields: "id") {
3408+
id: ID!
3409+
fullName: String
3410+
}
3411+
3412+
# Schema B
3413+
type User @key(fields: "id") {
3414+
id: ID!
3415+
fullName: String
3416+
}
3417+
```
3418+
33043419
## Merge
33053420

33063421
During this stage, all definitions from each source schema are combined into a
@@ -5924,46 +6039,41 @@ type Book {
59246039

59256040
### Validate Shareable Directives
59266041

5927-
#### Invalid Field Sharing
6042+
#### Invalid Shareable Usage
59286043

59296044
**Error Code**
59306045

5931-
`INVALID_FIELD_SHARING`
6046+
`INVALID_SHAREABLE_USAGE`
59326047

59336048
**Severity**
59346049

59356050
ERROR
59366051

59376052
**Formal Specification**
59386053

5939-
- Let {schema} be the merged composite execution schema.
5940-
- Let {types} be the set of all object and interface types in {schema}.
6054+
- Let {schema} be one of the composed schemas.
6055+
- Let {types} be the set of types defined in {schema}.
59416056
- For each {type} in {types}:
6057+
- If {type} is an interface type:
6058+
- For each field definition {field} in {type}:
6059+
- If {field} is annotated with `@shareable`, produce an
6060+
`INVALID_SHAREABLE_USAGE` error.
59426061
- If {type} is the `Subscription` type:
5943-
- Let {fields} be the set of all fields in {type}.
5944-
- For each {field} in {fields}:
5945-
- If {field} is marked with `@shareable`:
5946-
- Produce an `INVALID_FIELD_SHARING` error.
5947-
- Otherwise:
5948-
- Let {fields} be the set of all fields on {type}.
5949-
- For each {field} in {fields}:
5950-
- If {field} is not part of a `@key` directive:
5951-
- Let {fieldDefinitions} be the set of all field definitions for {field}
5952-
across all source schemas excluding fields marked with `@external` or
5953-
`@override`.
5954-
- If {fieldDefinitions} has more than one element:
5955-
- {field} must be marked as `@shareable` in at least one schema.
6062+
- For each field definition {field} in {type}:
6063+
- If {field} is annotated with `@shareable`, produce an
6064+
`INVALID_SHAREABLE_USAGE` error.
59566065

59576066
**Explanatory Text**
59586067

5959-
A field in a federated GraphQL schema may be marked `@shareable`, indicating
5960-
that the same field can be resolved by multiple schemas without conflict. When a
5961-
field is **not** marked as `@shareable` (sometimes called "non-shareable"), it
5962-
cannot be provided by more than one schema.
6068+
The `@shareable` directive is intended to indicate that a field on an **object
6069+
type** can be resolved by multiple schemas without conflict. As a result, it is
6070+
only valid to use `@shareable` on fields **of object types** (or on the entire
6071+
object type itself).
59636072

5964-
Field definitions marked as `@external` or `@override` are excluded when
5965-
validating whether a field is shareable. These annotations indicate specific
5966-
cases where field ownership lies with another schema or has been replaced.
6073+
Applying `@shareable` to interface fields is disallowed and violates the valid
6074+
usage of the directive. This rule prevents schema composition errors and data
6075+
conflicts by ensuring that `@shareable` is used only in contexts where shared
6076+
field resolution is meaningful and unambiguous.
59676077

59686078
Additionally, subscription root fields cannot be shared (i.e., they are
59696079
effectively non-shareable), as subscription events from multiple schemas would
@@ -5972,84 +6082,26 @@ as shareable or to define it in multiple schemas triggers the same error.
59726082

59736083
**Examples**
59746084

5975-
In this example, the `User` type field `fullName` is marked as shareable in both
5976-
schemas, allowing them to serve consistent data for that field without conflict.
5977-
5978-
```graphql example
5979-
# Schema A
5980-
type User @key(fields: "id") {
5981-
id: ID!
5982-
username: String
5983-
fullName: String @shareable
5984-
}
5985-
5986-
# Schema B
5987-
type User @key(fields: "id") {
5988-
id: ID!
5989-
fullName: String @shareable
5990-
email: String
5991-
}
5992-
```
5993-
5994-
In the following example, `User.fullName` is overridden in one schema and
5995-
therefore the field can be defined in multiple schemas without being marked as
5996-
`@shareable`.
5997-
5998-
```graphql example
5999-
# Schema A
6000-
type User @key(fields: "id") {
6001-
id: ID!
6002-
fullName: String @override(from: "B")
6003-
}
6004-
6005-
# Schema B
6006-
type User @key(fields: "id") {
6007-
id: ID!
6008-
fullName: String
6009-
}
6010-
```
6011-
6012-
In the following example, `User.fullName` is marked as `@external` in one schema
6013-
and therefore the field can be defined in the other schema without being marked
6014-
as `@shareable`.
6085+
In this example, the field `orderStatus` on the `Order` object type is marked
6086+
with `@shareable`, which is allowed. It signals that this field can be served
6087+
from multiple schemas without creating a conflict.
60156088

60166089
```graphql example
6017-
# Schema A
6018-
type User @key(fields: "id") {
6019-
id: ID!
6020-
fullName: String @external
6021-
}
6022-
6023-
# Schema B
6024-
type User @key(fields: "id") {
6090+
type Order {
60256091
id: ID!
6026-
fullName: String
6092+
orderStatus: String @shareable
6093+
total: Float
60276094
}
60286095
```
60296096

6030-
In the following counter-example, `User.profile` is non-shareable but is defined
6031-
and resolved by two different schemas, resulting in an `INVALID_FIELD_SHARING`
6032-
error.
6097+
In this counter-example, the `InventoryItem` interface has a field `sku` marked
6098+
with `@shareable`, which is invalid usage. Marking an interface field as
6099+
shareable leads to an `INVALID_SHAREABLE_USAGE` error.
60336100

60346101
```graphql counter-example
6035-
# Schema A
6036-
type User @key(fields: "id") {
6037-
id: ID!
6038-
profile: Profile
6039-
}
6040-
6041-
type Profile {
6042-
avatarUrl: String
6043-
}
6044-
6045-
# Schema B
6046-
type User @key(fields: "id") {
6047-
id: ID!
6048-
profile: Profile
6049-
}
6050-
6051-
type Profile {
6052-
avatarUrl: String
6102+
interface InventoryItem {
6103+
sku: ID! @shareable
6104+
name: String
60536105
}
60546106
```
60556107

@@ -6074,63 +6126,6 @@ type Subscription {
60746126
}
60756127
```
60766128

6077-
#### Invalid Shareable Usage
6078-
6079-
**Error Code**
6080-
6081-
`INVALID_SHAREABLE_USAGE`
6082-
6083-
**Severity**
6084-
6085-
ERROR
6086-
6087-
**Formal Specification**
6088-
6089-
- Let {schema} be one of the composed schemas.
6090-
- Let {types} be the set of types defined in {schema}.
6091-
- For each {type} in {types}:
6092-
- If {type} is an interface type:
6093-
- For each field definition {field} in {type}:
6094-
- If {field} is annotated with `@shareable`, produce an
6095-
`INVALID_SHAREABLE_USAGE` error.
6096-
6097-
**Explanatory Text**
6098-
6099-
The `@shareable` directive is intended to indicate that a field on an **object
6100-
type** can be resolved by multiple schemas without conflict. As a result, it is
6101-
only valid to use `@shareable` on fields **of object types** (or on the entire
6102-
object type itself).
6103-
6104-
Applying `@shareable` to interface fields is disallowed and violates the valid
6105-
usage of the directive. This rule prevents schema composition errors and data
6106-
conflicts by ensuring that `@shareable` is used only in contexts where shared
6107-
field resolution is meaningful and unambiguous.
6108-
6109-
**Examples**
6110-
6111-
In this example, the field `orderStatus` on the `Order` object type is marked
6112-
with `@shareable`, which is allowed. It signals that this field can be served
6113-
from multiple schemas without creating a conflict.
6114-
6115-
```graphql example
6116-
type Order {
6117-
id: ID!
6118-
orderStatus: String @shareable
6119-
total: Float
6120-
}
6121-
```
6122-
6123-
In this example, the `InventoryItem` interface has a field `sku` marked with
6124-
`@shareable`, which is invalid usage. Marking an interface field as shareable
6125-
leads to an `INVALID_SHAREABLE_USAGE` error.
6126-
6127-
```graphql counter-example
6128-
interface InventoryItem {
6129-
sku: ID! @shareable
6130-
name: String
6131-
}
6132-
```
6133-
61346129
## Validate Satisfiability
61356130

61366131
The final step confirms that the composite schema supports executable queries

0 commit comments

Comments
 (0)