From 54f43789894e9233710dc66814b7d6b216d979dc Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Mon, 19 Jun 2017 16:46:58 -0700 Subject: [PATCH] RFC: Scalar serialize as built-in scalar type Currently, Custom Scalars only describe their name and no other behavior about them can be programmatically determined. However in practice most custom scalar types are specializations of one of the existing built-in scalar types (String, Int, Float, etc.). This proposes adding one additional piece of metadata to custom scalars, a "serializes as" type which must be a built-in scalar type. This serialize as type describes what kinds of values are allowed to be provided as input to that type and what kind of value is expected to serialize from that type. This metadata is useful for code-generation systems which expect to generate code in well typed environments. It can also improve the validation accuracy and IDE ergonomics by providing a similar level of information about custom scalars as exist for the built-in scalars. To provide this metadata to those kinds of tools, this also extends the introspection system to return the serialize as type in the `ofType` field. --- spec/Section 3 -- Type System.md | 69 ++++++++++++++++++++++++++---- spec/Section 4 -- Introspection.md | 8 +++- 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 9ac1b049e..6c927b27a 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -335,13 +335,7 @@ response format being used, there may be a more appropriate primitive for the given scalar type, and server should use those types when appropriate. GraphQL provides a number of built-in scalars, but type systems can add -additional scalars with semantic meaning. For example, a GraphQL system could -define a scalar called `Time` which, while serialized as a string, promises to -conform to ISO-8601. When querying a field of type `Time`, you can then rely on -the ability to parse the result with an ISO-8601 parser and use a -client-specific primitive for time. Another example of a potentially useful -custom scalar is `Url`, which serializes as a string, but is guaranteed by -the server to be a valid URL. +additional custom scalars with specific semantic meaning. ```graphql example scalar Time @@ -397,8 +391,8 @@ For all types below, with the exception of Non-Null, if the explicit value **Built-in Scalars** GraphQL provides a basic set of well-defined Scalar types. A GraphQL server -should support all of these types, and a GraphQL server which provide a type by -these names must adhere to the behavior described below. +should support all of these types, and a GraphQL server which provides a type by +these names must adhere to the behavior described for that type. ### Int @@ -531,6 +525,63 @@ input values (such as `4.0`), must raise a query error indicating an incorrect type. +### Custom Scalars + +In addition to GraphQL's built-in scalars, a type system may add additional +custom scalars which provide specific semantic meaning. + +For example, a type system might define a scalar called `Time` which, while +serialized as `String`, promises to conform to ISO-8601. When querying a field +of type `Time`, you can then rely on the ability to parse the result with an +ISO-8601 parser and use a client-specific primitive for time. + +Another example of a potentially useful custom scalar is `Url`, which serializes +as `String`, but is guaranteed by the server to be a valid URL. + +**Serialize as type** + +A custom scalar may provide a built-in scalar type which describes how the +custom scalar is serialized. This provides more information for how execution +responses are query variable inputs are coerced. If provided, this built-in +scalar can be used both for improved validation results and stricter guarantees +during execution. + +While a custom scalar type is not required to provide a serialize as type, if +one is provided it must be one of the built-in scalar types (`Int`, `Float`, +`String`, `Boolean`, `ID`). + +**Result Coercion** + +If a custom scalar provides a built-in scalar type which it serializes as, then +that custom scalar must coerce using the same rules as that built-in scalar +type. Using the example above, if a custom scalar `Time` declares it serializes +as a `String`, then the result of a field of type `Time` must produce a +`String`, which may be subject to additional coercion rules specific to the +custom scalar type. + +If a custom scalar does not provide a built-in scalar type which it serializes +as, then since this coercion behavior is not observable to clients of the +GraphQL server, the precise rules of coercion are left to the implementation, +and the serialized form will not be known ahead of time. + +**Input Coercion** + +If a custom scalar provides a built-in scalar type which it serializes as, then +that custom scalar should accept the same kinds of input which that built-in +scalar type accepts and must produce an error for the kinds of input for which +that built-in scalar type would produce an error. Using the example above, if a +custom scalar `Time` declares it serializes as a `String`, then it should accept +`String` values and must not accept `Int` or `Float` values as input. + +A custom scalar may provide additional rules for coercing input values as +implementation details, producing a query error when it deems necessary. + +If a custom scalar does not provide a built-in scalar type which it serializes +as, then since this input coercion behavior is not observable to clients of the +GraphQL server, the precise rules of coercion are left to the implementation, +and the allowed input values will not be known ahead of time. + + ### Scalar Extensions ScalarTypeExtension : diff --git a/spec/Section 4 -- Introspection.md b/spec/Section 4 -- Introspection.md index 8a24e2882..1b6d16a83 100644 --- a/spec/Section 4 -- Introspection.md +++ b/spec/Section 4 -- Introspection.md @@ -144,7 +144,7 @@ type __Type { # INPUT_OBJECT only inputFields: [__InputValue!] - # NON_NULL and LIST only + # SCALAR, NON_NULL, and LIST only ofType: __Type } @@ -232,7 +232,10 @@ actually valid. These kinds are listed in the `__TypeKind` enumeration. Represents scalar types such as Int, String, and Boolean. Scalars cannot have fields. -A GraphQL type designer should describe the data format and scalar coercion +Custom scalars may provide a built-in scalar type it serializes as in `ofType`, +which describe primary data format and scalar coercion rules. + +A GraphQL type designer should describe further data format and scalar coercion rules in the description field of any scalar. Fields @@ -240,6 +243,7 @@ Fields * `kind` must return `__TypeKind.SCALAR`. * `name` must return a String. * `description` may return a String or {null}. +* `ofType`: one of the built-in scalar types or {null}. * All other fields must return {null}.