Skip to content

Commit b304ecf

Browse files
authored
Fixed Input and Argument Merge Rules (#100)
* Fixed Input and Argument Merge Rules * Fixed Input and Argument Merge Rules * Cleanup * cleanup
1 parent d11fbec commit b304ecf

File tree

1 file changed

+126
-46
lines changed

1 file changed

+126
-46
lines changed

spec/Section 4 -- Composition.md

+126-46
Original file line numberDiff line numberDiff line change
@@ -2366,14 +2366,16 @@ ERROR
23662366

23672367
**Formal Specification**
23682368

2369-
- Let {typeNames} be the set of all output type names from all source schemas.
2369+
- Let {typeNames} be the set of all output type names from all source schemas
2370+
that are not declared as `@inaccessible` in any schema.
23702371
- For each {typeName} in {typeNames}
23712372
- Let {types} be the set of all types with the {typeName} from all source
2372-
schemas.
2373-
- Let {fieldNames} be the set of all field names from all {types}.
2373+
schemas that are not declared as `@internal`.
2374+
- Let {fieldNames} be the set of all field names from all {types} that are not
2375+
declared as `@inaccessible` in any schema.
23742376
- For each {fieldName} in {fieldNames}
23752377
- Let {fields} be the set of all fields with the {fieldName} from all
2376-
{types}.
2378+
{types} that are not declared as `@internal`.
23772379
- For each {field} in {fields}
23782380
- Let {argumentNames} be the set of all argument names from all {fields}.
23792381
- For each {argumentName} in {argumentNames}
@@ -2386,7 +2388,7 @@ ArgumentsAreMergeable(argumentA, argumentB):
23862388

23872389
- Let {typeA} be the type of {argumentA}
23882390
- Let {typeB} be the type of {argumentB}
2389-
- {InputTypesAreMergeable(typeA, typeB)} must be true.
2391+
- {SameTypeShape(typeA, typeB)} must be true.
23902392

23912393
**Explanatory Text**
23922394

@@ -3882,12 +3884,19 @@ MergeInputTypes(types):
38823884
- Set {description} to the description of {type}.
38833885
- Let {fieldNames} be the set of all field names in {types}.
38843886
- For each {fieldName} in {fieldNames}:
3885-
- Let {fields} be the set of fields with the name {fieldName} in {types}.
3886-
- Let {mergedField} be the result of {MergeInputFields(fields)}.
3887+
- Let {fieldDefinitions} be the set of fields with the name {fieldName} in
3888+
{types}.
3889+
- If length of {fieldDefinitions} is not equal to the length of {types}:
3890+
- Continue
3891+
- If any field in {fieldDefinitions} is marked with `@inaccessible`
3892+
- Continue
3893+
- Let {mergedField} be the result of {MergeInputField(fieldDefinitions)}.
38873894
- If {mergedField} is not {null}:
3888-
- Add {mergedField} to {mergedFields}.
3889-
- Return a new input object type with the name of {typeName}, description of
3890-
{description}, fields of {mergedFields}.
3895+
- Add {mergedField} to {fields}.
3896+
- If {fields} is empty:
3897+
- Return {null}
3898+
- Return a new input type with the name of {typeName}, description of
3899+
{description}, and fields of {fields}.
38913900

38923901
**Explanatory Text**
38933902

@@ -3919,10 +3928,18 @@ instance, because one of its underlying definitions was inaccessible - that
39193928
field is not included in the final definition. The end result is a single input
39203929
type that correctly unifies every compatible field from the various sources.
39213930

3931+
After filtering out inaccessible types, the algorithm takes the **intersection**
3932+
of the field names across the remaining types - only those fields that appear in
3933+
**every** source definition are eligible. For each eligible field, it invokes
3934+
{MergeInputField(fieldsForName)} to reconcile differences in type, nullability,
3935+
default values, etc. The end result is a single input type that correctly
3936+
unifies every compatible field that appears in all source types.
3937+
39223938
**Examples**
39233939

39243940
Here, two `OrderInput` input types from different schemas are merged into a
3925-
single composed `OrderInput` type.
3941+
single composed `OrderInput` type. Notice that only the fields present in _both_
3942+
schemas are included.
39263943

39273944
```graphql example
39283945
# Schema A
@@ -3943,15 +3960,11 @@ input OrderInput {
39433960

39443961
input OrderInput {
39453962
id: ID!
3946-
description: String
3947-
total: Float
39483963
}
39493964
```
39503965

3951-
In this example, the `OrderInput` type from two schemas is merged. The `id`
3952-
field is shared across both schemas, while `description` and `total` fields are
3953-
contributed by the individual source schemas. The resulting composed type
3954-
includes all fields.
3966+
Although `description` appears in Schema A and `total` appears in Schema B,
3967+
neither field is defined in _both_ schemas; therefore, only `id` remains.
39553968

39563969
Another example demonstrates preserving descriptions during merging:
39573970

@@ -4168,7 +4181,13 @@ MergeOutputFields(fields):
41684181
- Let {argumentNames} be the set of all argument names in {fields}.
41694182
- For each {argumentName} in {argumentNames}:
41704183
- Let {arguments} be the set of arguments with the name {argumentName} in
4171-
{fields}.
4184+
{fields}
4185+
- If length of {arguments} is not equal to the length of {fields}:
4186+
- Continue.
4187+
- If any argument in {arguments} is marked with `@inaccessible`:
4188+
- Continue.
4189+
- If any argument in {arguments} is marked with `@require`:
4190+
- Continue.
41724191
- Let {mergedArgument} be the result of {MergeArgumentDefinitions(arguments)}.
41734192
- If {mergedArgument} is not {null}:
41744193
- Add {mergedArgument} to {mergedArguments}.
@@ -4217,17 +4236,14 @@ schema does not break schemas expecting any of those types. For example,
42174236

42184237
_Merging Arguments_
42194238

4220-
Each field can declare arguments. The algorithm collects all all argument names
4221-
across these fields and merges them using {MergeArgumentDefinitions(arguments)},
4222-
ensuring argument definitions remain compatible. If any of the arguments for a
4223-
particular name is `@inaccessible`, then that argument is removed from the final
4224-
set of arguments. Otherwise, any differences in argument type, default value, or
4225-
description are resolved via the merging rules in
4226-
{MergeArgumentDefinitions(arguments)}.
4227-
4228-
This algorithm preserves as much information as possible from the source fields
4229-
while ensuring they remain mutually compatible. It also systematically excludes
4230-
fields or arguments deemed inaccessible.
4239+
Each field can declare arguments. The algorithm collects all argument names
4240+
across these fields and merges them using {MergeArgumentDefinitions(arguments)}.
4241+
Before merging, any arguments marked with `@inaccessible` or `@require` are
4242+
excluded. If this exclusion causes the number of arguments available for a given
4243+
name to differ from the total number of fields - or if at least one schema omits
4244+
the argument - the argument is skipped entirely. Otherwise, any differences in
4245+
argument type, default value, or description are resolved via the merging rules
4246+
in {MergeArgumentDefinitions(arguments)}.
42314247

42324248
**Example**
42334249

@@ -4260,6 +4276,67 @@ type Product {
42604276
}
42614277
```
42624278

4279+
If the argument is missing in one of the schemas, the composed field will not
4280+
include that argument:
4281+
4282+
```graphql example
4283+
# Schema A
4284+
type Product {
4285+
discountPercentage(percent: Int): Int
4286+
}
4287+
4288+
# Schema B
4289+
type Product {
4290+
discountPercentage: Int
4291+
}
4292+
4293+
# Composed Result
4294+
type Product {
4295+
discountPercentage: Int
4296+
}
4297+
```
4298+
4299+
In case one argument is marked with `@inaccessible`, the composed field will not
4300+
include that argument:
4301+
4302+
```graphql example
4303+
# Schema A
4304+
type Product {
4305+
discountPercentage(percent: Int): Int
4306+
}
4307+
4308+
# Schema B
4309+
type Product {
4310+
discountPercentage(percent: Int @inaccessible): Int
4311+
}
4312+
4313+
# Composed Result
4314+
type Product {
4315+
discountPercentage: Int
4316+
}
4317+
```
4318+
4319+
In case a schema defines a requirement through the `@require` directive, the
4320+
composed field will not include that argument
4321+
4322+
```graphql example
4323+
# Schema A
4324+
type Product {
4325+
discountPercentage(percent: Int): Int
4326+
discount: Int
4327+
}
4328+
4329+
# Schema B
4330+
type Product {
4331+
discountPercentage(percent: Int @require(field: "discount")): Int
4332+
}
4333+
4334+
# Composed Result
4335+
type Product {
4336+
discountPercentage: Int
4337+
}
4338+
```
4339+
42634340
#### Merge Input Fields
42644341

42654342
**Formal Specification**
@@ -4275,6 +4352,7 @@ MergeInputFields(fields):
42754352
- Let {defaultValue} be the default value of {firstField} or undefined if none
42764353
exists.
42774354
- For each {field} in {fields}:
4355+
- Assert: {field} is **not** marked with `@inaccessible`
42784356
- Let {type} be the type of {field}.
42794357
- Set {fieldType} to be the result of {MostRestrictiveType(fieldType, type)}.
42804358
- If {description} is null:
@@ -4295,8 +4373,9 @@ breakdown of how {MergeInputFields(fields)} operates:
42954373

42964374
_Inaccessible Fields_
42974375

4298-
If any of the fields is marked with `@inaccessible`, we cannot include the field
4299-
in the composed schema, and the merge algorithm returns `null`.
4376+
Before calling {MergeInputField(fields)}, all fields marked with `@inaccessible`
4377+
must be filtered out. If any such field appears in the input, it is a
4378+
precondition violation of this algorithm.
43004379

43014380
_Combining Descriptions_
43024381

@@ -4368,8 +4447,8 @@ MergeArgumentDefinitions(arguments):
43684447
- If {mergedArgument} is null
43694448
- Return null
43704449
- For each {argument} in {arguments}:
4371-
- If {argument} is marked with `@require`
4372-
- Continue
4450+
- Assert: {argument} is **not** marked with `@inaccessible`
4451+
- Assert: {argument} is **not** marked with `@require`
43734452
- Set {mergedArgument} to the result of {MergeArguments(mergedArgument,
43744453
argument)}
43754454
- Return {mergedArgument}
@@ -4382,26 +4461,27 @@ definition.
43824461

43834462
_Inaccessible Arguments_
43844463

4385-
If any argument in the set is marked with `@inaccessible`, the entire argument
4386-
definition is discarded by returning `null`. An inaccessible argument should not
4387-
appear in the final composed schema.
4464+
Inaccessible arguments (`@inaccessible`) should be handled and filtered out
4465+
**before** calling {MergeArgumentDefinitions(arguments)}. By the time this
4466+
algorithm is invoked, any arguments marked `@inaccessible` must already be
4467+
removed. If such an argument somehow appears here, it is a precondition
4468+
violation of this algorithm.
43884469

43894470
_Handling `@require`_
43904471

4391-
The `@require` directive is used to indicate that the argument is required for
4392-
the field to be resolved, yet it specifies it as a dependency that is resolved
4393-
at runtime. Therefore, this argument should not affect the merge process. If
4394-
there are only `@require` arguments in the set, the merge algorithm returns
4395-
`null`.
4472+
The `@require` directive is likewise handled **before** this algorithm.
4473+
Arguments marked with `@require` do not participate in the merge process and
4474+
must be filtered out of the input. If any `@require` arguments are included in
4475+
this function, it is also a precondition violation.
43964476

43974477
_Merging Arguments_
43984478

4399-
All arguments that are not marked with `@require` are merged using the
4400-
`MergeArgument` algorithm. This algorithm ensures that the final composed
4401-
argument is compatible with all definitions of that argument, resolving
4402-
differences in type, default value, and description.
4479+
All remaining arguments (those not marked `@inaccessible` or `@require`) are
4480+
merged via {MergeArgument(mergedArgument, argument)}. This algorithm ensures
4481+
that the final composed argument is compatible with all definitions of that
4482+
argument, resolving differences in type, default value, and description.
44034483

4404-
By selectively including or excluding certain arguments (via `@inaccessible` or
4484+
By selectively merging differences where possible, this algorithm ensures that
44054485
`@require`), and merging differences where possible, this algorithm ensures that
44064486
the resulting composed argument is both valid and compatible with the source
44074487
definitions.

0 commit comments

Comments
 (0)