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

Schema Coordinates #794

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion spec/Appendix B -- Grammar Summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,13 @@ Token ::
- FloatValue
- StringValue

Punctuator :: one of ! $ & ( ) ... : = @ [ ] { | }
Punctuator ::
- DotPunctuator
- OtherPunctuator

DotPunctuator :: `.` [lookahead != {`.`, Digit}]

OtherPunctuator :: one of ! $ & ( ) ... : = @ [ ] { | }

Name ::
- NameStart NameContinue* [lookahead != NameContinue]
Expand Down Expand Up @@ -352,3 +358,10 @@ TypeSystemDirectiveLocation : one of
- `ENUM_VALUE`
- `INPUT_OBJECT`
- `INPUT_FIELD_DEFINITION`

SchemaCoordinate :
- Name
- Name . Name
- Name . Name ( Name : )
- @ Name
- @ Name ( Name : )
14 changes: 13 additions & 1 deletion spec/Section 2 -- Language.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,24 @@ characters are permitted between the characters defining a {FloatValue}.

### Punctuators

Punctuator :: one of ! $ & ( ) ... : = @ [ ] { | }
Punctuator ::
- DotPunctuator
- OtherPunctuator

DotPunctuator :: `.` [lookahead != {`.`, Digit}]

OtherPunctuator :: one of ! $ & ( ) ... : = @ [ ] { | }

GraphQL documents include punctuation in order to describe structure. GraphQL
is a data description language and not a programming language, therefore GraphQL
lacks the punctuation often used to describe mathematical expressions.

The {`.`} punctuator must not be followed by a {`.`} or {Digit}. This
ensures that the source {"..."} can only be interpreted as a single {`...`} and
not three {`.`}. It also avoids any potential ambiguity with {FloatValue}. As
an example the source {".123"} has no valid lexical representation (without this
restriction it would have been interpreted as {`.`} followed by {IntValue}).


### Names

Expand Down
113 changes: 113 additions & 0 deletions spec/Section 3 -- Type System.md
Original file line number Diff line number Diff line change
Expand Up @@ -2067,3 +2067,116 @@ to the relevant IETF specification.
```graphql example
scalar UUID @specifiedBy(url: "https://tools.ietf.org/html/rfc4122")
```

## Schema Coordinates

SchemaCoordinate :
- Name
- Name . Name
- Name . Name ( Name : )
- @ Name
- @ Name ( Name : )

:: A *schema coordinate* is a human readable string that uniquely identifies a
*schema element* within a GraphQL Schema.

:: A *schema element* is a specific instance of a named type, field, input
field, enum value, field argument, directive, or directive argument.

A *schema coordinate* is always unique. Each *schema element* may be referenced
by exactly one possible schema coordinate.

A *schema coordinate* may refer to either a defined or built-in *schema element*.
For example, `String` and `@deprecated(reason:)` are both valid schema
coordinates which refer to built-in schema elements. However it must not refer
to a meta-field. For example, `Business.__typename` is *not* a valid
schema coordinate.

Note: A {SchemaCoordinate} is not a definition within a GraphQL {Document}, but
a separate standalone grammar, intended to be used by tools to reference types,
fields, and other *schema element*s. For example as references within
documentation, or as lookup keys in usage frequency tracking.

**Semantics**

To refer to a *schema element*, a *schema coordinate* must be interpreted in the
context of a GraphQL {schema}.

SchemaCoordinate : Name
1. Let {typeName} be the value of the first {Name}.
2. Return the type in the {schema} named {typeName}.

SchemaCoordinate : Name . Name
1. Let {typeName} be the value of the first {Name}.
2. Let {type} be the type in the {schema} named {typeName}.
3. If {type} is an Enum type:
1. Let {enumValueName} be the value of the second {Name}.
2. Return the enum value of {type} named {enumValueName}.
4. Otherwise if {type} is an Input Object type:
1. Let {inputFieldName} be the value of the second {Name}.
2. Return the input field of {type} named {inputFieldName}.
5. Otherwise:
1. Assert {type} must be an Object or Interface type.
2. Let {fieldName} be the value of the second {Name}.
3. Return the field of {type} named {fieldName}.
Comment on lines +2112 to +2121
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this section should also specify the behaviour for Union types.
At the moment the spec does not define the behaviour for Union.UnionTypeMember

Suggested change
3. If {type} is an Enum type:
1. Let {enumValueName} be the value of the second {Name}.
2. Return the enum value of {type} named {enumValueName}.
4. Otherwise if {type} is an Input Object type:
1. Let {inputFieldName} be the value of the second {Name}.
2. Return the input field of {type} named {inputFieldName}.
5. Otherwise:
1. Assert {type} must be an Object or Interface type.
2. Let {fieldName} be the value of the second {Name}.
3. Return the field of {type} named {fieldName}.
3. If {type} is an Union type:
1. Assert {type} must be an Enum, Input Object, Object or Interface type.
4. If {type} is an Enum type:
1. Let {enumValueName} be the value of the second {Name}.
2. Return the enum value of {type} named {enumValueName}.
5. Otherwise if {type} is an Input Object type:
1. Let {inputFieldName} be the value of the second {Name}.
2. Return the input field of {type} named {inputFieldName}.
6. Otherwise:
1. Assert {type} must be an Object or Interface type.
2. Let {fieldName} be the value of the second {Name}.
3. Return the field of {type} named {fieldName}.

Copy link
Contributor Author

@magicmark magicmark Dec 30, 2021

Choose a reason for hiding this comment

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

Perhaps I'm misunderstanding your question, but to clarify:

UnionTypeMember

The definition for this would live somewhere else in the schema, outside of the context of the union. You would write the schema coordinate to that instead.

i.e. Union.UnionTypeMember is not a valid schema coordinate.

The rule of thumb is: there should only be one way to reference a thing with Schema Coordinates

Is there still some ambiguity in this spec?

Copy link
Contributor

Choose a reason for hiding this comment

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

@magicmark Makes sense to me

Copy link
Contributor Author

@magicmark magicmark Dec 31, 2021

Choose a reason for hiding this comment

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

@leebyron is it worth keeping in the clarification (or something similar) from a previous commit?

https://github.com/magicmark/graphql-spec/blob/ac5318f9a9f04d0f8dd03d3758d5ca2654f7f7d7/spec/Section%203%20--%20Type%20System.md?plain=1#L391-L399

My gut says this may crop up again and cause some confusion - worth the counter example?

Copy link

Choose a reason for hiding this comment

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

@leebyron is it worth keeping in the clarification (or something similar) from a previous commit?

magicmark/graphql-spec@ac5318f/spec/Section%203%20--%20Type%20System.md?plain=1#L391-L399

My gut says this may crop up again and cause some confusion - worth the counter example?

Just chiming in. From my perspective the counter example would be ideal (maybe even clarify that directives on types are also invalid?).

Maybe add a line on why union members are not valid schema coordinates, instead of solely stating that they are not?

- Note: You may not select members inside a union definition.
+ Note: Union members do not uniquely identify elements in the schema. Therefore the following counter example is *not* considered a valid Schema Coordinate:

...or something along those lines

Copy link
Contributor Author

@magicmark magicmark Dec 31, 2021

Choose a reason for hiding this comment

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

fwiw this is all sort of encapsulated by the following line the spec:

A schema coordinate is always unique. Each schema element may be referenced
by exactly one possible schema coordinate.

++ for the explainer, I would maybe try and reuse this language (e.g. "as stated above, each schema element...."), or hyperlink back to this source of truth somehow


SchemaCoordinate : Name . Name ( Name : )
1. Let {typeName} be the value of the first {Name}.
2. Let {type} be the type in the {schema} named {typeName}.
3. Assert {type} must be an Object or Interface type.
4. Let {fieldName} be the value of the second {Name}.
5. Let {field} be the field of {type} named {fieldName}.
6. Assert {field} must exist.
7. Let {fieldArgumentName} be the value of the third {Name}.
8. Return the argument of {field} named {fieldArgumentName}.

SchemaCoordinate : @ Name
1. Let {directiveName} be the value of the first {Name}.
2. Return the directive in the {schema} named {directiveName}.

SchemaCoordinate : @ Name ( Name : )
1. Let {directiveName} be the value of the first {Name}.
2. Let {directive} be the directive in the {schema} named {directiveName}.
3. Assert {directive} must exist.
4. Let {directiveArgumentName} be the value of the second {Name}.
5. Return the argument of {directive} named {directiveArgumentName}.

**Examples**

| Element Kind | *Schema Coordinate* | *Schema Element* |
| ------------------ | -------------------------------- | ----------------------------------------------------------------------- |
| Named Type | `Business` | `Business` type |
| Field | `Business.name` | `name` field on the `Business` type |
| Input Field | `SearchCriteria.filter` | `filter` input field on the `SearchCriteria` input object type |
| Enum Value | `SearchFilter.OPEN_NOW` | `OPEN_NOW` value of the `SearchFilter` enum |
| Field Argument | `Query.searchBusiness(criteria:)`| `criteria` argument on the `searchBusiness` field on the `Query` type |
| Directive | `@private` | `@private` directive |
| Directive Argument | `@private(scope:)` | `scope` argument on the `@private` directive |

The table above shows an example of a *schema coordinate* for every kind of
*schema element* based on the schema below.

```graphql
type Query {
searchBusiness(criteria: SearchCriteria!): [Business]
}

input SearchCriteria {
name: String
filter: SearchFilter
}

enum SearchFilter {
OPEN_NOW
DELIVERS_TAKEOUT
VEGETARIAN_MENU
}

type Business {
id: ID
name: String
email: String @private(scope: "loggedIn")
}

directive @private(scope: String!) on FIELD_DEFINITION
```