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

Adds field selection RFC #35

Merged
merged 17 commits into from
Sep 7, 2024
143 changes: 109 additions & 34 deletions spec/Appendix A -- Field Selection.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ In this case, `"dimension.size"` and `"dimension.weight"` refer to fields of the

Consequently, a {FieldSelectionMap} must be interpreted in the context of a specific argument, its associated directive, and the relevant output type as determined by that directive's behavior.

### Examples
**Examples**

Scalar fields can be mapped directly to arguments.

Expand Down Expand Up @@ -190,11 +190,19 @@ type Product {
}
```

Instead, use the path syntax to traverse into the list elements:
Instead, use the path syntax and angle brackets to specify the list elements:
PascalSenn marked this conversation as resolved.
Show resolved Hide resolved

```graphql example
type Product {
shippingCost(dimensions: [DimensionInput] @require(field: "dimensions.{ width height }")): Currency
shippingCost(dimensions: [DimensionInput] @require(field: "dimensions[{ width height }]")): Currency
}
```

With the path syntax it is possible to also select fields from a list of nested objects
PascalSenn marked this conversation as resolved.
Show resolved Hide resolved

```graphql example
type Product {
shippingCost(partIds: @require(field: "parts[id]")): Currency
}
```

Expand Down Expand Up @@ -321,67 +329,134 @@ This operator is applied when mapping an abstract output type to a `@oneOf` inpu
{ nested: { movieId: <Movie>.id } | { productId: <Product>.id }}
```

### SelectedObjectValue
SelectedObjectValue ::
- { SelectedObjectField+ }

SelectedObjectField ::
- Name: SelectedValue

{SelectedObjectValue} are unordered lists of keyed input values wrapped in curly-braces `{}`.
It has to be used when the expected input type is an object type.

This structure is similar to the `ObjectValue` defined in the GraphQL specification, but it differs by allowing the inclusion of {Path} values within a {SelectedValue}, thus extending the traditional `ObjectValue` capabilities to support direct path selections.

A {SelectedObjectValue} following a {Path} is scoped to the type of the field selected by the {Path}.
This means that the root of all {Path} inside the selection is no longer scoped to the root (defined by @is or @require) but to the field selected by the {Path}.
This means that the root of all {SelectedValue} inside the selection is no longer scoped to the root (defined by `@is` or `@require`) but to the field selected by the {Path}. The {Path} does not effect the structure of the input type.
PascalSenn marked this conversation as resolved.
Show resolved Hide resolved

This allows to reduce repetition in the selection.
PascalSenn marked this conversation as resolved.
Show resolved Hide resolved

This allows reshaping lists of objects into input objects:
The following example is valid:

```graphql example
type Query {
findLocation(location: LocationInput! @is(field: "{ coordinates: coordinates.{ lat: x lon: y}}")): Location @lookup
type Product {
dimension: Dimension!
shippingCost(dimension: DimensionInput! @require(field: "dimension.{ size weight }")): Int! @lookup
PascalSenn marked this conversation as resolved.
Show resolved Hide resolved
}
```

type Coordinate {
x: Int!
y: Int!
The following example is equivalent to the previous one:

```graphql example
type Product {
dimensions: Dimension!
shippingCost(dimensions: DimensionInput! @require(field: "{ size: dimensions.size weight: dimensions.weight }")): Int! @lookup
}
```

type Location {
coordinates: [Coordinate!]!

### SelectedListValue
SelectedListValue ::
- [ SelectedValue ]

A {SelectedListValue} is an ordered list of {SelectedValue} wrapped in square brackets `[]`.
It is used to express semantic equivalence between a an argument expecting a list of values and the values of a list fields within the output object.
PascalSenn marked this conversation as resolved.
Show resolved Hide resolved

The {SelectedListValue} differs from the `ListValue` defined in the GraphQL specification by only allowing one {SelectedValue} as and element.
PascalSenn marked this conversation as resolved.
Show resolved Hide resolved

The following example is valid:

```graphql example
type Product {
parts: [Part!]!
partIds(partIds: [ID!]! @require(field: "parts[id]")): [ID!]!
}
```

input PositionInput {
lat: Int!
lon: Int!
In this example, the `partIds` argument is semantically equivalent to the `id` fields of the `parts` list.

The following example is invalid because it uses multiple {SelectedValue} as elements:

```graphql counter-example
type Product {
parts: [Part!]!
partIds(parts: [PartInput!]! @require(field: "parts[id name]")): [ID!]!
}

input LocationInput {
coordinates: [PositionInput!]!
input PartInput {
id: ID!
name: String!
}
```
```

A {SelectedObjectValue} can be used as an element of a {SelectedListValue} to select multiple object fields as long as the input type is a list of structurally equivalent objects.

Similar to {SelectedObjectValue}, a {SelectedListValue} following a {Path} is scoped to the type of the field selected by the {Path}.
This means that the root of all {SelectedValue} inside the selection is no longer scoped to the root (defined by `@is` or `@require`) but to the field selected by the {Path}. The {Path} does not effect the structure of the input type.
PascalSenn marked this conversation as resolved.
Show resolved Hide resolved

The same principle applies to object values, which can be used to avoid repeating the same path multiple times.

The following example is valid:

```graphql example
type Product {
dimensions: Dimension!
shippingCost(dimensions: DimensionInput! @require(field: "{ size: dimensions.size weight: dimensions.weight }")): Int! @lookup
parts: [Part!]!
partIds(parts: [PartInput!]! @require(field: "parts[{ id name }]")): [ID!]!
}

input PartInput {
id: ID!
name: String!
}
```

The following example is equivalent to the previous one:
In case the input type is a nested list, the shape of the input object must match the shape of the output object.

```graphql example
type Product {
dimensions: Dimension!
shippingCost(dimensions: DimensionInput! @require(field: "dimensions.{ size weight }")): Int! @lookup
parts: [[Part!]]!
partIds(parts: [[PartInput!]]! @require(field: "parts[[{ id name }]]")): [ID!]!
}

input PartInput {
id: ID!
name: String!
}
```

### SelectedObjectValue
SelectedObjectValue ::
- { SelectedObjectField+ }
The following example is valid:

SelectedObjectField ::
- Name: SelectedValue
```graphql example
type Query {
findLocation(location: LocationInput! @is(field: "{ coordinates: coordinates[{lat: x lon: y}]}")): Location @lookup
}

{SelectedObjectValue} are unordered lists of keyed input values wrapped in curly-braces `{}`.
This structure is similar to the `ObjectValue` defined in the GraphQL specification, but it differs by allowing the inclusion of {Path} values within a {SelectedValue}, thus extending the traditional `ObjectValue` capabilities to support direct path selections.
type Coordinate {
x: Int!
y: Int!
}

### Name
Is equivalent to the `Name` defined in the GraphQL specification.
type Location {
coordinates: [Coordinate!]!
}

input PositionInput {
lat: Int!
lon: Int!
}

input LocationInput {
coordinates: [PositionInput!]!
}
```

## Validation
Validation ensures that {FieldSelectionMap} scalars are semantically correct within the given context.
Expand Down