diff --git a/book/src/types/enums.md b/book/src/types/enums.md index c84cfe057..ac2bd85fd 100644 --- a/book/src/types/enums.md +++ b/book/src/types/enums.md @@ -75,7 +75,7 @@ enum Episode { ### Documentation and deprecation -Just like when [defining GraphQL objects](objects/index.md#documentation), the [GraphQL enum][0] type and its values could be [documented][4] and [deprecated][5] via `#[graphql(description = "...")]` and `#[graphql(deprecated = "...")]`/[`#[deprecated]`][13] attributes: +Just like when [defining GraphQL objects](objects/index.md#documentation), the [GraphQL enum][0] type and its values could be [documented][4] and [deprecated][9] via `#[graphql(description = "...")]` and `#[graphql(deprecated = "...")]`/[`#[deprecated]`][13] attributes: ```rust # extern crate juniper; # use juniper::GraphQLEnum; @@ -105,7 +105,7 @@ enum StarWarsEpisode { # # fn main() {} ``` -> **NOTE**: Only [GraphQL object][6]/[interface][7] fields and [GraphQL enum][0] values can be [deprecated][5]. +> **NOTE**: Only [GraphQL object][6]/[interface][7]/[input object][8] fields, [arguments][5] and [GraphQL enum][0] values can be [deprecated][9]. ### Ignoring @@ -145,7 +145,9 @@ enum Episode { [2]: https://docs.rs/juniper/0.17.0/juniper/derive.GraphQLEnum.html [3]: https://doc.rust-lang.org/reference/items/enumerations.html [4]: https://spec.graphql.org/October2021#sec-Descriptions -[5]: https://spec.graphql.org/October2021#sec--deprecated +[5]: https://spec.graphql.org/October2021#sec-Language.Arguments [6]: https://spec.graphql.org/October2021#sec-Objects [7]: https://spec.graphql.org/October2021#sec-Interfaces -[13]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-deprecated-attribute \ No newline at end of file +[8]: https://spec.graphql.org/October2021#sec-Input-Objects +[9]: https://spec.graphql.org/October2021#sec--deprecated +[13]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-deprecated-attribute diff --git a/book/src/types/input_objects.md b/book/src/types/input_objects.md index 14e58bfa6..318722d58 100644 --- a/book/src/types/input_objects.md +++ b/book/src/types/input_objects.md @@ -84,9 +84,9 @@ struct Person { > **TIP**: Supported policies are: `SCREAMING_SNAKE_CASE`, `camelCase` and `none` (disables any renaming). -### Documentation +### Documentation and deprecation -Similarly, [GraphQL descriptions][7] may be provided by either using [Rust doc comments][6] or with the `#[graphql(description = "...")]` attribute: +Similarly, [GraphQL input fields][1] may also be [documented][7] and [deprecated][9] via `#[graphql(description = "...")]` and `#[graphql(deprecated = "...")]`/[`#[deprecated]`][13] attributes: ```rust # extern crate juniper; # use juniper::GraphQLInputObject; @@ -102,12 +102,20 @@ struct Person { /// This doc comment is visible in both Rust API docs and GraphQL schema /// descriptions. + // Only `Null`able input fields or non-`Null` input fields with default values + // can be deprecated. + #[graphql(default, deprecated = "Just because.")] age: i32, + + // If no explicit deprecation reason is provided, + // then the default "No longer supported" one is used. + #[deprecated] + another: Option, // has no description in GraphQL schema } # # fn main() {} ``` -> **NOTE**: As of [October 2021 GraphQL specification][spec], [GraphQL input object][0]'s fields **cannot be** [deprecated][9]. +> **NOTE**: Only [GraphQL input object][0]/[object][8]/[interface][11] fields, [arguments][5] and [GraphQL enum][10] values can be [deprecated][9]. ### Ignoring @@ -150,14 +158,17 @@ struct Point2D { [Juniper]: https://docs.rs/juniper [Rust]: https://www.rust-lang.org [struct]: https://doc.rust-lang.org/reference/items/structs.html -[spec]: https://spec.graphql.org/October2021 [0]: https://spec.graphql.org/October2021#sec-Input-Objects +[1]: https://spec.graphql.org/October2021#InputFieldsDefinition [2]: https://docs.rs/juniper/0.17.0/juniper/derive.GraphQLInputObject.html [4]: https://spec.graphql.org/October2021#sec-Language.Fields [5]: https://spec.graphql.org/October2021#sec-Language.Arguments [6]: https://doc.rust-lang.org/reference/comments.html#doc-comments [7]: https://spec.graphql.org/October2021#sec-Descriptions +[8]: https://spec.graphql.org/October2021#sec-Objects [9]: https://spec.graphql.org/October2021#sec--deprecated [10]: https://spec.graphql.org/October2021#sec-Enums +[11]: https://spec.graphql.org/October2021#sec-Interfaces [12]: https://spec.graphql.org/October2021#sec-Scalars +[13]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-deprecated-attribute diff --git a/book/src/types/interfaces.md b/book/src/types/interfaces.md index f2b7ffb44..0443d2cf0 100644 --- a/book/src/types/interfaces.md +++ b/book/src/types/interfaces.md @@ -317,7 +317,7 @@ trait Person { ### Documentation and deprecation -Similarly, [GraphQL fields][4] of [interfaces][0] may also be [documented][7] and [deprecated][9] via `#[graphql(description = "...")]` and `#[graphql(deprecated = "...")]`/[`#[deprecated]`][13] attributes: +Similarly, [GraphQL fields][4] (and their [arguments][5]) of [interfaces][0] may also be [documented][7] and [deprecated][9] via `#[graphql(description = "...")]` and `#[graphql(deprecated = "...")]`/[`#[deprecated]`][13] attributes: ```rust # extern crate juniper; # use juniper::graphql_interface; @@ -340,16 +340,24 @@ trait Person { /// This doc comment is visible in both Rust API docs and GraphQL schema /// descriptions. #[graphql(deprecated = "Just because.")] - fn deprecated_graphql() -> bool; + fn deprecated_graphql( + // Only `Null`able arguments or non-`Null` arguments with default values + // can be deprecated. + #[graphql(default, deprecated = "No need.")] arg: bool, + ) -> bool; // Standard Rust's `#[deprecated]` attribute works too! #[deprecated(note = "Reason is optional, btw!")] - fn deprecated_standard() -> bool; // has no description in GraphQL schema + fn deprecated_standard( // has no description in GraphQL schema + // If no explicit deprecation reason is provided, + // then the default "No longer supported" one is used. + #[graphql(deprecated)] arg: Option, + ) -> bool; } # # fn main() {} ``` -> **NOTE**: Only [GraphQL interface][0]/[object][10] fields and [GraphQL enum][11] values can be [deprecated][9]. +> **NOTE**: Only [GraphQL interface][0]/[object][10]/[input object][8] fields, [arguments][5] and [GraphQL enum][11] values can be [deprecated][9]. ### Ignoring @@ -403,6 +411,7 @@ trait Person { [5]: https://spec.graphql.org/October2021#sec-Language.Arguments [6]: https://spec.graphql.org/October2021#sec-Non-Null [7]: https://spec.graphql.org/October2021#sec-Descriptions +[8]: https://spec.graphql.org/October2021#sec-Input-Objects [9]: https://spec.graphql.org/October2021#sec--deprecated [10]: https://spec.graphql.org/October2021#sec-Objects [11]: https://spec.graphql.org/October2021#sec-Enums diff --git a/book/src/types/objects/complex_fields.md b/book/src/types/objects/complex_fields.md index ef9f31a00..82596e9dc 100644 --- a/book/src/types/objects/complex_fields.md +++ b/book/src/types/objects/complex_fields.md @@ -116,7 +116,7 @@ impl Person { ### Documentation and deprecation -Similarly, [GraphQL fields][4] may also be [documented][7] and [deprecated][9] via `#[graphql(description = "...")]` and `#[graphql(deprecated = "...")]`/[`#[deprecated]`][13] attributes: +Similarly, [GraphQL fields][4] (and their [arguments][5]) may also be [documented][7] and [deprecated][9] via `#[graphql(description = "...")]` and `#[graphql(deprecated = "...")]`/[`#[deprecated]`][13] attributes: ```rust # extern crate juniper; # use juniper::graphql_object; @@ -145,20 +145,28 @@ impl Person { /// This doc comment is visible in both Rust API docs and GraphQL schema /// descriptions. #[graphql(deprecated = "Just because.")] - fn deprecated_graphql() -> bool { + fn deprecated_graphql( + // Only `Null`able arguments or non-`Null` arguments with default values + // can be deprecated. + #[graphql(default, deprecated = "No need.")] arg: bool, + ) -> bool { true } // Standard Rust's `#[deprecated]` attribute works too! #[deprecated(note = "Reason is optional, btw!")] - fn deprecated_standard() -> bool { // has no description in GraphQL schema + fn deprecated_standard( // has no description in GraphQL schema + // If no explicit deprecation reason is provided, + // then the default "No longer supported" one is used. + #[graphql(deprecated)] arg: Option, + ) -> bool { false } } # # fn main() {} ``` -> **NOTE**: Only [GraphQL object][0]/[interface][11] fields and [GraphQL enum][10] values can be [deprecated][9]. +> **NOTE**: Only [GraphQL object][0]/[interface][11]/[input object][8] fields, [arguments][5] and [GraphQL enum][10] values can be [deprecated][9]. ### Ignoring @@ -223,6 +231,7 @@ impl Person { [5]: https://spec.graphql.org/October2021#sec-Language.Arguments [6]: https://doc.rust-lang.org/reference/items/implementations.html#inherent-implementations [7]: https://spec.graphql.org/October2021#sec-Descriptions +[8]: https://spec.graphql.org/October2021#sec-Input-Objects [9]: https://spec.graphql.org/October2021#sec--deprecated [10]: https://spec.graphql.org/October2021#sec-Enums [11]: https://spec.graphql.org/October2021#sec-Interfaces diff --git a/book/src/types/objects/error/field.md b/book/src/types/objects/error/field.md index d73c30bab..1df54e72e 100644 --- a/book/src/types/objects/error/field.md +++ b/book/src/types/objects/error/field.md @@ -37,11 +37,11 @@ impl Example { # fn main() {} ``` -[`FieldResult`][21] is an alias for [`Result`][22], which is the [error type][1] all fallible [fields][6] must return. By using the [`?` operator][14], any type that implements the [`Display` trait][15] (which most of the error types out there do) can be automatically converted into a [`FieldError`][22]. +[`FieldResult`][21] is an alias for [`Result`][22], which is the [error type][1] all fallible [fields][6] must return. By using the [`?` operator][14], any type that implements the [`Display` trait][19] (which most of the error types out there do) can be automatically converted into a [`FieldError`][22]. > **TIP**: If a custom conversion into a [`FieldError`][22] is needed (to [fill up `extensions`][2], for example), the [`IntoFieldError` trait][23] should be implemented. -> **NOTE**: [`FieldError`][22]s are [GraphQL field errors][1] and are [not visible][9] in a [GraphQL schema][8] in any way. +> **NOTE**: [`FieldError`][22]s are [GraphQL field errors][1] and are [not visible][15] in a [GraphQL schema][8] in any way. @@ -172,12 +172,12 @@ And the specified structured error information will be included into the [error' [6]: https://spec.graphql.org/October2021#sec-Language.Fields [7]: https://spec.graphql.org/October2021#sec-Response [8]: https://graphql.org/learn/schema -[9]: https://spec.graphql.org/October2021#sec-Introspection [11]: https://doc.rust-lang.org/book/ch09-00-error-handling.html [12]: https://doc.rust-lang.org/stable/std/result/enum.Result.html [13]: https://doc.rust-lang.org/stable/std/macro.panic.html [14]: https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator -[15]: https://doc.rust-lang.org/stable/std/fmt/trait.Display.html +[15]: https://spec.graphql.org/October2021#sec-Introspection +[19]: https://doc.rust-lang.org/stable/std/fmt/trait.Display.html [21]: https://docs.rs/juniper/0.17.0/juniper/executor/type.FieldResult.html [22]: https://docs.rs/juniper/0.17.0/juniper/executor/struct.FieldError.html [23]: https://docs.rs/juniper/0.17.0/juniper/executor/trait.IntoFieldError.html diff --git a/book/src/types/objects/index.md b/book/src/types/objects/index.md index 3465fdaf1..85c590ea0 100644 --- a/book/src/types/objects/index.md +++ b/book/src/types/objects/index.md @@ -34,7 +34,7 @@ This creates a [GraphQL object][0] type called `Person`, with two fields: `name` ### Documentation -We should take advantage of the fact that [GraphQL] is [self-documenting][5] and add descriptions to the defined [GraphQL object][0] type and its fields. [Juniper] will automatically use associated [Rust doc comments][6] as [GraphQL descriptions][7]: +We should take advantage of the fact that [GraphQL] is [self-documenting][15] and add descriptions to the defined [GraphQL object][0] type and its fields. [Juniper] will automatically use associated [Rust doc comments][6] as [GraphQL descriptions][7]: ```rust # extern crate juniper; # use juniper::GraphQLObject; @@ -145,7 +145,7 @@ struct Person { # # fn main() {} ``` -> **NOTE**: Only [GraphQL object][0]/[interface][11] fields and [GraphQL enum][10] values can be [deprecated][9]. +> **NOTE**: Only [GraphQL object][0]/[interface][11]/[input object][8] fields, [arguments][5] and [GraphQL enum][10] values can be [deprecated][9]. ### Ignoring @@ -216,7 +216,7 @@ Because `Person` is a valid [GraphQL] type, we can have a `Vec` in a [st [2]: https://docs.rs/juniper/0.17.0/juniper/derive.GraphQLObject.html [3]: https://docs.rs/juniper/0.17.0/juniper/attr.graphql_object.html [4]: https://spec.graphql.org/October2021#sec-Non-Null -[5]: https://spec.graphql.org/October2021#sec-Introspection +[5]: https://spec.graphql.org/October2021#sec-Language.Arguments [6]: https://doc.rust-lang.org/reference/comments.html#doc-comments [7]: https://spec.graphql.org/October2021#sec-Descriptions [8]: https://spec.graphql.org/October2021#sec-Input-Objects @@ -226,3 +226,4 @@ Because `Person` is a valid [GraphQL] type, we can have a `Vec` in a [st [12]: https://spec.graphql.org/October2021#sec-List [13]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-deprecated-attribute [14]: https://spec.graphql.org/October2021#sel-EAFdRDHAAEJDAoBxzT +[15]: https://spec.graphql.org/October2021#sec-Introspection diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 20db1534d..4863ea4bf 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -6,6 +6,51 @@ All user visible changes to `juniper` crate will be documented in this file. Thi +## main + +[Diff](/../../compare/juniper-v0.17.0...main) | [Milestone](/../../milestone/9) + +### BC Breaks + +- [September 2025] GraphQL spec: ([#1347]) + - Made `includeDeprecated` argument of `__Type.fields`, `__Type.enumValues`, `__Type.inputFields`, `__Field.args` and `__Directive.args` fields non-`Null`. ([#1348], [graphql/graphql-spec#1142]) + - Made `@deprecated(reason:)` argument non-`Null`. ([#1348], [graphql/graphql-spec#1040]) + +### Added + +- [September 2025] GraphQL spec: ([#1347]) + - `__Type.isOneOf` field. ([#1348], [graphql/graphql-spec#825]) + - `SCHEMA`, `OBJECT`, `ARGUMENT_DEFINITION`, `INTERFACE`, `UNION`, `ENUM`, `INPUT_OBJECT` and `INPUT_FIELD_DEFINITION` values to `__DirectiveLocation` enum. ([#1348]) + - Arguments and input object fields deprecation: ([#1348], [#864], [graphql/graphql-spec#525], [graphql/graphql-spec#805]) + - Placing `#[graphql(deprecated)]` and `#[deprecated]` attributes on struct fields in `#[derive(GraphQLInputObject)]` macro. + - Placing `#[graphql(deprecated)]` attribute on method arguments in `#[graphql_object]` and `#[graphql_interface]` macros. + - Placing `@deprecated` directive on arguments and input object fields. + - `includeDeprecated` argument to `__Type.inputFields`, `__Field.args` and `__Directive.args` fields. + - `__InputValue.isDeprecated` and `__InputValue.deprecationReason` fields. + - `schema::meta::Argument::deprecation_status` field. + +### Changed + +- [September 2025] GraphQL spec: ([#1347]) + - Canonical introspection query to [16.11.0 version of GraphQL.js](https://github.com/graphql/graphql-js/blob/v16.11.0/src/utilities/getIntrospectionQuery.ts#L75). ([#1348]) + +### Fixed + +- Incorrect `__Type.specifiedByUrl` field to `__Type.specifiedByURL`. ([#1348]) +- Missing `@specifiedBy(url:)` directive in [SDL] generated by `RootNode::as_sdl()` and `RootNode::as_document()` methods. ([#1348]) + +[#864]: /../../issues/864 +[#1347]: /../../issues/1347 +[#1348]: /../../pull/1348 +[graphql/graphql-spec#525]: https://github.com/graphql/graphql-spec/pull/525 +[graphql/graphql-spec#805]: https://github.com/graphql/graphql-spec/pull/805 +[graphql/graphql-spec#825]: https://github.com/graphql/graphql-spec/pull/825 +[graphql/graphql-spec#1040]: https://github.com/graphql/graphql-spec/pull/1040 +[graphql/graphql-spec#1142]: https://github.com/graphql/graphql-spec/pull/1142 + + + + ## [0.17.0] ยท 2025-09-08 [0.17.0]: /../../tree/juniper-v0.17.0/juniper @@ -398,3 +443,5 @@ See [old CHANGELOG](/../../blob/juniper-v0.15.12/juniper/CHANGELOG.md). [object safety]: https://doc.rust-lang.org/reference/items/traits.html#object-safety [orphan rules]: https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules [Semantic Versioning 2.0.0]: https://semver.org +[September 2025]: https://spec.graphql.org/September2025 +[SDL]: https://graphql.org/learn/schema#type-language diff --git a/juniper/benches/bench.rs b/juniper/benches/bench.rs index 7284c86dd..e1c843bab 100644 --- a/juniper/benches/bench.rs +++ b/juniper/benches/bench.rs @@ -17,16 +17,16 @@ fn query_type_name(b: &mut Bencher) { EmptySubscription::::new(), ); - let doc = r#" - query IntrospectionQueryTypeQuery { - __schema { - queryType { - name - } - } - }"#; + // language=GraphQL + let query = "query IntrospectionQueryTypeQuery { + __schema { + queryType { + name + } + } + }"; - b.iter(|| execute_sync(doc, None, &schema, &graphql_vars! {}, &database)); + b.iter(|| execute_sync(query, None, &schema, &graphql_vars! {}, &database)); } fn introspection_query(b: &mut Bencher) { @@ -42,101 +42,9 @@ fn introspection_query(b: &mut Bencher) { EmptySubscription::::new(), ); - let doc = r#" - query IntrospectionQuery { - __schema { - queryType { name } - mutationType { name } - subscriptionType { name } - types { - ...FullType - } - directives { - name - description - locations - args { - ...InputValue - } - } - } - } - - fragment FullType on __Type { - kind - name - description - fields(includeDeprecated: true) { - name - description - args { - ...InputValue - } - type { - ...TypeRef - } - isDeprecated - deprecationReason - } - inputFields { - ...InputValue - } - interfaces { - ...TypeRef - } - enumValues(includeDeprecated: true) { - name - description - isDeprecated - deprecationReason - } - possibleTypes { - ...TypeRef - } - } - - fragment InputValue on __InputValue { - name - description - type { ...TypeRef } - defaultValue - } - - fragment TypeRef on __Type { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - } - } - } - } - } - } - } - } -"#; + let query = include_str!("../src/introspection/query.graphql"); - b.iter(|| execute_sync(doc, None, &schema, &graphql_vars! {}, &database)); + b.iter(|| execute_sync(query, None, &schema, &graphql_vars! {}, &database)); } benchmark_group!(queries, query_type_name, introspection_query); diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index a34fea301..85d35d637 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -1217,7 +1217,7 @@ impl Registry { Argument::new(name, self.get_type::(info)) } - /// Creates an [`Argument`] with the provided default `value`. + /// Creates an [`Argument`] with the provided `name` and default `value`. pub fn arg_with_default( &mut self, name: impl Into, @@ -1228,7 +1228,8 @@ impl Registry { T: GraphQLType + ToInputValue + FromInputValue, S: ScalarValue, { - Argument::new(name, self.get_type::(info)).default_value(value.to_input_value()) + self.arg::(name, info) + .default_value(value.to_input_value()) } fn insert_placeholder(&mut self, name: Name, of_type: Type) { diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs index 8cfe90b76..29d15a65b 100644 --- a/juniper/src/executor_tests/introspection/mod.rs +++ b/juniper/src/executor_tests/introspection/mod.rs @@ -477,7 +477,7 @@ async fn scalar_introspection() { name kind description - specifiedByUrl + specifiedByURL fields { name } interfaces { name } possibleTypes { name } @@ -513,7 +513,7 @@ async fn scalar_introspection() { "name": "SampleScalar", "kind": "SCALAR", "description": null, - "specifiedByUrl": null, + "specifiedByURL": null, "fields": null, "interfaces": null, "possibleTypes": null, diff --git a/juniper/src/introspection/mod.rs b/juniper/src/introspection/mod.rs index e59a492e6..4d8bf7c16 100644 --- a/juniper/src/introspection/mod.rs +++ b/juniper/src/introspection/mod.rs @@ -1,10 +1,10 @@ -/// From pub(crate) const INTROSPECTION_QUERY: &str = include_str!("./query.graphql"); pub(crate) const INTROSPECTION_QUERY_WITHOUT_DESCRIPTIONS: &str = include_str!("./query_without_descriptions.graphql"); -/// The desired GraphQL introspection format for the canonical query -/// () +/// Desired GraphQL introspection format for the [canonical introspection query][0]. +/// +/// [0]: https://github.com/graphql/graphql-js/blob/v16.11.0/src/utilities/getIntrospectionQuery.ts#L75 #[derive(Clone, Copy, Debug, Default)] pub enum IntrospectionFormat { /// The canonical GraphQL introspection query. diff --git a/juniper/src/introspection/query.graphql b/juniper/src/introspection/query.graphql index fe522f4ef..997106650 100644 --- a/juniper/src/introspection/query.graphql +++ b/juniper/src/introspection/query.graphql @@ -1,15 +1,9 @@ query IntrospectionQuery { __schema { description - queryType { - name - } - mutationType { - name - } - subscriptionType { - name - } + queryType { name kind } + mutationType { name kind } + subscriptionType { name kind } types { ...FullType } @@ -18,21 +12,23 @@ query IntrospectionQuery { description isRepeatable locations - args { + args(includeDeprecated: true) { ...InputValue } } } } + fragment FullType on __Type { kind name description - specifiedByUrl + specifiedByURL + isOneOf fields(includeDeprecated: true) { name description - args { + args(includeDeprecated: true) { ...InputValue } type { @@ -41,7 +37,7 @@ fragment FullType on __Type { isDeprecated deprecationReason } - inputFields { + inputFields(includeDeprecated: true) { ...InputValue } interfaces { @@ -57,14 +53,16 @@ fragment FullType on __Type { ...TypeRef } } + fragment InputValue on __InputValue { name description - type { - ...TypeRef - } + type { ...TypeRef } defaultValue + isDeprecated + deprecationReason } + fragment TypeRef on __Type { kind name @@ -89,6 +87,14 @@ fragment TypeRef on __Type { ofType { kind name + ofType { + kind + name + ofType { + kind + name + } + } } } } @@ -96,4 +102,4 @@ fragment TypeRef on __Type { } } } -} +} \ No newline at end of file diff --git a/juniper/src/introspection/query_without_descriptions.graphql b/juniper/src/introspection/query_without_descriptions.graphql index 0699bc5b3..4cd6f379b 100644 --- a/juniper/src/introspection/query_without_descriptions.graphql +++ b/juniper/src/introspection/query_without_descriptions.graphql @@ -1,14 +1,8 @@ query IntrospectionQuery { __schema { - queryType { - name - } - mutationType { - name - } - subscriptionType { - name - } + queryType { name kind } + mutationType { name kind } + subscriptionType { name kind } types { ...FullType } @@ -16,19 +10,21 @@ query IntrospectionQuery { name isRepeatable locations - args { + args(includeDeprecated: true) { ...InputValue } } } } + fragment FullType on __Type { kind name - specifiedByUrl + specifiedByURL + isOneOf fields(includeDeprecated: true) { name - args { + args(includeDeprecated: true) { ...InputValue } type { @@ -37,7 +33,7 @@ fragment FullType on __Type { isDeprecated deprecationReason } - inputFields { + inputFields(includeDeprecated: true) { ...InputValue } interfaces { @@ -52,13 +48,15 @@ fragment FullType on __Type { ...TypeRef } } + fragment InputValue on __InputValue { name - type { - ...TypeRef - } + type { ...TypeRef } defaultValue + isDeprecated + deprecationReason } + fragment TypeRef on __Type { kind name @@ -83,6 +81,14 @@ fragment TypeRef on __Type { ofType { kind name + ofType { + kind + name + ofType { + kind + name + } + } } } } diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 373ee877b..e79c5290a 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -297,7 +297,9 @@ where .await } -/// Execute the reference introspection query in the provided schema +/// Executes the [canonical introspection query][0] in the provided schema. +/// +/// [0]: https://github.com/graphql/graphql-js/blob/v16.11.0/src/utilities/getIntrospectionQuery.ts#L75 pub fn introspect( root_node: &RootNode, context: &QueryT::Context, diff --git a/juniper/src/macros/reflect.rs b/juniper/src/macros/reflect.rs index c9e86fec2..45bebb682 100644 --- a/juniper/src/macros/reflect.rs +++ b/juniper/src/macros/reflect.rs @@ -329,7 +329,7 @@ pub trait FieldMeta { /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields type TypeInfo; - /// [`Types`] of [GraphQL field's][1] return type. + /// [`Type`] of [GraphQL field's][1] return type. /// /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields const TYPE: Type; @@ -457,6 +457,20 @@ pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool { } } +/// Extracts an [`Argument`] from the provided [`Arguments`] by its [`Name`]. +#[must_use] +pub const fn get_arg_by_name(args: Arguments, name: Name) -> Option { + let mut i = 0; + while i < args.len() { + let arg = args[i]; + if str_eq(arg.0, name) { + return Some(arg); + } + i += 1; + } + None +} + /// Checks whether the given `val` exists in the given `arr`. #[must_use] pub const fn str_exists_in_arr(val: &str, arr: &[&str]) -> bool { @@ -680,8 +694,9 @@ macro_rules! assert_subtype { }; } -/// Asserts validness of the [`Field`]s arguments. See [spec][1] for more -/// info. +/// Asserts validness of the [`Field`]s [`Arguments`]. +/// +/// See [spec][1] for more info. /// /// [1]: https://spec.graphql.org/October2021#sel-IAHZhCHCDEEFAAADHD8Cxob #[macro_export] @@ -863,6 +878,103 @@ macro_rules! assert_field_args { }; } +/// Statically asserts whether a [`Field`]'s [`Argument`] represents a `Null`able type, so can be +/// deprecated. +/// +/// Must be used in conjunction with the check whether the [`Argument`] has its default value set. +#[doc(hidden)] +#[macro_export] +macro_rules! assert_field_arg_deprecable { + ( + $ty: ty, + $scalar: ty, + $field_name: expr, + $arg_name: expr $(,)? + ) => { + const _: () = { + const TY_NAME: &::core::primitive::str = + <$ty as $crate::macros::reflect::BaseType<$scalar>>::NAME; + const FIELD_NAME: &::core::primitive::str = $field_name; + const ARGS: $crate::macros::reflect::Arguments = + <$ty as $crate::macros::reflect::FieldMeta< + $scalar, + { $crate::checked_hash!(FIELD_NAME, $ty, $scalar) }, + >>::ARGUMENTS; + const ARG_NAME: &::core::primitive::str = $arg_name; + const ARG: $crate::macros::reflect::Argument = + $crate::macros::reflect::get_arg_by_name(ARGS, ARG_NAME).expect( + $crate::const_concat!( + "field `", + FIELD_NAME, + "` has no argument `", + ARG_NAME, + "`", + ), + ); + if ARG.2 % 10 != 2 { + const ARG_TY: &::core::primitive::str = $crate::format_type!(ARG.1, ARG.2); + const ERROR_MSG: &::core::primitive::str = $crate::const_concat!( + "argument `", + ARG_NAME, + "` of `", + TY_NAME, + ".", + FIELD_NAME, + "` field cannot be deprecated, because its type `", + ARG_TY, + "` is neither `Null`able nor the default argument value is specified", + ); + ::core::panic!("{}", ERROR_MSG); + } + }; + }; +} + +/// Statically asserts whether an input object [`Field`] represents a `Null`able type, so can be +/// deprecated. +/// +/// Must be used in conjunction with the check whether the [`Field`] has its default value set. +#[doc(hidden)] +#[macro_export] +macro_rules! assert_input_field_deprecable { + ( + $ty: ty, + $scalar: ty, + $field_name: expr $(,)? + ) => { + const _: () = { + const TY_NAME: &::core::primitive::str = + <$ty as $crate::macros::reflect::BaseType<$scalar>>::NAME; + const FIELD_NAME: &::core::primitive::str = $field_name; + const FIELD_TY_NAME: &::core::primitive::str = + <$ty as $crate::macros::reflect::FieldMeta< + $scalar, + { $crate::checked_hash!(FIELD_NAME, $ty, $scalar) }, + >>::TYPE; + const FIELD_WRAPPED_VAL: $crate::macros::reflect::WrappedValue = + <$ty as $crate::macros::reflect::FieldMeta< + $scalar, + { $crate::checked_hash!(FIELD_NAME, $ty, $scalar) }, + >>::WRAPPED_VALUE; + + if FIELD_WRAPPED_VAL % 10 != 2 { + const FIELD_TY: &::core::primitive::str = + $crate::format_type!(FIELD_TY_NAME, FIELD_WRAPPED_VAL); + const ERROR_MSG: &::core::primitive::str = $crate::const_concat!( + "field `", + FIELD_NAME, + "` of `", + TY_NAME, + "` input object cannot be deprecated, because its type `", + FIELD_TY, + "` is neither `Null`able nor the default field value is specified", + ); + ::core::panic!("{}", ERROR_MSG); + } + }; + }; +} + /// Concatenates `const` [`str`](prim@str)s in a `const` context. #[macro_export] macro_rules! const_concat { @@ -907,11 +1019,11 @@ macro_rules! checked_hash { } else { const MSG: &str = $crate::const_concat!( $($prefix,)? - "Field `", + "field `", $field_name, "` isn't implemented on `", <$impl_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME, - "`." + "`" ); ::core::panic!("{}", MSG) } diff --git a/juniper/src/schema/meta.rs b/juniper/src/schema/meta.rs index 4bd652d8a..27f44711d 100644 --- a/juniper/src/schema/meta.rs +++ b/juniper/src/schema/meta.rs @@ -462,6 +462,8 @@ pub struct Argument { pub arg_type: Type, #[doc(hidden)] pub default_value: Option>, + #[doc(hidden)] + pub deprecation_status: DeprecationStatus, } impl Argument { @@ -472,6 +474,7 @@ impl Argument { description: None, arg_type, default_value: None, + deprecation_status: DeprecationStatus::Current, } } @@ -499,6 +502,15 @@ impl Argument { self.default_value = Some(val); self } + + /// Sets this [`Argument`] as deprecated with an optional `reason`. + /// + /// Overwrites any previously set deprecation reason. + #[must_use] + pub fn deprecated(mut self, reason: Option>) -> Self { + self.deprecation_status = DeprecationStatus::Deprecated(reason.map(Into::into)); + self + } } /// Metadata for a single value in an enum diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs index 476da4fcb..95e973356 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -194,8 +194,8 @@ where /// /// # Unsorted /// - /// The order of the generated definitions in the returned [`Document`] is NOT stable and may change without any - /// real schema changes. + /// The order of the generated definitions in the returned [`Document`] is NOT stable and may + /// change without any real schema changes. #[must_use] pub fn as_document(&self) -> Document<'_, &str> { use crate::schema::translate::{ @@ -250,12 +250,12 @@ impl SchemaType { registry.get_type::>(&()); - let skip_directive = DirectiveType::new_skip(&mut registry); let include_directive = DirectiveType::new_include(&mut registry); + let skip_directive = DirectiveType::new_skip(&mut registry); let deprecated_directive = DirectiveType::new_deprecated(&mut registry); let specified_by_directive = DirectiveType::new_specified_by(&mut registry); - directives.insert(skip_directive.name.clone(), skip_directive); directives.insert(include_directive.name.clone(), include_directive); + directives.insert(skip_directive.name.clone(), skip_directive); directives.insert(deprecated_directive.name.clone(), deprecated_directive); directives.insert(specified_by_directive.name.clone(), specified_by_directive); @@ -440,7 +440,7 @@ impl SchemaType { } } - /// A list of possible typeees for a given type. + /// A list of possible types for a given type. pub fn possible_types(&self, t: &MetaType) -> Vec<&MetaType> { match *t { MetaType::Union(UnionMeta { @@ -585,12 +585,12 @@ impl DirectiveType { } } - fn new_skip(registry: &mut Registry) -> Self + fn new_include(registry: &mut Registry) -> Self where S: ScalarValue, { Self::new( - arcstr::literal!("skip"), + arcstr::literal!("include"), &[ DirectiveLocation::Field, DirectiveLocation::FragmentSpread, @@ -601,12 +601,12 @@ impl DirectiveType { ) } - fn new_include(registry: &mut Registry) -> Self + fn new_skip(registry: &mut Registry) -> Self where S: ScalarValue, { Self::new( - arcstr::literal!("include"), + arcstr::literal!("skip"), &[ DirectiveLocation::Field, DirectiveLocation::FragmentSpread, @@ -625,9 +625,15 @@ impl DirectiveType { arcstr::literal!("deprecated"), &[ DirectiveLocation::FieldDefinition, + DirectiveLocation::ArgumentDefinition, + DirectiveLocation::InputFieldDefinition, DirectiveLocation::EnumValue, ], - &[registry.arg::(arcstr::literal!("reason"), &())], + &[registry.arg_with_default::( + arcstr::literal!("reason"), + &"No longer supported".into(), + &(), + )], false, ) } @@ -661,20 +667,36 @@ pub enum DirectiveLocation { Subscription, #[display("field")] Field, - #[display("scalar")] - Scalar, #[display("fragment definition")] FragmentDefinition, - #[display("field definition")] - FieldDefinition, - #[display("variable definition")] - VariableDefinition, #[display("fragment spread")] FragmentSpread, #[display("inline fragment")] InlineFragment, + #[display("variable definition")] + VariableDefinition, + #[display("schema")] + Schema, + #[display("scalar")] + Scalar, + #[display("object")] + Object, + #[display("field definition")] + FieldDefinition, + #[display("argument definition")] + ArgumentDefinition, + #[display("interface")] + Interface, + #[display("union")] + Union, + #[display("enum")] + Enum, #[display("enum value")] EnumValue, + #[display("input object")] + InputObject, + #[display("input field definition")] + InputFieldDefinition, } /// Sorts the provided [`TypeType`]s in the "type-then-name" manner. diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs index 732ffe982..39313a7ff 100644 --- a/juniper/src/schema/schema.rs +++ b/juniper/src/schema/schema.rs @@ -1,3 +1,7 @@ +//! GraphQL [Type System Definitions][0]. +//! +//! [0]:https://spec.graphql.org/September2025/#sec-Schema-Introspection.Schema-Introspection-Schema + use arcstr::ArcStr; use crate::{ @@ -184,6 +188,14 @@ impl SchemaType { internal, )] impl<'a, S: ScalarValue + 'a> TypeType<'a, S> { + fn kind(&self) -> TypeKind { + match self { + Self::Concrete(t) => t.type_kind(), + Self::List(..) => TypeKind::List, + Self::NonNull(..) => TypeKind::NonNull, + } + } + fn name(&self) -> Option<&ArcStr> { match self { Self::Concrete(t) => t.name(), @@ -198,6 +210,7 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> { } } + #[graphql(name = "specifiedByURL")] fn specified_by_url(&self) -> Option<&ArcStr> { match self { Self::Concrete(t) => t.specified_by_url(), @@ -205,28 +218,14 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> { } } - fn kind(&self) -> TypeKind { - match self { - Self::Concrete(t) => t.type_kind(), - Self::List(..) => TypeKind::List, - Self::NonNull(..) => TypeKind::NonNull, - } - } - - fn fields( - &self, - #[graphql(default = false)] include_deprecated: Option, - ) -> Option>> { + fn fields(&self, #[graphql(default)] include_deprecated: bool) -> Option>> { match self { Self::Concrete(t) => match t { MetaType::Interface(InterfaceMeta { fields, .. }) | MetaType::Object(ObjectMeta { fields, .. }) => Some( fields .iter() - .filter(|f| { - include_deprecated.unwrap_or_default() - || !f.deprecation_status.is_deprecated() - }) + .filter(|f| include_deprecated || !f.deprecation_status.is_deprecated()) .filter(|f| !f.name.starts_with("__")) .collect(), ), @@ -242,32 +241,6 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> { } } - fn of_type(&self) -> Option<&Self> { - match self { - Self::Concrete(..) => None, - Self::List(l, _) | Self::NonNull(l) => Some(&**l), - } - } - - fn input_fields(&self) -> Option<&[Argument]> { - match self { - Self::Concrete(t) => match t { - MetaType::InputObject(InputObjectMeta { input_fields, .. }) => { - Some(input_fields.as_slice()) - } - MetaType::Enum(..) - | MetaType::Interface(..) - | MetaType::List(..) - | MetaType::Nullable(..) - | MetaType::Object(..) - | MetaType::Placeholder(..) - | MetaType::Scalar(..) - | MetaType::Union(..) => None, - }, - Self::List(..) | Self::NonNull(..) => None, - } - } - fn interfaces<'s>(&self, context: &'s SchemaType) -> Option>> { match self { Self::Concrete(t) => match t { @@ -345,19 +318,13 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> { } } - fn enum_values( - &self, - #[graphql(default = false)] include_deprecated: Option, - ) -> Option> { + fn enum_values(&self, #[graphql(default)] include_deprecated: bool) -> Option> { match self { Self::Concrete(t) => match t { MetaType::Enum(EnumMeta { values, .. }) => Some( values .iter() - .filter(|f| { - include_deprecated.unwrap_or_default() - || !f.deprecation_status.is_deprecated() - }) + .filter(|f| include_deprecated || !f.deprecation_status.is_deprecated()) .collect(), ), MetaType::InputObject(..) @@ -372,6 +339,56 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> { Self::List(..) | Self::NonNull(..) => None, } } + + fn input_fields( + &self, + #[graphql(default)] include_deprecated: bool, + ) -> Option>> { + match self { + Self::Concrete(t) => match t { + MetaType::InputObject(InputObjectMeta { input_fields, .. }) => Some( + input_fields + .iter() + .filter(|f| include_deprecated || !f.deprecation_status.is_deprecated()) + .collect(), + ), + MetaType::Enum(..) + | MetaType::Interface(..) + | MetaType::List(..) + | MetaType::Nullable(..) + | MetaType::Object(..) + | MetaType::Placeholder(..) + | MetaType::Scalar(..) + | MetaType::Union(..) => None, + }, + Self::List(..) | Self::NonNull(..) => None, + } + } + + fn of_type(&self) -> Option<&Self> { + match self { + Self::Concrete(..) => None, + Self::List(l, _) | Self::NonNull(l) => Some(&**l), + } + } + + fn is_one_of(&self) -> Option { + match self { + Self::Concrete(t) => match t { + // TODO: Implement once `@oneOf` is implemented for input objects. + MetaType::InputObject(InputObjectMeta { .. }) => Some(false), + MetaType::Enum(..) + | MetaType::Interface(..) + | MetaType::List(..) + | MetaType::Nullable(..) + | MetaType::Object(..) + | MetaType::Placeholder(..) + | MetaType::Scalar(..) + | MetaType::Union(..) => None, + }, + Self::List(..) | Self::NonNull(..) => None, + } + } } #[graphql_object] @@ -391,10 +408,12 @@ impl Field { self.description.as_ref() } - fn args(&self) -> Vec<&Argument> { - self.arguments - .as_ref() - .map_or_else(Vec::new, |v| v.iter().collect()) + fn args(&self, #[graphql(default)] include_deprecated: bool) -> Vec<&Argument> { + self.arguments.as_ref().map_or_else(Vec::new, |args| { + args.iter() + .filter(|a| include_deprecated || !a.deprecation_status.is_deprecated()) + .collect() + }) } #[graphql(name = "type")] @@ -437,6 +456,14 @@ impl Argument { fn default_value_(&self) -> Option { self.default_value.as_ref().map(ToString::to_string) } + + fn is_deprecated(&self) -> bool { + self.deprecation_status.is_deprecated() + } + + fn deprecation_reason(&self) -> Option<&ArcStr> { + self.deprecation_status.reason() + } } #[graphql_object] @@ -477,26 +504,29 @@ impl DirectiveType { self.description.as_ref() } - fn locations(&self) -> &[DirectiveLocation] { - &self.locations - } - fn is_repeatable(&self) -> bool { self.is_repeatable } - fn args(&self) -> &[Argument] { - &self.arguments + fn locations(&self) -> &[DirectiveLocation] { + &self.locations + } + + fn args(&self, #[graphql(default)] include_deprecated: bool) -> Vec<&Argument> { + self.arguments + .iter() + .filter(|a| include_deprecated || !a.deprecation_status.is_deprecated()) + .collect() } - // Included for compatibility with the introspection query in GraphQL.js - #[graphql(deprecated = "Use the locations array instead")] + // Included for compatibility with the introspection query in GraphQL.js. + #[graphql(deprecated = "Use `__Directive.locations` instead.")] fn on_operation(&self) -> bool { self.locations.contains(&DirectiveLocation::Query) } - // Included for compatibility with the introspection query in GraphQL.js - #[graphql(deprecated = "Use the locations array instead")] + // Included for compatibility with the introspection query in GraphQL.js. + #[graphql(deprecated = "Use `__Directive.locations` instead.")] fn on_fragment(&self) -> bool { self.locations .contains(&DirectiveLocation::FragmentDefinition) @@ -504,8 +534,8 @@ impl DirectiveType { || self.locations.contains(&DirectiveLocation::FragmentSpread) } - // Included for compatibility with the introspection query in GraphQL.js - #[graphql(deprecated = "Use the locations array instead")] + // Included for compatibility with the introspection query in GraphQL.js. + #[graphql(deprecated = "Use `__Directive.locations` instead.")] fn on_field(&self) -> bool { self.locations.contains(&DirectiveLocation::Field) } diff --git a/juniper/src/schema/translate/graphql_parser.rs b/juniper/src/schema/translate/graphql_parser.rs index 693f1fa5b..c687d029a 100644 --- a/juniper/src/schema/translate/graphql_parser.rs +++ b/juniper/src/schema/translate/graphql_parser.rs @@ -1,25 +1,10 @@ use std::collections::BTreeMap; -use graphql_parser::{ - Pos, - query::{Directive as ExternalDirective, Number as ExternalNumber, Type as ExternalType}, - schema::{ - Definition, Document, EnumType as ExternalEnum, EnumValue as ExternalEnumValue, - Field as ExternalField, InputObjectType as ExternalInputObjectType, - InputValue as ExternalInputValue, InterfaceType as ExternalInterfaceType, - ObjectType as ExternalObjectType, ScalarType as ExternalScalarType, SchemaDefinition, Text, - TypeDefinition as ExternalTypeDefinition, UnionType as ExternalUnionType, - Value as ExternalValue, - }, -}; +use graphql_parser::{Pos, schema}; use crate::{ - ast::{InputValue, Type, TypeModifier}, - schema::{ - meta::{Argument, DeprecationStatus, EnumValue, Field, MetaType}, - model::SchemaType, - translate::SchemaTranslator, - }, + ast, + schema::{meta, model::SchemaType, translate::SchemaTranslator}, value::ScalarValue, }; @@ -30,26 +15,25 @@ mod for_minimal_versions_check_only { pub struct GraphQLParserTranslator; -impl<'a, S: 'a, T> From<&'a SchemaType> for Document<'a, T> +impl<'a, S: 'a, T> From<&'a SchemaType> for schema::Document<'a, T> where S: ScalarValue, - T: Text<'a> + Default, + T: schema::Text<'a> + Default, { - fn from(input: &'a SchemaType) -> Document<'a, T> { + fn from(input: &'a SchemaType) -> schema::Document<'a, T> { GraphQLParserTranslator::translate_schema(input) } } -impl<'a, T> SchemaTranslator<'a, graphql_parser::schema::Document<'a, T>> - for GraphQLParserTranslator +impl<'a, T> SchemaTranslator<'a, schema::Document<'a, T>> for GraphQLParserTranslator where - T: Text<'a> + Default, + T: schema::Text<'a> + Default, { - fn translate_schema(input: &'a SchemaType) -> graphql_parser::schema::Document<'a, T> + fn translate_schema(input: &'a SchemaType) -> schema::Document<'a, T> where S: ScalarValue + 'a, { - let mut doc = Document::default(); + let mut doc = schema::Document::default(); // Translate type defs. let mut types = input @@ -57,263 +41,303 @@ where .iter() .filter(|(_, meta)| !meta.is_builtin()) .map(|(_, meta)| GraphQLParserTranslator::translate_meta(meta)) - .map(Definition::TypeDefinition) + .map(schema::Definition::TypeDefinition) .collect(); doc.definitions.append(&mut types); - doc.definitions - .push(Definition::SchemaDefinition(SchemaDefinition { + doc.definitions.push(schema::Definition::SchemaDefinition( + schema::SchemaDefinition { position: Pos::default(), directives: vec![], - query: Some(From::from(input.query_type_name.as_str())), - mutation: input - .mutation_type_name - .as_ref() - .map(|s| From::from(s.as_str())), + query: Some(input.query_type_name.as_str().into()), + mutation: input.mutation_type_name.as_ref().map(|s| s.as_str().into()), subscription: input .subscription_type_name .as_ref() - .map(|s| From::from(s.as_str())), - })); + .map(|s| s.as_str().into()), + }, + )); doc } } impl GraphQLParserTranslator { - fn translate_argument<'a, S, T>(input: &'a Argument) -> ExternalInputValue<'a, T> + fn translate_argument<'a, S, T>(input: &'a meta::Argument) -> schema::InputValue<'a, T> where S: ScalarValue, - T: Text<'a>, + T: schema::Text<'a>, { - ExternalInputValue { + let meta::Argument { + name, + description, + arg_type, + default_value, + deprecation_status, + } = input; + schema::InputValue { position: Pos::default(), - description: input.description.as_deref().map(Into::into), - name: From::from(input.name.as_str()), - value_type: GraphQLParserTranslator::translate_type(&input.arg_type), - default_value: input - .default_value + description: description.as_deref().map(Into::into), + name: name.as_str().into(), + value_type: GraphQLParserTranslator::translate_type(arg_type), + default_value: default_value .as_ref() .map(|x| GraphQLParserTranslator::translate_value(x)), - directives: vec![], + directives: generate_directives(deprecation_status), } } - fn translate_value<'a, S, T>(input: &'a InputValue) -> ExternalValue<'a, T> + fn translate_value<'a, S, T>(input: &'a ast::InputValue) -> schema::Value<'a, T> where S: ScalarValue + 'a, - T: Text<'a>, + T: schema::Text<'a>, { match input { - InputValue::Null => ExternalValue::Null, - InputValue::Scalar(x) => { + ast::InputValue::Null => schema::Value::Null, + ast::InputValue::Scalar(x) => { if let Some(v) = x.try_to_string() { - ExternalValue::String(v) + schema::Value::String(v) } else if let Some(v) = x.try_to_int() { - ExternalValue::Int(ExternalNumber::from(v)) + schema::Value::Int(v.into()) } else if let Some(v) = x.try_to_float() { - ExternalValue::Float(v) + schema::Value::Float(v) } else if let Some(v) = x.try_to_bool() { - ExternalValue::Boolean(v) + schema::Value::Boolean(v) } else { panic!("unknown argument type") } } - InputValue::Enum(x) => ExternalValue::Enum(From::from(x.as_str())), - InputValue::Variable(x) => ExternalValue::Variable(From::from(x.as_str())), - InputValue::List(x) => ExternalValue::List( + ast::InputValue::Enum(x) => schema::Value::Enum(x.as_str().into()), + ast::InputValue::Variable(x) => schema::Value::Variable(x.as_str().into()), + ast::InputValue::List(x) => schema::Value::List( x.iter() .map(|s| GraphQLParserTranslator::translate_value(&s.item)) .collect(), ), - InputValue::Object(x) => { + ast::InputValue::Object(x) => { let mut fields = BTreeMap::new(); x.iter().for_each(|(name_span, value_span)| { fields.insert( - From::from(name_span.item.as_str()), + name_span.item.as_str().into(), GraphQLParserTranslator::translate_value(&value_span.item), ); }); - ExternalValue::Object(fields) + schema::Value::Object(fields) } } } - fn translate_type<'a, T>(input: &'a Type) -> ExternalType<'a, T> + fn translate_type<'a, T>(input: &'a ast::Type) -> schema::Type<'a, T> where - T: Text<'a>, + T: schema::Text<'a>, { - let mut ty = ExternalType::NamedType(input.innermost_name().into()); + let mut ty = schema::Type::NamedType(input.innermost_name().into()); for m in input.modifiers() { ty = match m { - TypeModifier::NonNull => ExternalType::NonNullType(ty.into()), - TypeModifier::List(..) => ExternalType::ListType(ty.into()), + ast::TypeModifier::NonNull => schema::Type::NonNullType(ty.into()), + ast::TypeModifier::List(..) => schema::Type::ListType(ty.into()), }; } ty } - fn translate_meta<'a, S, T>(input: &'a MetaType) -> ExternalTypeDefinition<'a, T> + fn translate_meta<'a, S, T>(input: &'a meta::MetaType) -> schema::TypeDefinition<'a, T> where S: ScalarValue, - T: Text<'a>, + T: schema::Text<'a>, { match input { - MetaType::Scalar(x) => ExternalTypeDefinition::Scalar(ExternalScalarType { + meta::MetaType::Scalar(meta::ScalarMeta { + name, + description, + specified_by_url, + try_parse_fn: _, + parse_fn: _, + }) => schema::TypeDefinition::Scalar(schema::ScalarType { position: Pos::default(), - description: x.description.as_deref().map(Into::into), - name: From::from(x.name.as_ref()), - directives: vec![], + description: description.as_deref().map(Into::into), + name: name.as_str().into(), + directives: specified_by_url + .as_deref() + .map(|url| vec![specified_by_url_to_directive(url)]) + .unwrap_or_default(), }), - MetaType::Enum(x) => ExternalTypeDefinition::Enum(ExternalEnum { + meta::MetaType::Enum(meta::EnumMeta { + name, + description, + values, + try_parse_fn: _, + }) => schema::TypeDefinition::Enum(schema::EnumType { position: Pos::default(), - description: x.description.as_deref().map(Into::into), - name: From::from(x.name.as_ref()), + description: description.as_deref().map(Into::into), + name: name.as_str().into(), directives: vec![], - values: x - .values + values: values .iter() .map(GraphQLParserTranslator::translate_enum_value) .collect(), }), - MetaType::Union(x) => ExternalTypeDefinition::Union(ExternalUnionType { + meta::MetaType::Union(meta::UnionMeta { + name, + description, + of_type_names, + }) => schema::TypeDefinition::Union(schema::UnionType { position: Pos::default(), - description: x.description.as_deref().map(Into::into), - name: From::from(x.name.as_ref()), + description: description.as_deref().map(Into::into), + name: name.as_str().into(), directives: vec![], - types: x - .of_type_names - .iter() - .map(|s| From::from(s.as_str())) - .collect(), + types: of_type_names.iter().map(|s| s.as_str().into()).collect(), }), - MetaType::Interface(x) => ExternalTypeDefinition::Interface(ExternalInterfaceType { + meta::MetaType::Interface(meta::InterfaceMeta { + name, + description, + fields, + interface_names, + }) => schema::TypeDefinition::Interface(schema::InterfaceType { position: Pos::default(), - description: x.description.as_deref().map(Into::into), - name: From::from(x.name.as_ref()), - implements_interfaces: x - .interface_names - .iter() - .map(|s| From::from(s.as_str())) - .collect(), + description: description.as_deref().map(Into::into), + name: name.as_str().into(), + implements_interfaces: interface_names.iter().map(|s| s.as_str().into()).collect(), directives: vec![], - fields: x - .fields + fields: fields .iter() .filter(|x| !x.is_builtin()) .map(GraphQLParserTranslator::translate_field) .collect(), }), - MetaType::InputObject(x) => { - ExternalTypeDefinition::InputObject(ExternalInputObjectType { - position: Pos::default(), - description: x.description.as_deref().map(Into::into), - name: From::from(x.name.as_ref()), - directives: vec![], - fields: x - .input_fields - .iter() - .filter(|x| !x.is_builtin()) - .map(GraphQLParserTranslator::translate_argument) - .collect(), - }) - } - MetaType::Object(x) => ExternalTypeDefinition::Object(ExternalObjectType { + meta::MetaType::InputObject(meta::InputObjectMeta { + name, + description, + input_fields, + try_parse_fn: _, + }) => schema::TypeDefinition::InputObject(schema::InputObjectType { position: Pos::default(), - description: x.description.as_deref().map(Into::into), - name: From::from(x.name.as_ref()), + description: description.as_deref().map(Into::into), + name: name.as_str().into(), directives: vec![], - fields: x - .fields + fields: input_fields .iter() .filter(|x| !x.is_builtin()) - .map(GraphQLParserTranslator::translate_field) + .map(GraphQLParserTranslator::translate_argument) .collect(), - implements_interfaces: x - .interface_names + }), + meta::MetaType::Object(meta::ObjectMeta { + name, + description, + fields, + interface_names, + }) => schema::TypeDefinition::Object(schema::ObjectType { + position: Pos::default(), + description: description.as_deref().map(Into::into), + name: name.as_str().into(), + directives: vec![], + fields: fields .iter() - .map(|s| From::from(s.as_str())) + .filter(|x| !x.is_builtin()) + .map(GraphQLParserTranslator::translate_field) .collect(), + implements_interfaces: interface_names.iter().map(|s| s.as_str().into()).collect(), }), - _ => panic!("unknown meta type when translating"), + _ => panic!("unknown `MetaType` when translating"), } } - fn translate_enum_value<'a, T>(input: &'a EnumValue) -> ExternalEnumValue<'a, T> + fn translate_enum_value<'a, T>(input: &'a meta::EnumValue) -> schema::EnumValue<'a, T> where - T: Text<'a>, + T: schema::Text<'a>, { - ExternalEnumValue { + let meta::EnumValue { + name, + description, + deprecation_status, + } = input; + schema::EnumValue { position: Pos::default(), - name: From::from(input.name.as_ref()), - description: input.description.as_deref().map(Into::into), - directives: generate_directives(&input.deprecation_status), + name: name.as_str().into(), + description: description.as_deref().map(Into::into), + directives: generate_directives(deprecation_status), } } - fn translate_field<'a, S, T>(input: &'a Field) -> ExternalField<'a, T> + fn translate_field<'a, S, T>(input: &'a meta::Field) -> schema::Field<'a, T> where S: ScalarValue + 'a, - T: Text<'a>, + T: schema::Text<'a>, { - let arguments = input - .arguments - .as_ref() - .map(|a| { - a.iter() - .filter(|x| !x.is_builtin()) - .map(|x| GraphQLParserTranslator::translate_argument(x)) - .collect() - }) - .unwrap_or_default(); - - ExternalField { - position: Pos::default(), - name: From::from(input.name.as_str()), - description: input.description.as_deref().map(Into::into), - directives: generate_directives(&input.deprecation_status), - field_type: GraphQLParserTranslator::translate_type(&input.field_type), + let meta::Field { + name, + description, arguments, + field_type, + deprecation_status, + } = input; + schema::Field { + position: Pos::default(), + name: name.as_str().into(), + description: description.as_deref().map(Into::into), + directives: generate_directives(deprecation_status), + field_type: GraphQLParserTranslator::translate_type(field_type), + arguments: arguments + .as_ref() + .map(|a| { + a.iter() + .filter(|x| !x.is_builtin()) + .map(|x| GraphQLParserTranslator::translate_argument(x)) + .collect() + }) + .unwrap_or_default(), } } } -fn deprecation_to_directive<'a, T>(status: &DeprecationStatus) -> Option> +fn deprecation_to_directive<'a, T>( + status: &meta::DeprecationStatus, +) -> Option> where - T: Text<'a>, + T: schema::Text<'a>, { match status { - DeprecationStatus::Current => None, - DeprecationStatus::Deprecated(reason) => Some(ExternalDirective { + meta::DeprecationStatus::Current => None, + meta::DeprecationStatus::Deprecated(reason) => Some(schema::Directive { position: Pos::default(), name: "deprecated".into(), arguments: reason .as_ref() - .map(|rsn| { - vec![( - From::from("reason"), - ExternalValue::String(rsn.as_str().into()), - )] - }) + .map(|rsn| vec![("reason".into(), schema::Value::String(rsn.as_str().into()))]) .unwrap_or_default(), }), } } +/// Returns the `@specifiedBy(url:)` [`schema::Directive`] for the provided `url`. +fn specified_by_url_to_directive<'a, T>(url: &str) -> schema::Directive<'a, T> +where + T: schema::Text<'a>, +{ + schema::Directive { + position: Pos::default(), + name: "specifiedBy".into(), + arguments: vec![("url".into(), schema::Value::String(url.into()))], + } +} + // Right now the only directive supported is `@deprecated`. // `@skip` and `@include` are dealt with elsewhere. // https://spec.graphql.org/October2021#sec-Type-System.Directives.Built-in-Directives -fn generate_directives<'a, T>(status: &DeprecationStatus) -> Vec> +fn generate_directives<'a, T>(status: &meta::DeprecationStatus) -> Vec> where - T: Text<'a>, + T: schema::Text<'a>, { deprecation_to_directive(status) .map(|d| vec![d]) .unwrap_or_default() } -/// Sorts the provided [`Document`] in the "type-then-name" manner. -pub(crate) fn sort_schema_document<'a, T: Text<'a>>(document: &mut Document<'a, T>) { +/// Sorts the provided [`schema::Document`] in the "type-then-name" manner. +pub(crate) fn sort_schema_document<'a, T>(document: &mut schema::Document<'a, T>) +where + T: schema::Text<'a>, +{ document.definitions.sort_by(move |a, b| { let type_cmp = sort_value::by_type(a).cmp(&sort_value::by_type(b)); let name_cmp = sort_value::by_is_directive(a) @@ -324,14 +348,14 @@ pub(crate) fn sort_schema_document<'a, T: Text<'a>>(document: &mut Document<'a, }) } -/// Evaluation of a [`Definition`] weights for sorting. +/// Evaluation of a [`schema::Definition`] weights for sorting. mod sort_value { - use graphql_parser::schema::{Definition, Text, TypeDefinition, TypeExtension}; + use graphql_parser::schema::{self, Definition, TypeDefinition, TypeExtension}; /// Returns a [`Definition`] sorting weight by its type. pub(super) fn by_type<'a, T>(definition: &Definition<'a, T>) -> u8 where - T: Text<'a>, + T: schema::Text<'a>, { match definition { Definition::SchemaDefinition(_) => 0, @@ -358,7 +382,7 @@ mod sort_value { /// Returns a [`Definition`] sorting weight by its name. pub(super) fn by_name<'b, 'a, T>(definition: &'b Definition<'a, T>) -> Option<&'b T::Value> where - T: Text<'a>, + T: schema::Text<'a>, { match definition { Definition::SchemaDefinition(_) => None, @@ -385,7 +409,7 @@ mod sort_value { /// Returns a [`Definition`] sorting weight by its directive. pub(super) fn by_directive<'b, 'a, T>(definition: &'b Definition<'a, T>) -> Option<&'b T::Value> where - T: Text<'a>, + T: schema::Text<'a>, { match definition { Definition::SchemaDefinition(_) => None, @@ -412,7 +436,7 @@ mod sort_value { /// Returns a [`Definition`] sorting weight by whether it represents a directive. pub(super) fn by_is_directive<'a, T>(definition: &Definition<'a, T>) -> u8 where - T: Text<'a>, + T: schema::Text<'a>, { match definition { Definition::SchemaDefinition(_) => 0, diff --git a/juniper/src/tests/introspection_tests.rs b/juniper/src/tests/introspection_tests.rs index 210c3bab0..3d23a1eec 100644 --- a/juniper/src/tests/introspection_tests.rs +++ b/juniper/src/tests/introspection_tests.rs @@ -196,6 +196,8 @@ async fn test_introspection_directives() { "name": "deprecated", "locations": [ "FIELD_DEFINITION", + "ARGUMENT_DEFINITION", + "INPUT_FIELD_DEFINITION", "ENUM_VALUE", ], }, @@ -289,7 +291,10 @@ async fn test_builtin_introspection_query() { let result = crate::introspect(&schema, &database, IntrospectionFormat::default()).unwrap(); let expected = schema_introspection_result(); - assert_eq!(result, (expected, vec![])); + assert_eq!( + serde_json::to_string_pretty(&result.0).unwrap(), + serde_json::to_string_pretty(&expected).unwrap(), + ); } #[tokio::test] @@ -305,5 +310,8 @@ async fn test_builtin_introspection_query_without_descriptions() { crate::introspect(&schema, &database, IntrospectionFormat::WithoutDescriptions).unwrap(); let expected = schema_introspection_result_without_descriptions(); - assert_eq!(result, (expected, vec![])); + assert_eq!( + serde_json::to_string_pretty(&result.0).unwrap(), + serde_json::to_string_pretty(&expected).unwrap(), + ); } diff --git a/juniper/src/tests/schema_introspection.rs b/juniper/src/tests/schema_introspection.rs index 301a0af97..e8dc19edc 100644 --- a/juniper/src/tests/schema_introspection.rs +++ b/juniper/src/tests/schema_introspection.rs @@ -4,9 +4,7 @@ pub(crate) fn schema_introspection_result() -> Value { graphql_value!({ "__schema": { "description": null, - "queryType": { - "name": "Query" - }, + "queryType": {"name": "Query", "kind": "OBJECT"}, "mutationType": null, "subscriptionType": null, "types": [ @@ -14,7 +12,8 @@ pub(crate) fn schema_introspection_result() -> Value { "kind": "ENUM", "name": "Episode", "description": null, - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": null, "inputFields": null, "interfaces": null, @@ -44,7 +43,8 @@ pub(crate) fn schema_introspection_result() -> Value { "kind": "ENUM", "name": "__DirectiveLocation", "description": null, - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": null, "inputFields": null, "interfaces": null, @@ -73,6 +73,36 @@ pub(crate) fn schema_introspection_result() -> Value { "isDeprecated": false, "deprecationReason": null }, + { + "name": "FRAGMENT_DEFINITION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_SPREAD", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INLINE_FRAGMENT", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "VARIABLE_DEFINITION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCHEMA", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "SCALAR", "description": null, @@ -80,7 +110,7 @@ pub(crate) fn schema_introspection_result() -> Value { "deprecationReason": null }, { - "name": "FRAGMENT_DEFINITION", + "name": "OBJECT", "description": null, "isDeprecated": false, "deprecationReason": null @@ -92,19 +122,25 @@ pub(crate) fn schema_introspection_result() -> Value { "deprecationReason": null }, { - "name": "VARIABLE_DEFINITION", + "name": "ARGUMENT_DEFINITION", "description": null, "isDeprecated": false, "deprecationReason": null }, { - "name": "FRAGMENT_SPREAD", + "name": "INTERFACE", "description": null, "isDeprecated": false, "deprecationReason": null }, { - "name": "INLINE_FRAGMENT", + "name": "UNION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", "description": null, "isDeprecated": false, "deprecationReason": null @@ -114,6 +150,18 @@ pub(crate) fn schema_introspection_result() -> Value { "description": null, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_FIELD_DEFINITION", + "description": null, + "isDeprecated": false, + "deprecationReason": null } ], "possibleTypes": null @@ -122,7 +170,8 @@ pub(crate) fn schema_introspection_result() -> Value { "kind": "ENUM", "name": "__TypeKind", "description": "GraphQL type kind\n\nThe GraphQL specification defines a number of type kinds - the meta type of a type.", - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": null, "inputFields": null, "interfaces": null, @@ -182,7 +231,8 @@ pub(crate) fn schema_introspection_result() -> Value { "kind": "INTERFACE", "name": "Character", "description": "A character in the Star Wars Trilogy", - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": [ { "name": "id", @@ -281,7 +331,8 @@ pub(crate) fn schema_introspection_result() -> Value { "kind": "SCALAR", "name": "Boolean", "description": null, - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": null, "inputFields": null, "interfaces": null, @@ -292,7 +343,8 @@ pub(crate) fn schema_introspection_result() -> Value { "kind": "SCALAR", "name": "String", "description": null, - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": null, "inputFields": null, "interfaces": null, @@ -303,7 +355,8 @@ pub(crate) fn schema_introspection_result() -> Value { "kind": "OBJECT", "name": "Droid", "description": "A mechanical creature in the Star Wars universe.", - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": [ { "name": "id", @@ -409,7 +462,8 @@ pub(crate) fn schema_introspection_result() -> Value { "kind": "OBJECT", "name": "Human", "description": "A humanoid creature in the Star Wars universe.", - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": [ { "name": "id", @@ -515,7 +569,8 @@ pub(crate) fn schema_introspection_result() -> Value { "kind": "OBJECT", "name": "Query", "description": "The root query object of the schema", - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": [ { "name": "human", @@ -533,7 +588,9 @@ pub(crate) fn schema_introspection_result() -> Value { "ofType": null } }, - "defaultValue": null + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null } ], "type": { @@ -561,7 +618,9 @@ pub(crate) fn schema_introspection_result() -> Value { "ofType": null } }, - "defaultValue": null + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null } ], "type": { @@ -584,7 +643,9 @@ pub(crate) fn schema_introspection_result() -> Value { "name": "Episode", "ofType": null }, - "defaultValue": null + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null } ], "type": { @@ -605,7 +666,8 @@ pub(crate) fn schema_introspection_result() -> Value { "kind": "OBJECT", "name": "__Directive", "description": null, - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": [ { "name": "name", @@ -636,40 +698,40 @@ pub(crate) fn schema_introspection_result() -> Value { "deprecationReason": null }, { - "name": "locations", + "name": "isRepeatable", "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "ENUM", - "name": "__DirectiveLocation", - "ofType": null - } - } + "kind": "SCALAR", + "name": "Boolean", + "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "isRepeatable", + "name": "locations", "description": null, "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__DirectiveLocation", + "ofType": null + } + } } }, "isDeprecated": false, @@ -678,7 +740,24 @@ pub(crate) fn schema_introspection_result() -> Value { { "name": "args", "description": null, - "args": [], + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": "false", + "isDeprecated": false, + "deprecationReason": null + } + ], "type": { "kind": "NON_NULL", "name": null, @@ -713,7 +792,7 @@ pub(crate) fn schema_introspection_result() -> Value { } }, "isDeprecated": true, - "deprecationReason": "Use the locations array instead" + "deprecationReason": "Use `__Directive.locations` instead." }, { "name": "onFragment", @@ -729,7 +808,7 @@ pub(crate) fn schema_introspection_result() -> Value { } }, "isDeprecated": true, - "deprecationReason": "Use the locations array instead" + "deprecationReason": "Use `__Directive.locations` instead." }, { "name": "onField", @@ -745,7 +824,7 @@ pub(crate) fn schema_introspection_result() -> Value { } }, "isDeprecated": true, - "deprecationReason": "Use the locations array instead" + "deprecationReason": "Use `__Directive.locations` instead." } ], "inputFields": null, @@ -757,7 +836,8 @@ pub(crate) fn schema_introspection_result() -> Value { "kind": "OBJECT", "name": "__EnumValue", "description": null, - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": [ { "name": "name", @@ -825,7 +905,8 @@ pub(crate) fn schema_introspection_result() -> Value { "kind": "OBJECT", "name": "__Field", "description": null, - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": [ { "name": "name", @@ -858,7 +939,24 @@ pub(crate) fn schema_introspection_result() -> Value { { "name": "args", "description": null, - "args": [], + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": "false", + "isDeprecated": false, + "deprecationReason": null + } + ], "type": { "kind": "NON_NULL", "name": null, @@ -933,7 +1031,8 @@ pub(crate) fn schema_introspection_result() -> Value { "kind": "OBJECT", "name": "__InputValue", "description": null, - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": [ { "name": "name", @@ -990,6 +1089,34 @@ pub(crate) fn schema_introspection_result() -> Value { }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -1001,7 +1128,8 @@ pub(crate) fn schema_introspection_result() -> Value { "kind": "OBJECT", "name": "__Schema", "description": null, - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": [ { "name": "description", @@ -1113,22 +1241,27 @@ pub(crate) fn schema_introspection_result() -> Value { "kind": "OBJECT", "name": "__Type", "description": null, - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": [ { - "name": "name", + "name": "kind", "description": null, "args": [], "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__TypeKind", + "ofType": null + } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "description", + "name": "name", "description": null, "args": [], "type": { @@ -1140,7 +1273,7 @@ pub(crate) fn schema_introspection_result() -> Value { "deprecationReason": null }, { - "name": "specifiedByUrl", + "name": "description", "description": null, "args": [], "type": { @@ -1152,17 +1285,13 @@ pub(crate) fn schema_introspection_result() -> Value { "deprecationReason": null }, { - "name": "kind", + "name": "specifiedByURL", "description": null, "args": [], "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "ENUM", - "name": "__TypeKind", - "ofType": null - } + "kind": "SCALAR", + "name": "String", + "ofType": null }, "isDeprecated": false, "deprecationReason": null @@ -1175,11 +1304,17 @@ pub(crate) fn schema_introspection_result() -> Value { "name": "includeDeprecated", "description": null, "type": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } }, - "defaultValue": "false" + "defaultValue": "false", + "isDeprecated": false, + "deprecationReason": null } ], "type": { @@ -1199,19 +1334,7 @@ pub(crate) fn schema_introspection_result() -> Value { "deprecationReason": null }, { - "name": "ofType", - "description": null, - "args": [], - "type": { - "kind": "OBJECT", - "name": "__Type", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "inputFields", + "name": "interfaces", "description": null, "args": [], "type": { @@ -1222,7 +1345,7 @@ pub(crate) fn schema_introspection_result() -> Value { "name": null, "ofType": { "kind": "OBJECT", - "name": "__InputValue", + "name": "__Type", "ofType": null } } @@ -1231,7 +1354,7 @@ pub(crate) fn schema_introspection_result() -> Value { "deprecationReason": null }, { - "name": "interfaces", + "name": "possibleTypes", "description": null, "args": [], "type": { @@ -1251,9 +1374,26 @@ pub(crate) fn schema_introspection_result() -> Value { "deprecationReason": null }, { - "name": "possibleTypes", + "name": "enumValues", "description": null, - "args": [], + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": "false", + "isDeprecated": false, + "deprecationReason": null + } + ], "type": { "kind": "LIST", "name": null, @@ -1262,7 +1402,7 @@ pub(crate) fn schema_introspection_result() -> Value { "name": null, "ofType": { "kind": "OBJECT", - "name": "__Type", + "name": "__EnumValue", "ofType": null } } @@ -1271,18 +1411,24 @@ pub(crate) fn schema_introspection_result() -> Value { "deprecationReason": null }, { - "name": "enumValues", + "name": "inputFields", "description": null, "args": [ { "name": "includeDeprecated", "description": null, "type": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } }, - "defaultValue": "false" + "defaultValue": "false", + "isDeprecated": false, + "deprecationReason": null } ], "type": { @@ -1293,13 +1439,37 @@ pub(crate) fn schema_introspection_result() -> Value { "name": null, "ofType": { "kind": "OBJECT", - "name": "__EnumValue", + "name": "__InputValue", "ofType": null } } }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "ofType", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isOneOf", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -1315,6 +1485,8 @@ pub(crate) fn schema_introspection_result() -> Value { "isRepeatable": false, "locations": [ "FIELD_DEFINITION", + "ARGUMENT_DEFINITION", + "INPUT_FIELD_DEFINITION", "ENUM_VALUE" ], "args": [ @@ -1330,7 +1502,9 @@ pub(crate) fn schema_introspection_result() -> Value { "ofType": null } }, - "defaultValue": null + "defaultValue": "\"No longer supported\"", + "isDeprecated": false, + "deprecationReason": null } ] }, @@ -1356,7 +1530,9 @@ pub(crate) fn schema_introspection_result() -> Value { "ofType": null } }, - "defaultValue": null + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null } ] }, @@ -1382,7 +1558,9 @@ pub(crate) fn schema_introspection_result() -> Value { "ofType": null } }, - "defaultValue": null + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null } ] }, @@ -1406,7 +1584,9 @@ pub(crate) fn schema_introspection_result() -> Value { "ofType": null } }, - "defaultValue": null + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null } ] } @@ -1418,16 +1598,15 @@ pub(crate) fn schema_introspection_result() -> Value { pub(crate) fn schema_introspection_result_without_descriptions() -> Value { graphql_value!({ "__schema": { - "queryType": { - "name": "Query" - }, + "queryType": {"name": "Query", "kind": "OBJECT"}, "mutationType": null, "subscriptionType": null, "types": [ { "kind": "ENUM", "name": "Episode", - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": null, "inputFields": null, "interfaces": null, @@ -1453,7 +1632,8 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { { "kind": "ENUM", "name": "__DirectiveLocation", - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": null, "inputFields": null, "interfaces": null, @@ -1478,13 +1658,38 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { "isDeprecated": false, "deprecationReason": null }, + { + "name": "FRAGMENT_DEFINITION", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_SPREAD", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INLINE_FRAGMENT", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "VARIABLE_DEFINITION", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCHEMA", + "isDeprecated": false, + "deprecationReason": null + }, { "name": "SCALAR", "isDeprecated": false, "deprecationReason": null }, { - "name": "FRAGMENT_DEFINITION", + "name": "OBJECT", "isDeprecated": false, "deprecationReason": null }, @@ -1494,17 +1699,22 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { "deprecationReason": null }, { - "name": "VARIABLE_DEFINITION", + "name": "ARGUMENT_DEFINITION", "isDeprecated": false, "deprecationReason": null }, { - "name": "FRAGMENT_SPREAD", + "name": "INTERFACE", "isDeprecated": false, "deprecationReason": null }, { - "name": "INLINE_FRAGMENT", + "name": "UNION", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", "isDeprecated": false, "deprecationReason": null }, @@ -1512,6 +1722,16 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { "name": "ENUM_VALUE", "isDeprecated": false, "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_FIELD_DEFINITION", + "isDeprecated": false, + "deprecationReason": null } ], "possibleTypes": null @@ -1519,7 +1739,8 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { { "kind": "ENUM", "name": "__TypeKind", - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": null, "inputFields": null, "interfaces": null, @@ -1570,7 +1791,8 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { { "kind": "INTERFACE", "name": "Character", - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": [ { "name": "id", @@ -1664,7 +1886,8 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { { "kind": "SCALAR", "name": "Boolean", - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": null, "inputFields": null, "interfaces": null, @@ -1674,7 +1897,8 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { { "kind": "SCALAR", "name": "String", - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": null, "inputFields": null, "interfaces": null, @@ -1684,7 +1908,8 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { { "kind": "OBJECT", "name": "Droid", - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": [ { "name": "id", @@ -1784,7 +2009,8 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { { "kind": "OBJECT", "name": "Human", - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": [ { "name": "id", @@ -1884,7 +2110,8 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { { "kind": "OBJECT", "name": "Query", - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": [ { "name": "human", @@ -1900,7 +2127,9 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { "ofType": null } }, - "defaultValue": null + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null } ], "type": { @@ -1925,7 +2154,9 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { "ofType": null } }, - "defaultValue": null + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null } ], "type": { @@ -1946,7 +2177,9 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { "name": "Episode", "ofType": null }, - "defaultValue": null + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null } ], "type": { @@ -1966,7 +2199,8 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { { "kind": "OBJECT", "name": "__Directive", - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": [ { "name": "name", @@ -1994,6 +2228,21 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { "isDeprecated": false, "deprecationReason": null }, + { + "name": "isRepeatable", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "locations", "args": [], @@ -2017,24 +2266,25 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { "isDeprecated": false, "deprecationReason": null }, - { - "name": "isRepeatable", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "args", - "args": [], + "args": [ + { + "name": "includeDeprecated", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": "false", + "isDeprecated": false, + "deprecationReason": null + } + ], "type": { "kind": "NON_NULL", "name": null, @@ -2068,7 +2318,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { } }, "isDeprecated": true, - "deprecationReason": "Use the locations array instead" + "deprecationReason": "Use `__Directive.locations` instead." }, { "name": "onFragment", @@ -2083,7 +2333,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { } }, "isDeprecated": true, - "deprecationReason": "Use the locations array instead" + "deprecationReason": "Use `__Directive.locations` instead." }, { "name": "onField", @@ -2098,7 +2348,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { } }, "isDeprecated": true, - "deprecationReason": "Use the locations array instead" + "deprecationReason": "Use `__Directive.locations` instead." }, ], "inputFields": null, @@ -2109,7 +2359,8 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { { "kind": "OBJECT", "name": "__EnumValue", - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": [ { "name": "name", @@ -2172,7 +2423,8 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { { "kind": "OBJECT", "name": "__Field", - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": [ { "name": "name", @@ -2202,7 +2454,23 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { }, { "name": "args", - "args": [], + "args": [ + { + "name": "includeDeprecated", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": "false", + "isDeprecated": false, + "deprecationReason": null + } + ], "type": { "kind": "NON_NULL", "name": null, @@ -2273,7 +2541,8 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { { "kind": "OBJECT", "name": "__InputValue", - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": [ { "name": "name", @@ -2326,6 +2595,32 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "isDeprecated", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -2336,7 +2631,8 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { { "kind": "OBJECT", "name": "__Schema", - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": [ { "name": "description", @@ -2441,21 +2737,26 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { { "kind": "OBJECT", "name": "__Type", - "specifiedByUrl": null, + "specifiedByURL": null, + "isOneOf": null, "fields": [ { - "name": "name", + "name": "kind", "args": [], "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "__TypeKind", + "ofType": null + } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "description", + "name": "name", "args": [], "type": { "kind": "SCALAR", @@ -2466,7 +2767,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { "deprecationReason": null }, { - "name": "specifiedByUrl", + "name": "description", "args": [], "type": { "kind": "SCALAR", @@ -2477,16 +2778,12 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { "deprecationReason": null }, { - "name": "kind", + "name": "specifiedByURL", "args": [], "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "ENUM", - "name": "__TypeKind", - "ofType": null - } + "kind": "SCALAR", + "name": "String", + "ofType": null }, "isDeprecated": false, "deprecationReason": null @@ -2497,11 +2794,17 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { { "name": "includeDeprecated", "type": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } }, - "defaultValue": "false" + "defaultValue": "false", + "isDeprecated": false, + "deprecationReason": null } ], "type": { @@ -2521,18 +2824,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { "deprecationReason": null }, { - "name": "ofType", - "args": [], - "type": { - "kind": "OBJECT", - "name": "__Type", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "inputFields", + "name": "interfaces", "args": [], "type": { "kind": "LIST", @@ -2542,7 +2834,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { "name": null, "ofType": { "kind": "OBJECT", - "name": "__InputValue", + "name": "__Type", "ofType": null } } @@ -2551,7 +2843,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { "deprecationReason": null }, { - "name": "interfaces", + "name": "possibleTypes", "args": [], "type": { "kind": "LIST", @@ -2570,8 +2862,24 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { "deprecationReason": null }, { - "name": "possibleTypes", - "args": [], + "name": "enumValues", + "args": [ + { + "name": "includeDeprecated", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": "false", + "isDeprecated": false, + "deprecationReason": null + } + ], "type": { "kind": "LIST", "name": null, @@ -2580,7 +2888,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { "name": null, "ofType": { "kind": "OBJECT", - "name": "__Type", + "name": "__EnumValue", "ofType": null } } @@ -2589,16 +2897,22 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { "deprecationReason": null }, { - "name": "enumValues", + "name": "inputFields", "args": [ { "name": "includeDeprecated", "type": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } }, - "defaultValue": "false" + "defaultValue": "false", + "isDeprecated": false, + "deprecationReason": null } ], "type": { @@ -2609,13 +2923,35 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { "name": null, "ofType": { "kind": "OBJECT", - "name": "__EnumValue", + "name": "__InputValue", "ofType": null } } }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "ofType", + "args": [], + "type": { + "kind": "OBJECT", + "name": "__Type", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isOneOf", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -2630,6 +2966,8 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { "isRepeatable": false, "locations": [ "FIELD_DEFINITION", + "ARGUMENT_DEFINITION", + "INPUT_FIELD_DEFINITION", "ENUM_VALUE" ], "args": [ @@ -2644,7 +2982,9 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { "ofType": null } }, - "defaultValue": null + "defaultValue": "\"No longer supported\"", + "isDeprecated": false, + "deprecationReason": null } ] }, @@ -2668,7 +3008,9 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { "ofType": null } }, - "defaultValue": null + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null } ] }, @@ -2692,7 +3034,9 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { "ofType": null } }, - "defaultValue": null + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null } ] }, @@ -2714,7 +3058,9 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value { "ofType": null } }, - "defaultValue": null + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null } ] } diff --git a/juniper_codegen/CHANGELOG.md b/juniper_codegen/CHANGELOG.md index cae64d3c0..e61e702d5 100644 --- a/juniper_codegen/CHANGELOG.md +++ b/juniper_codegen/CHANGELOG.md @@ -6,6 +6,24 @@ All user visible changes to `juniper_codegen` crate will be documented in this f +## main + +### Added + +- [September 2025] GraphQL spec: ([#1347]) + - Arguments and input object fields deprecation: ([#1348], [#864], [graphql/graphql-spec#525], [graphql/graphql-spec#805]) + - Placing `#[graphql(deprecated)]` and `#[deprecated]` attributes on struct fields in `#[derive(GraphQLInputObject)]` macro. + - Placing `#[graphql(deprecated)]` attribute on method arguments in `#[graphql_object]` and `#[graphql_interface]` macros. + +[#864]: /../../issues/864 +[#1347]: /../../issues/1347 +[#1348]: /../../pull/1348 +[graphql/graphql-spec#525]: https://github.com/graphql/graphql-spec/pull/525 +[graphql/graphql-spec#805]: https://github.com/graphql/graphql-spec/pull/805 + + + + ## [0.17.0] ยท 2025-09-08 [0.17.0]: /../../tree/juniper_codegen-v0.17.0/juniper_codegen @@ -110,3 +128,4 @@ All user visible changes to `juniper_codegen` crate will be documented in this f [MSRV]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field [orphan rules]: https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules [Semantic Versioning 2.0.0]: https://semver.org +[September 2025]: https://spec.graphql.org/September2025 diff --git a/juniper_codegen/src/common/deprecation.rs b/juniper_codegen/src/common/deprecation.rs index 6fb9fa539..e6f21de92 100644 --- a/juniper_codegen/src/common/deprecation.rs +++ b/juniper_codegen/src/common/deprecation.rs @@ -42,13 +42,12 @@ impl Parse for Directive { } impl Directive { - /// Tries to parse a [`Directive`] from a `#[deprecated(note = ...)]` - /// attribute, by looking up for it in the provided [`syn::Attribute`]s. + /// Tries to parse a [`Directive`] from a `#[deprecated(note = ...)]` attribute, by looking up + /// for it in the provided [`syn::Attribute`]s. /// /// # Errors /// - /// If failed to parse a [`Directive`] from a found - /// `#[deprecated(note = ...)]` attribute. + /// If failed to parse a [`Directive`] from the found `#[deprecated(note = ...)]` attribute. pub(crate) fn parse_from_deprecated_attr( attrs: &[syn::Attribute], ) -> syn::Result>> { @@ -83,7 +82,7 @@ impl Directive { return if !nv.path.is_ident("note") { Err(syn::Error::new( nv.path.span(), - "unrecognized setting on #[deprecated(..)] attribute", + "unrecognized setting on `#[deprecated(..)]` attribute", )) } else if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(strlit), diff --git a/juniper_codegen/src/common/field/arg.rs b/juniper_codegen/src/common/field/arg.rs index 733bc6ef4..570377b14 100644 --- a/juniper_codegen/src/common/field/arg.rs +++ b/juniper_codegen/src/common/field/arg.rs @@ -15,7 +15,7 @@ use syn::{ }; use crate::common::{ - Description, SpanContainer, default, diagnostic, filter_attrs, + Description, SpanContainer, default, deprecation, diagnostic, filter_attrs, parse::{ ParseBufferExt as _, TypeExt as _, attr::{OptionExt as _, err}, @@ -43,6 +43,15 @@ pub(crate) struct Attr { /// [2]: https://spec.graphql.org/October2021#sec-Descriptions pub(crate) description: Option>, + /// Explicitly specified [deprecation][2] of this [GraphQL argument][1]. + /// + /// If [`None`], then Rust `#[deprecated]` attribute will be used as the [deprecation][2], if + /// any. + /// + /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments + /// [2]: https://spec.graphql.org/October2021#sec-Deprecation + pub(crate) deprecated: Option>, + /// Explicitly specified [default value][2] of this [GraphQL argument][1]. /// /// If [`None`], then this [GraphQL argument][1] is considered as @@ -97,6 +106,16 @@ impl Parse for Attr { .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) .none_or_else(|_| err::dup_arg(&ident))? } + "deprecated" => { + let directive = input.parse::()?; + out.deprecated + .replace(SpanContainer::new( + ident.span(), + directive.reason.as_ref().map(|r| r.span()), + directive, + )) + .none_or_else(|_| err::dup_arg(&ident))? + } "default" => { let val = input.parse::()?; out.default @@ -132,6 +151,7 @@ impl Attr { Ok(Self { name: try_merge_opt!(name: self, another), description: try_merge_opt!(description: self, another), + deprecated: try_merge_opt!(deprecated: self, another), default: try_merge_opt!(default: self, another), context: try_merge_opt!(context: self, another), executor: try_merge_opt!(executor: self, another), @@ -141,13 +161,14 @@ impl Attr { /// Parses [`Attr`] from the given multiple `name`d [`syn::Attribute`]s /// placed on a function argument. pub(crate) fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { - let attr = filter_attrs(name, attrs) + let mut attr = filter_attrs(name, attrs) .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; if let Some(context) = &attr.context { if attr.name.is_some() || attr.description.is_some() + || attr.deprecated.is_some() || attr.default.is_some() || attr.executor.is_some() { @@ -161,6 +182,7 @@ impl Attr { if let Some(executor) = &attr.executor { if attr.name.is_some() || attr.description.is_some() + || attr.deprecated.is_some() || attr.default.is_some() || attr.context.is_some() { @@ -171,6 +193,10 @@ impl Attr { } } + if attr.deprecated.is_none() && attr.context.is_none() && attr.executor.is_none() { + attr.deprecated = deprecation::Directive::parse_from_deprecated_attr(attrs)?; + } + Ok(attr) } @@ -229,6 +255,12 @@ pub(crate) struct OnField { /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments /// [2]: https://spec.graphql.org/October2021#sec-Required-Arguments pub(crate) default: Option, + + /// [Deprecation][2] of this [GraphQL field argument][1] to put into GraphQL schema. + /// + /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments + /// [2]: https://spec.graphql.org/October2021#sec-Deprecation + pub(crate) deprecated: Option, } /// Possible kinds of Rust method arguments for code generation. @@ -274,9 +306,32 @@ impl OnMethod { } } - /// Returns generated code for the [`marker::IsOutputType::mark`] method, - /// which performs static checks for this argument, if it represents an - /// [`OnField`] one. + /// Returns generated code statically asserting deprecability for this argument, if it + /// represents an [`OnField`] one. + #[must_use] + pub(crate) fn assert_deprecable_tokens( + &self, + ty: &syn::Type, + const_scalar: &syn::Type, + field_name: &str, + ) -> Option { + let arg = self.as_regular()?; + (arg.deprecated.is_some() && arg.default.is_none()).then(|| { + let arg_ty = &arg.ty; + let arg_name = &arg.name; + quote_spanned! { arg_ty.span() => + ::juniper::assert_field_arg_deprecable!( + #ty, + #const_scalar, + #field_name, + #arg_name, + ); + } + }) + } + + /// Returns generated code for the [`marker::IsOutputType::mark`] method, which performs static + /// checks for this argument, if it represents an [`OnField`] one. /// /// [`marker::IsOutputType::mark`]: juniper::marker::IsOutputType::mark #[must_use] @@ -300,6 +355,7 @@ impl OnMethod { let (name, ty) = (&arg.name, &arg.ty); let description = &arg.description; + let deprecated = &arg.deprecated; let method = if let Some(val) = &arg.default { quote_spanned! { val.span() => @@ -311,7 +367,7 @@ impl OnMethod { } }; - Some(quote! { .argument(registry #method #description) }) + Some(quote! { .argument(registry #method #description #deprecated) }) } /// Returns generated code for the [`GraphQLValue::resolve_field`] method, @@ -437,6 +493,7 @@ impl OnMethod { ty: argument.ty.as_ref().clone(), description: attr.description.map(SpanContainer::into_inner), default: attr.default.map(SpanContainer::into_inner), + deprecated: attr.deprecated.map(SpanContainer::into_inner), }))) } } diff --git a/juniper_codegen/src/graphql_input_object/derive.rs b/juniper_codegen/src/graphql_input_object/derive.rs index b55589da7..f7137aedf 100644 --- a/juniper_codegen/src/graphql_input_object/derive.rs +++ b/juniper_codegen/src/graphql_input_object/derive.rs @@ -115,6 +115,7 @@ fn parse_field( default: field_attr.default.map(SpanContainer::into_inner), name, description: field_attr.description.map(SpanContainer::into_inner), + deprecated: field_attr.deprecated.map(SpanContainer::into_inner), ignored: field_attr.ignore.is_some(), }) } diff --git a/juniper_codegen/src/graphql_input_object/mod.rs b/juniper_codegen/src/graphql_input_object/mod.rs index 036160342..48b9e00f6 100644 --- a/juniper_codegen/src/graphql_input_object/mod.rs +++ b/juniper_codegen/src/graphql_input_object/mod.rs @@ -5,7 +5,7 @@ pub(crate) mod derive; use proc_macro2::TokenStream; -use quote::{ToTokens, format_ident, quote}; +use quote::{ToTokens, format_ident, quote, quote_spanned}; use syn::{ ext::IdentExt as _, parse::{Parse, ParseStream}, @@ -15,9 +15,9 @@ use syn::{ }; use crate::common::{ - Description, SpanContainer, default, filter_attrs, + Description, SpanContainer, default, deprecation, filter_attrs, parse::{ - ParseBufferExt as _, + GenericsExt as _, ParseBufferExt as _, attr::{OptionExt as _, err}, }, rename, scalar, @@ -186,17 +186,16 @@ struct FieldAttr { name: Option>, /// Explicitly specified [default value][2] of this - /// [GraphQL input object field][1] to be used used in case a field value is + /// [GraphQL input object field][1] to be used in case a field value is /// not provided. /// - /// If [`None`], the this [field][1] will have no [default value][2]. + /// If [`None`], then this [field][1] will have no [default value][2]. /// /// [1]: https://spec.graphql.org/October2021#InputValueDefinition /// [2]: https://spec.graphql.org/October2021#DefaultValue default: Option>, - /// Explicitly specified [description][2] of this - /// [GraphQL input object field][1]. + /// Explicitly specified [description][2] of this [GraphQL input object field][1]. /// /// If [`None`], then Rust doc comment will be used as the [description][2], /// if any. @@ -205,6 +204,15 @@ struct FieldAttr { /// [2]: https://spec.graphql.org/October2021#sec-Descriptions description: Option>, + /// Explicitly specified [deprecation][2] of this [GraphQL input object field][1]. + /// + /// If [`None`], then Rust `#[deprecated]` attribute will be used as the [deprecation][2], if + /// any. + /// + /// [1]: https://spec.graphql.org/October2021#InputValueDefinition + /// [2]: https://spec.graphql.org/October2021#sec-Deprecation + deprecated: Option>, + /// Explicitly specified marker for the Rust struct field to be ignored and /// not included into the code generated for a [GraphQL input object][0] /// implementation. @@ -247,6 +255,16 @@ impl Parse for FieldAttr { .replace(SpanContainer::new(ident.span(), Some(desc.span()), desc)) .none_or_else(|_| err::dup_arg(&ident))? } + "deprecated" => { + let directive = input.parse::()?; + out.deprecated + .replace(SpanContainer::new( + ident.span(), + directive.reason.as_ref().map(|r| r.span()), + directive, + )) + .none_or_else(|_| err::dup_arg(&ident))? + } "ignore" | "skip" => out .ignore .replace(SpanContainer::new(ident.span(), None, ident.clone())) @@ -269,12 +287,13 @@ impl FieldAttr { name: try_merge_opt!(name: self, another), default: try_merge_opt!(default: self, another), description: try_merge_opt!(description: self, another), + deprecated: try_merge_opt!(deprecated: self, another), ignore: try_merge_opt!(ignore: self, another), }) } - /// Parses [`FieldAttr`] from the given multiple `name`d [`syn::Attribute`]s - /// placed on a trait definition. + /// Parses [`FieldAttr`] from the given multiple `name`d [`syn::Attribute`]s placed on a field + /// definition. fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { let mut attr = filter_attrs(name, attrs) .map(|attr| attr.parse_args()) @@ -284,6 +303,10 @@ impl FieldAttr { attr.description = Description::parse_from_doc_attrs(attrs)?; } + if attr.deprecated.is_none() { + attr.deprecated = deprecation::Directive::parse_from_deprecated_attr(attrs)?; + } + Ok(attr) } } @@ -326,6 +349,12 @@ struct FieldDefinition { /// [2]: https://spec.graphql.org/October2021#sec-Descriptions description: Option, + /// [Deprecation][2] of this [GraphQL input object field][1] to put into GraphQL schema. + /// + /// [1]: https://spec.graphql.org/October2021#InputValueDefinition + /// [2]: https://spec.graphql.org/October2021#sec-Deprecation + deprecated: Option, + /// Indicator whether the Rust struct field behinds this /// [GraphQL input object field][1] is being ignored and should not be /// included into the generated code. @@ -399,6 +428,7 @@ impl ToTokens for Definition { self.impl_from_input_value_tokens().to_tokens(into); self.impl_to_input_value_tokens().to_tokens(into); self.impl_reflection_traits_tokens().to_tokens(into); + self.impl_field_meta_tokens().to_tokens(into); } } @@ -412,19 +442,31 @@ impl Definition { fn impl_input_type_tokens(&self) -> TokenStream { let ident = &self.ident; let scalar = &self.scalar; + let const_scalar = &self.scalar.default_ty(); let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); - let assert_fields_input_values = self.fields.iter().filter_map(|f| { - let ty = &f.ty; + let assert_fields_input_values = self.fields.iter().filter(|f| !f.ignored).map(|f| { + let field_ty = &f.ty; - (!f.ignored).then(|| { - quote! { - <#ty as ::juniper::marker::IsInputType<#scalar>>::mark(); + let assert_deprecable = (f.deprecated.is_some() && f.default.is_none()).then(|| { + let field_name = &f.name; + + quote_spanned! { field_ty.span() => + ::juniper::assert_input_field_deprecable!( + #ident #ty_generics, + #const_scalar, + #field_name, + ); } - }) + }); + + quote! { + <#field_ty as ::juniper::marker::IsInputType<#scalar>>::mark(); + #assert_deprecable + } }); quote! { @@ -474,8 +516,9 @@ impl Definition { } }; let description = &f.description; + let deprecated = &f.deprecated; - quote! { registry #arg #description } + quote! { registry #arg #description #deprecated } }) }); @@ -696,6 +739,8 @@ impl Definition { let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); + let fields = self.fields.iter().filter(|f| !f.ignored).map(|f| &f.name); + quote! { #[automatically_derived] impl #impl_generics ::juniper::macros::reflect::BaseType<#scalar> @@ -705,6 +750,7 @@ impl Definition { const NAME: ::juniper::macros::reflect::Type = #name; } + #[automatically_derived] impl #impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ident #ty_generics #where_clause @@ -713,15 +759,72 @@ impl Definition { &[>::NAME]; } + #[automatically_derived] impl #impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ident #ty_generics #where_clause { const VALUE: ::juniper::macros::reflect::WrappedValue = 1; } + + #[automatically_derived] + impl #impl_generics ::juniper::macros::reflect::Fields<#scalar> + for #ident #ty_generics + #where_clause + { + const NAMES: ::juniper::macros::reflect::Names = &[#(#fields),*]; + } } } + /// Returns generated code implementing [`FieldMeta`] for each field of this + /// [GraphQL input object][0]. + /// + /// [`FieldMeta`]: juniper::macros::reflect::FieldMeta + /// [0]: https://spec.graphql.org/October2021#sec-Input-Objects + fn impl_field_meta_tokens(&self) -> TokenStream { + let ident = &self.ident; + let context = &self.context; + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.generics.split_for_impl(); + + self.fields + .iter() + .filter(|f| !f.ignored) + .map(|field| { + let field_name = &field.name; + let mut field_ty = field.ty.clone(); + generics.replace_type_with_defaults(&mut field_ty); + + quote! { + #[allow(non_snake_case)] + #[automatically_derived] + impl #impl_generics ::juniper::macros::reflect::FieldMeta< + #scalar, + { ::juniper::macros::reflect::fnv1a128(#field_name) } + > for #ident #ty_generics #where_clause { + type Context = #context; + type TypeInfo = (); + const TYPE: ::juniper::macros::reflect::Type = + <#field_ty as ::juniper::macros::reflect::BaseType<#scalar>>::NAME; + const SUB_TYPES: ::juniper::macros::reflect::Types = + <#field_ty as ::juniper::macros::reflect::BaseSubTypes<#scalar>>::NAMES; + const WRAPPED_VALUE: ::juniper::macros::reflect::WrappedValue = + <#field_ty as ::juniper::macros::reflect::WrappedType<#scalar>>::VALUE; + const ARGUMENTS: &'static [( + ::juniper::macros::reflect::Name, + ::juniper::macros::reflect::Type, + ::juniper::macros::reflect::WrappedValue, + )] = &[]; + } + } + }) + .collect() + } + /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and /// similar) implementation of this struct. /// diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index feafb3b53..f76ba0866 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -610,12 +610,19 @@ impl Definition { let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.generics.split_for_impl(); let ty_const_generics = self.const_trait_generics(); + let reflectable_ty: syn::Type = parse_quote! { #ty #ty_const_generics }; let fields_marks = self .fields .iter() .map(|f| f.method_mark_tokens(false, scalar)); + let assert_args_deprecable = self.fields.iter().flat_map(|field| { + field.arguments.iter().flatten().filter_map(|arg| { + arg.assert_deprecable_tokens(&reflectable_ty, const_scalar, &field.name) + }) + }); + let is_output = self.implemented_for.iter().map(|impler| { quote_spanned! { impler.span() => <#impler as ::juniper::marker::IsOutputType<#scalar>>::mark(); @@ -639,7 +646,7 @@ impl Definition { quote_spanned! { const_impl_for.span() => ::juniper::assert_transitive_impls!( #const_scalar, - #ty #ty_const_generics, + #reflectable_ty, #const_impl_for, #( #const_implements ),* ); @@ -654,15 +661,16 @@ impl Definition { { fn mark() { #( #fields_marks )* + #( #assert_args_deprecable )* #( #is_output )* ::juniper::assert_interfaces_impls!( #const_scalar, - #ty #ty_const_generics, + #reflectable_ty, #( #const_impl_for ),* ); ::juniper::assert_implemented_for!( #const_scalar, - #ty #ty_const_generics, + #reflectable_ty, #( #const_implements ),* ); #( #transitive_checks )* @@ -1017,8 +1025,8 @@ impl Definition { .collect() } - /// Returns generated code implementing [`Field`] trait for each field of - /// this [GraphQL interface][1]. + /// Returns generated code implementing [`Field`] trait for each field of this + /// [GraphQL interface][1]. /// /// [`Field`]: juniper::macros::reflect::Field /// [1]: https://spec.graphql.org/October2021#sec-Interfaces @@ -1053,7 +1061,7 @@ impl Definition { let mut return_ty = field.ty.clone(); generics.replace_type_with_defaults(&mut return_ty); - let const_ty_generics = self.const_trait_generics(); + let ty_const_generics = self.const_trait_generics(); let unreachable_arm = (self.implemented_for.is_empty() || !self.generics.params.is_empty()) @@ -1077,7 +1085,7 @@ impl Definition { match self { #( #ty::#implemented_for_idents(v) => { ::juniper::assert_field!( - #ty #const_ty_generics, + #ty #ty_const_generics, #const_implemented_for, #const_scalar, #field_name, @@ -1097,8 +1105,8 @@ impl Definition { .collect() } - /// Returns generated code implementing [`AsyncField`] trait for each field - /// of this [GraphQL interface][1]. + /// Returns generated code implementing [`AsyncField`] trait for each field of this + /// [GraphQL interface][1]. /// /// [`AsyncField`]: juniper::macros::reflect::AsyncField /// [1]: https://spec.graphql.org/October2021#sec-Interfaces @@ -1133,7 +1141,7 @@ impl Definition { let mut return_ty = field.ty.clone(); generics.replace_type_with_defaults(&mut return_ty); - let const_ty_generics = self.const_trait_generics(); + let ty_const_generics = self.const_trait_generics(); let unreachable_arm = (self.implemented_for.is_empty() || !self.generics.params.is_empty()) @@ -1157,7 +1165,7 @@ impl Definition { match self { #( #ty::#implemented_for_idents(v) => { ::juniper::assert_field!( - #ty #const_ty_generics, + #ty #ty_const_generics, #const_implemented_for, #const_scalar, #field_name, diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index f59047bb1..486bee303 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -332,6 +332,7 @@ impl Definition { #[must_use] pub(crate) fn impl_output_type_tokens(&self) -> TokenStream { let scalar = &self.scalar; + let const_scalar = &self.scalar.default_ty(); let (impl_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; @@ -342,6 +343,14 @@ impl Definition { .iter() .map(|f| f.method_mark_tokens(coerce_result, scalar)); + let assert_args_deprecable = self.fields.iter().flat_map(|field| { + field + .arguments + .iter() + .flatten() + .filter_map(|arg| arg.assert_deprecable_tokens(ty, const_scalar, &field.name)) + }); + let interface_tys = self.interfaces.iter(); quote! { @@ -350,6 +359,7 @@ impl Definition { { fn mark() { #( #fields_marks )* + #( #assert_args_deprecable )* #( <#interface_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* } } diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 05f780c1d..935d803b7 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -135,7 +135,7 @@ use self::common::diagnostic::{self, ResultExt as _}; /// } /// ``` /// -/// # Custom name and description +/// # Custom name, description and deprecation /// /// The name of a [GraphQL input object][0] or its [fields][1] may be overridden /// with the `name` attribute's argument. By default, a type name or a struct @@ -145,6 +145,9 @@ use self::common::diagnostic::{self, ResultExt as _}; /// specified either with the `description`/`desc` attribute's argument, or with /// a regular Rust doc comment. /// +/// [GraphQL input object fields][1] may be deprecated by specifying the `deprecated` attribute's +/// argument, or with the regular Rust `#[deprecated]` attribute. +/// /// ```rust /// # use juniper::GraphQLInputObject; /// # @@ -161,7 +164,15 @@ use self::common::diagnostic::{self, ResultExt as _}; /// x: f64, /// /// #[graphql(name = "y", desc = "Ordinate value")] +/// // Only `Null`able input fields or non-`Null` input fields with default values +/// // can be deprecated. +/// #[graphql(default, deprecated = "Obsolete")] /// y_coord: f64, +/// +/// // If no explicit deprecation reason is provided, +/// // then the default "No longer supported" one is used. +/// #[deprecated] +/// z: Option, // has no description in GraphQL schema /// } /// ``` /// @@ -1004,8 +1015,8 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// The description of [GraphQL interface][1], its field, or a field argument may be specified /// either with a `description`/`desc` attribute's argument, or with a regular Rust doc comment. /// -/// A field of [GraphQL interface][1] may be deprecated by specifying a `deprecated` attribute's -/// argument, or with regular Rust `#[deprecated]` attribute. +/// A field (or its argument) of [GraphQL interface][1] may be deprecated by specifying a +/// `deprecated` attribute's argument, or with the regular Rust `#[deprecated]` attribute. /// /// The default value of a field argument may be specified with a `default` attribute argument (if /// no exact value is specified then [`Default::default`] is used). @@ -1014,25 +1025,43 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream { /// # use juniper::graphql_interface; /// # /// #[graphql_interface] -/// #[graphql(name = "Character", desc = "Possible episode characters.")] +/// #[graphql( +/// // Rename the type for GraphQL by specifying the name here. +/// name = "Character", +/// // You may also specify a description here. +/// // If present, doc comments will be ignored. +/// desc = "Possible episode characters.", +/// )] /// trait Chrctr { /// #[graphql(name = "id", desc = "ID of the character.")] /// #[graphql(deprecated = "Don't use it")] /// fn some_id( /// &self, /// #[graphql(name = "number", desc = "Arbitrary number.")] +/// // You may specify default values. +/// // A default can be any valid expression that yields the right type. /// #[graphql(default = 5)] /// num: i32, /// ) -> &str; /// } /// -/// // NOTICE: Rust docs are used as GraphQL description. +/// // Rust docs are used as GraphQL description. /// /// Possible episode characters. /// #[graphql_interface] /// trait CharacterWithDocs { +/// // Doc comments also work on fields. /// /// ID of the character. +/// // If no explicit deprecation reason is provided, +/// // then the default "No longer supported" one is used. /// #[deprecated] -/// fn id(&self, #[graphql(default)] num: i32) -> &str; +/// fn id( +/// &self, +/// // If expression is not specified then `Default::default()` is used. +/// #[graphql(default, deprecated)] num: i32, +/// // Only `Null`able arguments or non-`Null` arguments with default values +/// // can be deprecated. +/// #[graphql(deprecated)] modifier: Option, +/// ) -> &str; /// } /// ``` /// @@ -1652,9 +1681,8 @@ pub fn derive_object(body: TokenStream) -> TokenStream { /// be specified either with a `description`/`desc` attribute's argument, or /// with a regular Rust doc comment. /// -/// A field of [GraphQL object][1] may be deprecated by specifying a -/// `deprecated` attribute's argument, or with regular Rust `#[deprecated]` -/// attribute. +/// A field (or its argument) of [GraphQL object][1] may be deprecated by specifying a `deprecated` +/// attribute's argument, or with the regular Rust `#[deprecated]` attribute. /// /// The default value of a field argument may be specified with a `default` /// attribute argument (if no exact value is specified then [`Default::default`] @@ -1696,11 +1724,16 @@ pub fn derive_object(body: TokenStream) -> TokenStream { /// impl HumanWithDocs { /// // Doc comments also work on fields. /// /// ID of the human. +/// // If no explicit deprecation reason is provided, +/// // then the default "No longer supported" one is used. /// #[deprecated] /// fn id( /// &self, /// // If expression is not specified then `Default::default()` is used. -/// #[graphql(default)] num: i32, +/// #[graphql(default, deprecated)] num: i32, +/// // Only `Null`able arguments or non-`Null` arguments with default values +/// // can be deprecated. +/// #[graphql(deprecated)] modifier: Option, /// ) -> &str { /// "Deprecated" /// } diff --git a/tests/codegen/fail/input-object/derive_field_non_deprecable.rs b/tests/codegen/fail/input-object/derive_field_non_deprecable.rs new file mode 100644 index 000000000..90f6a249a --- /dev/null +++ b/tests/codegen/fail/input-object/derive_field_non_deprecable.rs @@ -0,0 +1,11 @@ +use juniper::GraphQLInputObject; + +#[derive(GraphQLInputObject)] +struct Object { + #[graphql(deprecated)] + test: String, + #[deprecated] + other: i32, +} + +fn main() {} diff --git a/tests/codegen/fail/input-object/derive_field_non_deprecable.stderr b/tests/codegen/fail/input-object/derive_field_non_deprecable.stderr new file mode 100644 index 000000000..2959ef204 --- /dev/null +++ b/tests/codegen/fail/input-object/derive_field_non_deprecable.stderr @@ -0,0 +1,15 @@ +error[E0080]: evaluation panicked: field `test` of `Object` input object cannot be deprecated, because its type `String!` is neither `Null`able nor the default field value is specified + --> fail/input-object/derive_field_non_deprecable.rs:6:11 + | +6 | test: String, + | ^^^^^^ evaluation of `>::mark::_` failed here + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_input_field_deprecable` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation panicked: field `other` of `Object` input object cannot be deprecated, because its type `Int!` is neither `Null`able nor the default field value is specified + --> fail/input-object/derive_field_non_deprecable.rs:8:12 + | +8 | other: i32, + | ^^^ evaluation of `>::mark::_` failed here + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_input_field_deprecable` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/struct/attr_missing_field.stderr b/tests/codegen/fail/interface/struct/attr_missing_field.stderr index 7c77a3246..9a5c34811 100644 --- a/tests/codegen/fail/interface/struct/attr_missing_field.stderr +++ b/tests/codegen/fail/interface/struct/attr_missing_field.stderr @@ -1,4 +1,4 @@ -error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`. +error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA` --> fail/interface/struct/attr_missing_field.rs:11:5 | 11 | id: String, @@ -6,7 +6,7 @@ error[E0080]: evaluation panicked: Failed to implement interface `Character` on | = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`. +error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA` --> fail/interface/struct/attr_missing_field.rs:11:5 | 11 | id: String, @@ -14,7 +14,7 @@ error[E0080]: evaluation panicked: Failed to implement interface `Character` on | = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`. +error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA` --> fail/interface/struct/attr_missing_field.rs:11:5 | 11 | id: String, @@ -22,7 +22,7 @@ error[E0080]: evaluation panicked: Failed to implement interface `Character` on | = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`. +error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA` --> fail/interface/struct/attr_missing_field.rs:11:5 | 11 | id: String, @@ -39,7 +39,7 @@ error[E0277]: the trait bound `ObjA: reflect::Field<__S, 11301463986558097276003 = help: the trait `Field<__S, 11301463986558097276003903130001171064>` is not implemented for `ObjA` but trait `Field<__S, 140650918148392961738240285796466530725>` is implemented for it -error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`. +error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA` --> fail/interface/struct/attr_missing_field.rs:11:5 | 11 | id: String, @@ -56,7 +56,7 @@ error[E0277]: the trait bound `ObjA: AsyncField<__S, 113014639865580972760039031 = help: the trait `AsyncField<__S, 11301463986558097276003903130001171064>` is not implemented for `ObjA` but trait `AsyncField<__S, 140650918148392961738240285796466530725>` is implemented for it -error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`. +error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA` --> fail/interface/struct/attr_missing_field.rs:11:5 | 11 | id: String, diff --git a/tests/codegen/fail/interface/struct/derive_missing_field.stderr b/tests/codegen/fail/interface/struct/derive_missing_field.stderr index 3fb268cc6..0f64c1c6e 100644 --- a/tests/codegen/fail/interface/struct/derive_missing_field.stderr +++ b/tests/codegen/fail/interface/struct/derive_missing_field.stderr @@ -1,4 +1,4 @@ -error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`. +error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA` --> fail/interface/struct/derive_missing_field.rs:12:5 | 12 | id: String, @@ -6,7 +6,7 @@ error[E0080]: evaluation panicked: Failed to implement interface `Character` on | = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`. +error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA` --> fail/interface/struct/derive_missing_field.rs:12:5 | 12 | id: String, @@ -14,7 +14,7 @@ error[E0080]: evaluation panicked: Failed to implement interface `Character` on | = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`. +error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA` --> fail/interface/struct/derive_missing_field.rs:12:5 | 12 | id: String, @@ -22,7 +22,7 @@ error[E0080]: evaluation panicked: Failed to implement interface `Character` on | = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`. +error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA` --> fail/interface/struct/derive_missing_field.rs:12:5 | 12 | id: String, @@ -39,7 +39,7 @@ error[E0277]: the trait bound `ObjA: reflect::Field<__S, 11301463986558097276003 = help: the trait `Field<__S, 11301463986558097276003903130001171064>` is not implemented for `ObjA` but trait `Field<__S, 140650918148392961738240285796466530725>` is implemented for it -error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`. +error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA` --> fail/interface/struct/derive_missing_field.rs:12:5 | 12 | id: String, @@ -56,7 +56,7 @@ error[E0277]: the trait bound `ObjA: AsyncField<__S, 113014639865580972760039031 = help: the trait `AsyncField<__S, 11301463986558097276003903130001171064>` is not implemented for `ObjA` but trait `AsyncField<__S, 140650918148392961738240285796466530725>` is implemented for it -error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`. +error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA` --> fail/interface/struct/derive_missing_field.rs:12:5 | 12 | id: String, diff --git a/tests/codegen/fail/interface/trait/argument_non_deprecable.rs b/tests/codegen/fail/interface/trait/argument_non_deprecable.rs new file mode 100644 index 000000000..89047df5f --- /dev/null +++ b/tests/codegen/fail/interface/trait/argument_non_deprecable.rs @@ -0,0 +1,9 @@ +use juniper::graphql_interface; + +#[graphql_interface] +trait Character { + fn id(&self, #[graphql(deprecated)] num: i32) -> &str; + async fn name(&self, #[graphql(deprecated = "reason")] pre: Vec) -> &str; +} + +fn main() {} diff --git a/tests/codegen/fail/interface/trait/argument_non_deprecable.stderr b/tests/codegen/fail/interface/trait/argument_non_deprecable.stderr new file mode 100644 index 000000000..40bbb1dfd --- /dev/null +++ b/tests/codegen/fail/interface/trait/argument_non_deprecable.stderr @@ -0,0 +1,15 @@ +error[E0080]: evaluation panicked: argument `num` of `Character.id` field cannot be deprecated, because its type `Int!` is neither `Null`able nor the default argument value is specified + --> fail/interface/trait/argument_non_deprecable.rs:5:46 + | +5 | fn id(&self, #[graphql(deprecated)] num: i32) -> &str; + | ^^^ evaluation of `>::mark::_` failed here + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field_arg_deprecable` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation panicked: argument `pre` of `Character.name` field cannot be deprecated, because its type `[String!]!` is neither `Null`able nor the default argument value is specified + --> fail/interface/trait/argument_non_deprecable.rs:6:65 + | +6 | async fn name(&self, #[graphql(deprecated = "reason")] pre: Vec) -> &str; + | ^^^ evaluation of `>::mark::_` failed here + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field_arg_deprecable` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/codegen/fail/interface/trait/missing_field.stderr b/tests/codegen/fail/interface/trait/missing_field.stderr index 39d9756e5..4a25b9349 100644 --- a/tests/codegen/fail/interface/trait/missing_field.stderr +++ b/tests/codegen/fail/interface/trait/missing_field.stderr @@ -1,4 +1,4 @@ -error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`. +error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA` --> fail/interface/trait/missing_field.rs:11:8 | 11 | fn id(&self) -> &str; @@ -6,7 +6,7 @@ error[E0080]: evaluation panicked: Failed to implement interface `Character` on | = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`. +error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA` --> fail/interface/trait/missing_field.rs:11:8 | 11 | fn id(&self) -> &str; @@ -14,7 +14,7 @@ error[E0080]: evaluation panicked: Failed to implement interface `Character` on | = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`. +error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA` --> fail/interface/trait/missing_field.rs:11:8 | 11 | fn id(&self) -> &str; @@ -22,7 +22,7 @@ error[E0080]: evaluation panicked: Failed to implement interface `Character` on | = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`. +error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA` --> fail/interface/trait/missing_field.rs:11:8 | 11 | fn id(&self) -> &str; @@ -39,7 +39,7 @@ error[E0277]: the trait bound `ObjA: reflect::Field<__S, 11301463986558097276003 = help: the trait `Field<__S, 11301463986558097276003903130001171064>` is not implemented for `ObjA` but trait `Field<__S, 140650918148392961738240285796466530725>` is implemented for it -error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`. +error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA` --> fail/interface/trait/missing_field.rs:11:8 | 11 | fn id(&self) -> &str; @@ -56,7 +56,7 @@ error[E0277]: the trait bound `ObjA: AsyncField<__S, 113014639865580972760039031 = help: the trait `AsyncField<__S, 11301463986558097276003903130001171064>` is not implemented for `ObjA` but trait `AsyncField<__S, 140650918148392961738240285796466530725>` is implemented for it -error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`. +error[E0080]: evaluation panicked: Failed to implement interface `Character` on `ObjA`: field `id` isn't implemented on `ObjA` --> fail/interface/trait/missing_field.rs:11:8 | 11 | fn id(&self) -> &str; diff --git a/tests/codegen/fail/object/argument_non_deprecable.rs b/tests/codegen/fail/object/argument_non_deprecable.rs new file mode 100644 index 000000000..5a974f1cd --- /dev/null +++ b/tests/codegen/fail/object/argument_non_deprecable.rs @@ -0,0 +1,18 @@ +use juniper::graphql_object; + +struct Character; + +#[graphql_object] +impl Character { + fn id(&self, #[graphql(deprecated)] num: i32) -> &str { + _ = num; + "123" + } + + async fn name(&self, #[graphql(deprecated = "reason")] pre: Vec) -> &str { + _ = pre; + "whatever" + } +} + +fn main() {} diff --git a/tests/codegen/fail/object/argument_non_deprecable.stderr b/tests/codegen/fail/object/argument_non_deprecable.stderr new file mode 100644 index 000000000..81669d0b4 --- /dev/null +++ b/tests/codegen/fail/object/argument_non_deprecable.stderr @@ -0,0 +1,15 @@ +error[E0080]: evaluation panicked: argument `num` of `Character.id` field cannot be deprecated, because its type `Int!` is neither `Null`able nor the default argument value is specified + --> fail/object/argument_non_deprecable.rs:7:46 + | +7 | fn id(&self, #[graphql(deprecated)] num: i32) -> &str { + | ^^^ evaluation of `>::mark::_` failed here + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field_arg_deprecable` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation panicked: argument `pre` of `Character.name` field cannot be deprecated, because its type `[String!]!` is neither `Null`able nor the default argument value is specified + --> fail/object/argument_non_deprecable.rs:12:65 + | +12 | async fn name(&self, #[graphql(deprecated = "reason")] pre: Vec) -> &str { + | ^^^ evaluation of `>::mark::_` failed here + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::juniper::assert_field_arg_deprecable` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/integration/tests/codegen_enum_derive.rs b/tests/integration/tests/codegen_enum_derive.rs index 7030bb262..b3bf3906a 100644 --- a/tests/integration/tests/codegen_enum_derive.rs +++ b/tests/integration/tests/codegen_enum_derive.rs @@ -387,7 +387,7 @@ mod description_from_doc_comment { } } -mod deprecation_from_attr { +mod value_deprecation_from_rust_attr { #![expect(deprecated, reason = "GraphQL schema testing")] use super::*; diff --git a/tests/integration/tests/codegen_input_object_derive.rs b/tests/integration/tests/codegen_input_object_derive.rs index f2599a327..4aa9e00bb 100644 --- a/tests/integration/tests/codegen_input_object_derive.rs +++ b/tests/integration/tests/codegen_input_object_derive.rs @@ -32,6 +32,7 @@ mod trivial { #[tokio::test] async fn resolves() { + // language=GraphQL const DOC: &str = r#"{ x(point: { x: 10, y: 20 }) }"#; @@ -46,6 +47,7 @@ mod trivial { #[tokio::test] async fn is_graphql_input_object() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Point2D") { kind @@ -62,6 +64,7 @@ mod trivial { #[tokio::test] async fn uses_type_name() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Point2D") { name @@ -78,6 +81,7 @@ mod trivial { #[tokio::test] async fn has_no_description() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Point2D") { description @@ -94,6 +98,7 @@ mod trivial { #[tokio::test] async fn has_input_fields() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Point2D") { inputFields { @@ -156,6 +161,7 @@ mod default_value { #[tokio::test] async fn resolves() { + // language=GraphQL const DOC: &str = r#"query q($ve_num: Float!) { literal_implicit_other_number: x(point: { y: 20 }) literal_explicit_number: x(point: { x: 20 }) @@ -181,6 +187,7 @@ mod default_value { #[tokio::test] async fn errs_on_explicit_null_literal() { + // language=GraphQL const DOC: &str = r#"{ x(point: { x: 20, y: null }) }"#; let schema = schema(QueryRoot); @@ -198,6 +205,7 @@ mod default_value { #[tokio::test] async fn errs_on_missing_variable() { + // language=GraphQL const DOC: &str = r#"query q($x: Float!){ x(point: { x: $x }) }"#; let schema = schema(QueryRoot); @@ -214,6 +222,7 @@ mod default_value { #[tokio::test] async fn is_graphql_input_object() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Point2D") { kind @@ -230,6 +239,7 @@ mod default_value { #[tokio::test] async fn has_input_fields() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Point2D") { inputFields { @@ -289,6 +299,7 @@ mod default_nullable_value { #[tokio::test] async fn resolves() { + // language=GraphQL const DOC: &str = r#"query q( $ve_num: Float, $ve_null: Float, @@ -347,6 +358,7 @@ mod default_nullable_value { #[tokio::test] async fn is_graphql_input_object() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Point2D") { kind @@ -363,6 +375,7 @@ mod default_nullable_value { #[tokio::test] async fn has_input_fields() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Point2D") { inputFields { @@ -435,6 +448,7 @@ mod ignored_field { #[tokio::test] async fn resolves() { + // language=GraphQL const DOC: &str = r#"{ x(point: { x: 10, y: 20 }) }"#; @@ -449,6 +463,7 @@ mod ignored_field { #[tokio::test] async fn is_graphql_input_object() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Point2D") { kind @@ -465,6 +480,7 @@ mod ignored_field { #[tokio::test] async fn uses_type_name() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Point2D") { name @@ -481,6 +497,7 @@ mod ignored_field { #[tokio::test] async fn has_no_description() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Point2D") { description @@ -497,6 +514,7 @@ mod ignored_field { #[tokio::test] async fn has_input_fields() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Point2D") { inputFields { @@ -561,6 +579,7 @@ mod description_from_doc_comment { #[tokio::test] async fn resolves() { + // language=GraphQL const DOC: &str = r#"{ x(point: { x: 10, yCoord: 20 }) }"#; @@ -575,6 +594,7 @@ mod description_from_doc_comment { #[tokio::test] async fn is_graphql_input_object() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Point2D") { kind @@ -591,6 +611,7 @@ mod description_from_doc_comment { #[tokio::test] async fn uses_type_name() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Point2D") { name @@ -607,6 +628,7 @@ mod description_from_doc_comment { #[tokio::test] async fn has_description() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Point2D") { description @@ -628,6 +650,7 @@ mod description_from_doc_comment { #[tokio::test] async fn has_input_fields() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Point2D") { inputFields { @@ -694,6 +717,7 @@ mod description_from_graphql_attr { #[tokio::test] async fn resolves() { + // language=GraphQL const DOC: &str = r#"{ x(point: { x: 10, y: 20 }) }"#; @@ -708,6 +732,7 @@ mod description_from_graphql_attr { #[tokio::test] async fn is_graphql_input_object() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Point") { kind @@ -724,6 +749,7 @@ mod description_from_graphql_attr { #[tokio::test] async fn uses_type_name() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Point") { name @@ -740,6 +766,7 @@ mod description_from_graphql_attr { #[tokio::test] async fn has_description() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Point") { description @@ -761,6 +788,7 @@ mod description_from_graphql_attr { #[tokio::test] async fn has_input_fields() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Point") { inputFields { @@ -801,6 +829,304 @@ mod description_from_graphql_attr { } } +mod deprecation_from_graphql_attr { + use super::*; + + #[derive(GraphQLInputObject)] + struct Point { + x: f64, + #[graphql(deprecated = "Use `Point2D.x`.")] + #[deprecated(note = "Should be omitted.")] + x_coord: prelude::Option, + y: f64, + #[graphql(deprecated)] + #[deprecated(note = "Should be omitted.")] + z: prelude::Option, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn x(point: Point) -> f64 { + point.x + } + } + + #[tokio::test] + async fn resolves() { + // language=GraphQL + const DOC: &str = r#"{ + x(point: { x: 10, y: 20 }) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"x": 10.0}), vec![])), + ); + } + + #[tokio::test] + async fn is_graphql_input_object() { + // language=GraphQL + const DOC: &str = r#"{ + __type(name: "Point") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INPUT_OBJECT"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_input_fields() { + // language=GraphQL + const DOC: &str = r#"{ + __type(name: "Point") { + inputFields { + name + type { + ofType { + name + } + } + defaultValue + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"inputFields": [{ + "name": "x", + "type": {"ofType": {"name": "Float"}}, + "defaultValue": null, + }, { + "name": "y", + "type": {"ofType": {"name": "Float"}}, + "defaultValue": null, + }]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn deprecates_fields() { + // language=GraphQL + const DOC: &str = r#"{ + __type(name: "Point") { + inputFields(includeDeprecated: true) { + name + isDeprecated + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"inputFields": [ + {"name": "x", "isDeprecated": false}, + {"name": "xCoord", "isDeprecated": true}, + {"name": "y", "isDeprecated": false}, + {"name": "z", "isDeprecated": true}, + ]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn provides_deprecation_reason() { + // language=GraphQL + const DOC: &str = r#"{ + __type(name: "Point") { + inputFields(includeDeprecated: true) { + name + deprecationReason + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"inputFields": [ + {"name": "x", "deprecationReason": null}, + {"name": "xCoord", "deprecationReason": "Use `Point2D.x`."}, + {"name": "y", "deprecationReason": null}, + {"name": "z", "deprecationReason": null}, + ]}}), + vec![], + )), + ); + } +} + +mod deprecation_from_rust_attr { + use super::*; + + #[derive(GraphQLInputObject)] + struct Point { + x: f64, + #[deprecated(note = "Use `Point2D.x`.")] + #[graphql(default = 0.0)] + x_coord: f64, + y: f64, + #[deprecated] + #[graphql(default = 0.0)] + z: f64, + } + + struct QueryRoot; + + #[graphql_object] + impl QueryRoot { + fn x(point: Point) -> f64 { + point.x + } + } + + #[tokio::test] + async fn resolves() { + // language=GraphQL + const DOC: &str = r#"{ + x(point: { x: 10, y: 20 }) + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"x": 10.0}), vec![])), + ); + } + + #[tokio::test] + async fn is_graphql_input_object() { + // language=GraphQL + const DOC: &str = r#"{ + __type(name: "Point") { + kind + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INPUT_OBJECT"}}), vec![])), + ); + } + + #[tokio::test] + async fn has_input_fields() { + // language=GraphQL + const DOC: &str = r#"{ + __type(name: "Point") { + inputFields { + name + type { + ofType { + name + } + } + defaultValue + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"inputFields": [{ + "name": "x", + "type": {"ofType": {"name": "Float"}}, + "defaultValue": null, + }, { + "name": "y", + "type": {"ofType": {"name": "Float"}}, + "defaultValue": null, + }]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn deprecates_fields() { + // language=GraphQL + const DOC: &str = r#"{ + __type(name: "Point") { + inputFields(includeDeprecated: true) { + name + isDeprecated + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"inputFields": [ + {"name": "x", "isDeprecated": false}, + {"name": "xCoord", "isDeprecated": true}, + {"name": "y", "isDeprecated": false}, + {"name": "z", "isDeprecated": true}, + ]}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn provides_deprecation_reason() { + // language=GraphQL + const DOC: &str = r#"{ + __type(name: "Point") { + inputFields(includeDeprecated: true) { + name + deprecationReason + } + } + }"#; + + let schema = schema(QueryRoot); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"inputFields": [ + {"name": "x", "deprecationReason": null}, + {"name": "xCoord", "deprecationReason": "Use `Point2D.x`."}, + {"name": "y", "deprecationReason": null}, + {"name": "z", "deprecationReason": null}, + ]}}), + vec![], + )), + ); + } +} + mod renamed_all_fields { use super::*; @@ -822,6 +1148,7 @@ mod renamed_all_fields { #[tokio::test] async fn resolves() { + // language=GraphQL const DOC: &str = r#"{ x(point: { x_coord: 10, y: 20 }) }"#; @@ -836,6 +1163,7 @@ mod renamed_all_fields { #[tokio::test] async fn is_graphql_input_object() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Point2D") { kind @@ -852,6 +1180,7 @@ mod renamed_all_fields { #[tokio::test] async fn has_input_fields() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Point2D") { inputFields { diff --git a/tests/integration/tests/codegen_interface_attr_struct.rs b/tests/integration/tests/codegen_interface_attr_struct.rs index bf7d25221..e9da94c3d 100644 --- a/tests/integration/tests/codegen_interface_attr_struct.rs +++ b/tests/integration/tests/codegen_interface_attr_struct.rs @@ -1083,7 +1083,7 @@ mod description_from_doc_comment { } } -mod deprecation_from_attr { +mod field_deprecation_from_rust_attr { use super::*; #[graphql_interface(for = Human)] diff --git a/tests/integration/tests/codegen_interface_attr_trait.rs b/tests/integration/tests/codegen_interface_attr_trait.rs index 129a12fc8..146991e4e 100644 --- a/tests/integration/tests/codegen_interface_attr_trait.rs +++ b/tests/integration/tests/codegen_interface_attr_trait.rs @@ -37,6 +37,7 @@ mod no_implers { #[tokio::test] async fn is_graphql_interface() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { kind @@ -53,6 +54,7 @@ mod no_implers { #[tokio::test] async fn uses_trait_name() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { name @@ -69,6 +71,7 @@ mod no_implers { #[tokio::test] async fn has_no_description() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { description @@ -141,6 +144,7 @@ mod trivial { #[tokio::test] async fn resolves_human() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Human { @@ -166,6 +170,7 @@ mod trivial { #[tokio::test] async fn resolves_droid() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Droid { @@ -191,6 +196,7 @@ mod trivial { #[tokio::test] async fn resolves_id_field() { + // language=GraphQL const DOC: &str = r#"{ character { id @@ -212,6 +218,7 @@ mod trivial { #[tokio::test] async fn is_graphql_interface() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { kind @@ -228,6 +235,7 @@ mod trivial { #[tokio::test] async fn registers_all_implementers() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { possibleTypes { @@ -281,6 +289,7 @@ mod trivial { #[tokio::test] async fn uses_trait_name() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { name @@ -297,6 +306,7 @@ mod trivial { #[tokio::test] async fn has_no_description() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { description @@ -369,6 +379,7 @@ mod explicit_alias { #[tokio::test] async fn resolves_human() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Human { @@ -394,6 +405,7 @@ mod explicit_alias { #[tokio::test] async fn resolves_droid() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Droid { @@ -419,6 +431,7 @@ mod explicit_alias { #[tokio::test] async fn resolves_id_field() { + // language=GraphQL const DOC: &str = r#"{ character { id @@ -440,6 +453,7 @@ mod explicit_alias { #[tokio::test] async fn is_graphql_interface() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { kind @@ -456,6 +470,7 @@ mod explicit_alias { #[tokio::test] async fn uses_trait_name() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { name @@ -472,6 +487,7 @@ mod explicit_alias { #[tokio::test] async fn has_no_description() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { description @@ -544,6 +560,7 @@ mod trivial_async { #[tokio::test] async fn resolves_human() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Human { @@ -569,6 +586,7 @@ mod trivial_async { #[tokio::test] async fn resolves_droid() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Droid { @@ -594,6 +612,7 @@ mod trivial_async { #[tokio::test] async fn resolves_id_field() { + // language=GraphQL const DOC: &str = r#"{ character { id @@ -615,6 +634,7 @@ mod trivial_async { #[tokio::test] async fn is_graphql_interface() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { kind @@ -631,6 +651,7 @@ mod trivial_async { #[tokio::test] async fn registers_all_implementers() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { possibleTypes { @@ -684,6 +705,7 @@ mod trivial_async { #[tokio::test] async fn uses_trait_name() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { name @@ -700,6 +722,7 @@ mod trivial_async { #[tokio::test] async fn has_no_description() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { description @@ -780,6 +803,7 @@ mod fallible_field { #[tokio::test] async fn resolves_human() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Human { @@ -805,6 +829,7 @@ mod fallible_field { #[tokio::test] async fn resolves_droid() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Droid { @@ -830,6 +855,7 @@ mod fallible_field { #[tokio::test] async fn resolves_id_field() { + // language=GraphQL const DOC: &str = r#"{ character { id @@ -851,6 +877,7 @@ mod fallible_field { #[tokio::test] async fn has_correct_graphql_type() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { name @@ -946,6 +973,7 @@ mod generic { #[tokio::test] async fn resolves_human() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Human { @@ -968,6 +996,7 @@ mod generic { #[tokio::test] async fn resolves_droid() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Droid { @@ -993,6 +1022,7 @@ mod generic { #[tokio::test] async fn resolves_id_field() { + // language=GraphQL const DOC: &str = r#"{ character { id @@ -1014,6 +1044,7 @@ mod generic { #[tokio::test] async fn uses_trait_name_without_type_params() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { name @@ -1105,6 +1136,7 @@ mod argument { #[tokio::test] async fn camelcases_name() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { fields { @@ -1140,6 +1172,7 @@ mod argument { #[tokio::test] async fn has_no_description() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { fields { @@ -1166,6 +1199,7 @@ mod argument { #[tokio::test] async fn has_no_defaults() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { fields { @@ -1279,6 +1313,7 @@ mod default_argument { #[tokio::test] async fn has_defaults() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { fields { @@ -1361,6 +1396,7 @@ mod description_from_doc_comment { #[tokio::test] async fn uses_doc_comment_as_description() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { description @@ -1385,7 +1421,7 @@ mod description_from_doc_comment { } } -mod deprecation_from_attr { +mod field_deprecation_from_rust_attr { use super::*; #[graphql_interface(for = Human)] @@ -1438,6 +1474,7 @@ mod deprecation_from_attr { #[tokio::test] async fn resolves_id_field() { + // language=GraphQL const DOC: &str = r#"{ character { id @@ -1454,6 +1491,7 @@ mod deprecation_from_attr { #[tokio::test] async fn resolves_deprecated_fields() { + // language=GraphQL const DOC: &str = r#"{ character { a @@ -1471,6 +1509,7 @@ mod deprecation_from_attr { #[tokio::test] async fn deprecates_fields() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { fields(includeDeprecated: true) { @@ -1497,6 +1536,7 @@ mod deprecation_from_attr { #[tokio::test] async fn provides_deprecation_reason() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { fields(includeDeprecated: true) { @@ -1533,7 +1573,10 @@ mod explicit_name_description_and_deprecation { #[deprecated(note = "Should be omitted.")] fn id( &self, - #[graphql(name = "myName", desc = "My argument.")] n: prelude::Option, + #[graphql(name = "myName", desc = "My name.", deprecated)] n: prelude::Option< + prelude::String, + >, + #[graphql(name = "myAge", desc = "My age.", default, deprecated = "Because.")] a: i32, ) -> &str; #[graphql(deprecated)] @@ -1550,7 +1593,11 @@ mod explicit_name_description_and_deprecation { #[graphql_object(impl = CharacterValue)] impl Human { - fn my_id(&self, #[graphql(name = "myName")] _: prelude::Option) -> &str { + fn my_id( + &self, + #[graphql(name = "myName")] _: prelude::Option, + #[graphql(name = "myAge", default)] _: i32, + ) -> &str { &self.id } @@ -1582,6 +1629,7 @@ mod explicit_name_description_and_deprecation { #[tokio::test] async fn resolves_fields() { + // language=GraphQL const DOC: &str = r#"{ character { myId @@ -1607,12 +1655,13 @@ mod explicit_name_description_and_deprecation { #[tokio::test] async fn uses_custom_name() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "MyChar") { name fields(includeDeprecated: true) { name - args { + args(includeDeprecated: true) { name } } @@ -1627,7 +1676,7 @@ mod explicit_name_description_and_deprecation { graphql_value!({"__type": { "name": "MyChar", "fields": [ - {"name": "myId", "args": [{"name": "myName"}]}, + {"name": "myId", "args": [{"name": "myName"}, {"name": "myAge"}]}, {"name": "a", "args": []}, {"name": "b", "args": []}, ], @@ -1639,13 +1688,14 @@ mod explicit_name_description_and_deprecation { #[tokio::test] async fn uses_custom_description() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "MyChar") { description fields(includeDeprecated: true) { name description - args { + args(includeDeprecated: true) { description } } @@ -1662,7 +1712,7 @@ mod explicit_name_description_and_deprecation { "fields": [{ "name": "myId", "description": "My character ID.", - "args": [{"description": "My argument."}], + "args": [{"description": "My name."}, {"description": "My age."}], }, { "name": "a", "description": null, @@ -1680,12 +1730,18 @@ mod explicit_name_description_and_deprecation { #[tokio::test] async fn uses_custom_deprecation() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "MyChar") { fields(includeDeprecated: true) { name isDeprecated deprecationReason + args(includeDeprecated: true) { + name + isDeprecated + deprecationReason + } } } }"#; @@ -1700,14 +1756,25 @@ mod explicit_name_description_and_deprecation { "name": "myId", "isDeprecated": true, "deprecationReason": "Not used.", + "args": [{ + "name": "myName", + "isDeprecated": true, + "deprecationReason": null, + }, { + "name": "myAge", + "isDeprecated": true, + "deprecationReason": "Because.", + }], }, { "name": "a", "isDeprecated": true, "deprecationReason": null, + "args": [], }, { "name": "b", "isDeprecated": false, "deprecationReason": null, + "args": [], }], }}), vec![], @@ -1756,6 +1823,7 @@ mod renamed_all_fields_and_args { #[tokio::test] async fn resolves_fields() { + // language=GraphQL const DOC: &str = r#"{ character { id @@ -1781,6 +1849,7 @@ mod renamed_all_fields_and_args { #[tokio::test] async fn uses_correct_fields_and_args_names() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { fields { @@ -1866,6 +1935,7 @@ mod explicit_scalar { #[tokio::test] async fn resolves_human() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Human { @@ -1891,6 +1961,7 @@ mod explicit_scalar { #[tokio::test] async fn resolves_droid() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Droid { @@ -1916,6 +1987,7 @@ mod explicit_scalar { #[tokio::test] async fn resolves_id_field() { + // language=GraphQL const DOC: &str = r#"{ character { id @@ -1995,6 +2067,7 @@ mod custom_scalar { #[tokio::test] async fn resolves_human() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Human { @@ -2020,6 +2093,7 @@ mod custom_scalar { #[tokio::test] async fn resolves_droid() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Droid { @@ -2045,6 +2119,7 @@ mod custom_scalar { #[tokio::test] async fn resolves_id_field() { + // language=GraphQL const DOC: &str = r#"{ character { id @@ -2122,6 +2197,7 @@ mod explicit_generic_scalar { #[tokio::test] async fn resolves_human() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Human { @@ -2147,6 +2223,7 @@ mod explicit_generic_scalar { #[tokio::test] async fn resolves_droid() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Droid { @@ -2172,6 +2249,7 @@ mod explicit_generic_scalar { #[tokio::test] async fn resolves_id_field() { + // language=GraphQL const DOC: &str = r#"{ character { id @@ -2249,6 +2327,7 @@ mod bounded_generic_scalar { #[tokio::test] async fn resolves_human() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Human { @@ -2274,6 +2353,7 @@ mod bounded_generic_scalar { #[tokio::test] async fn resolves_droid() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Droid { @@ -2299,6 +2379,7 @@ mod bounded_generic_scalar { #[tokio::test] async fn resolves_id_field() { + // language=GraphQL const DOC: &str = r#"{ character { id @@ -2411,6 +2492,7 @@ mod explicit_custom_context { #[tokio::test] async fn resolves_human() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Human { @@ -2436,6 +2518,7 @@ mod explicit_custom_context { #[tokio::test] async fn resolves_droid() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Droid { @@ -2461,6 +2544,7 @@ mod explicit_custom_context { #[tokio::test] async fn resolves_fields() { + // language=GraphQL const DOC: &str = r#"{ character { id @@ -2566,6 +2650,7 @@ mod inferred_custom_context_from_field { #[tokio::test] async fn resolves_human() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Human { @@ -2592,6 +2677,7 @@ mod inferred_custom_context_from_field { #[tokio::test] async fn resolves_droid() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Droid { @@ -2618,6 +2704,7 @@ mod inferred_custom_context_from_field { #[tokio::test] async fn resolves_fields() { + // language=GraphQL const DOC: &str = r#"{ character { id @@ -2727,6 +2814,7 @@ mod executor { #[tokio::test] async fn resolves_human() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Human { @@ -2752,6 +2840,7 @@ mod executor { #[tokio::test] async fn resolves_droid() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Droid { @@ -2777,6 +2866,7 @@ mod executor { #[tokio::test] async fn resolves_fields() { + // language=GraphQL const DOC: &str = r#"{ character { id @@ -2802,6 +2892,7 @@ mod executor { #[tokio::test] async fn not_arg() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { fields { @@ -2866,6 +2957,7 @@ mod ignored_method { #[tokio::test] async fn resolves_human() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Human { @@ -2891,6 +2983,7 @@ mod ignored_method { #[tokio::test] async fn resolves_id_field() { + // language=GraphQL const DOC: &str = r#"{ character { id @@ -2907,6 +3000,7 @@ mod ignored_method { #[tokio::test] async fn is_not_field() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Character") { fields { @@ -2984,6 +3078,7 @@ mod field_return_subtyping { #[tokio::test] async fn resolves_human() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Human { @@ -3009,6 +3104,7 @@ mod field_return_subtyping { #[tokio::test] async fn resolves_droid() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Droid { @@ -3034,6 +3130,7 @@ mod field_return_subtyping { #[tokio::test] async fn resolves_id_field() { + // language=GraphQL const DOC: &str = r#"{ character { id @@ -3140,6 +3237,7 @@ mod field_return_union_subtyping { #[tokio::test] async fn resolves_human() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Human { @@ -3169,6 +3267,7 @@ mod field_return_union_subtyping { #[tokio::test] async fn resolves_droid() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Droid { @@ -3198,6 +3297,7 @@ mod field_return_union_subtyping { #[tokio::test] async fn resolves_fields() { + // language=GraphQL const DOC: &str = r#"{ character { id @@ -3293,6 +3393,7 @@ mod nullable_argument_subtyping { #[tokio::test] async fn resolves_human() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Human { @@ -3318,6 +3419,7 @@ mod nullable_argument_subtyping { #[tokio::test] async fn resolves_droid() { + // language=GraphQL const DOC: &str = r#"{ character { ... on Droid { @@ -3343,6 +3445,7 @@ mod nullable_argument_subtyping { #[tokio::test] async fn resolves_id_field() { + // language=GraphQL const DOC: &str = r#"{ character { id @@ -3427,6 +3530,7 @@ mod simple_subtyping { #[tokio::test] async fn resolves_node() { + // language=GraphQL const DOC: &str = r#"{ node { id @@ -3443,6 +3547,7 @@ mod simple_subtyping { #[tokio::test] async fn resolves_node_on_resource() { + // language=GraphQL const DOC: &str = r#"{ node { ... on Resource { @@ -3468,6 +3573,7 @@ mod simple_subtyping { #[tokio::test] async fn resolves_node_on_endpoint() { + // language=GraphQL const DOC: &str = r#"{ node { ... on Endpoint { @@ -3493,6 +3599,7 @@ mod simple_subtyping { #[tokio::test] async fn resolves_resource() { + // language=GraphQL const DOC: &str = r#"{ resource { id @@ -3516,6 +3623,7 @@ mod simple_subtyping { #[tokio::test] async fn resolves_resource_on_endpoint() { + // language=GraphQL const DOC: &str = r#"{ resource { ... on Endpoint { @@ -3717,6 +3825,7 @@ mod branching_subtyping { #[tokio::test] async fn resolves_human_connection() { + // language=GraphQL const DOC: &str = r#"{ crew { ... on HumanConnection { @@ -3746,6 +3855,7 @@ mod branching_subtyping { #[tokio::test] async fn resolves_human() { + // language=GraphQL const DOC: &str = r#"{ crew { nodes { @@ -3775,6 +3885,7 @@ mod branching_subtyping { #[tokio::test] async fn resolves_luke() { + // language=GraphQL const DOC: &str = r#"{ crew { nodes { @@ -3806,6 +3917,7 @@ mod branching_subtyping { #[tokio::test] async fn resolves_droid_connection() { + // language=GraphQL const DOC: &str = r#"{ crew { ... on DroidConnection { @@ -3835,6 +3947,7 @@ mod branching_subtyping { #[tokio::test] async fn resolves_droid() { + // language=GraphQL const DOC: &str = r#"{ crew { nodes { @@ -3864,6 +3977,7 @@ mod branching_subtyping { #[tokio::test] async fn resolves_r2d2() { + // language=GraphQL const DOC: &str = r#"{ crew { nodes { diff --git a/tests/integration/tests/codegen_interface_derive.rs b/tests/integration/tests/codegen_interface_derive.rs index acc934645..4f18186a6 100644 --- a/tests/integration/tests/codegen_interface_derive.rs +++ b/tests/integration/tests/codegen_interface_derive.rs @@ -1092,7 +1092,7 @@ mod description_from_doc_comment { } } -mod deprecation_from_attr { +mod field_deprecation_from_rust_attr { use super::*; #[derive(GraphQLInterface)] diff --git a/tests/integration/tests/codegen_object_attr.rs b/tests/integration/tests/codegen_object_attr.rs index cfefdff26..aa7622649 100644 --- a/tests/integration/tests/codegen_object_attr.rs +++ b/tests/integration/tests/codegen_object_attr.rs @@ -39,6 +39,7 @@ mod trivial { #[tokio::test] async fn resolves() { + // language=GraphQL const DOC: &str = r#"{ human { id @@ -55,6 +56,7 @@ mod trivial { #[tokio::test] async fn is_graphql_object() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Human") { kind @@ -71,6 +73,7 @@ mod trivial { #[tokio::test] async fn uses_type_name() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Human") { name @@ -87,6 +90,7 @@ mod trivial { #[tokio::test] async fn has_no_description() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Human") { description @@ -127,6 +131,7 @@ mod trivial_async { #[tokio::test] async fn resolves() { + // language=GraphQL const DOC: &str = r#"{ human { id @@ -143,6 +148,7 @@ mod trivial_async { #[tokio::test] async fn is_graphql_object() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Human") { kind @@ -159,6 +165,7 @@ mod trivial_async { #[tokio::test] async fn uses_type_name() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Human") { name @@ -175,6 +182,7 @@ mod trivial_async { #[tokio::test] async fn has_no_description() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Human") { description @@ -217,6 +225,7 @@ mod raw_method { #[tokio::test] async fn resolves() { + // language=GraphQL const DOC: &str = r#"{ human { myId @@ -237,6 +246,7 @@ mod raw_method { #[tokio::test] async fn has_correct_name() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Human") { name @@ -292,6 +302,7 @@ mod ignored_method { #[tokio::test] async fn resolves() { + // language=GraphQL const DOC: &str = r#"{ human { id @@ -308,6 +319,7 @@ mod ignored_method { #[tokio::test] async fn is_not_field() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Human") { fields { @@ -368,6 +380,7 @@ mod fallible_method { #[tokio::test] async fn resolves() { + // language=GraphQL const DOC: &str = r#"{ human { id @@ -388,6 +401,7 @@ mod fallible_method { #[tokio::test] async fn has_correct_graphql_type() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Human") { name @@ -470,6 +484,7 @@ mod generic { #[tokio::test] async fn resolves_human() { + // language=GraphQL const DOC: &str = r#"{ human { id @@ -486,6 +501,7 @@ mod generic { #[tokio::test] async fn resolves_human_string() { + // language=GraphQL const DOC: &str = r#"{ humanString { id @@ -502,6 +518,7 @@ mod generic { #[tokio::test] async fn uses_type_name_without_type_params() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Human") { name @@ -561,6 +578,7 @@ mod generic_async { #[tokio::test] async fn resolves_human() { + // language=GraphQL const DOC: &str = r#"{ human { id @@ -577,6 +595,7 @@ mod generic_async { #[tokio::test] async fn resolves_human_string() { + // language=GraphQL const DOC: &str = r#"{ humanString { id @@ -593,6 +612,7 @@ mod generic_async { #[tokio::test] async fn uses_type_name_without_type_params() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Human") { name @@ -660,6 +680,7 @@ mod generic_lifetime_async { #[tokio::test] async fn resolves_human() { + // language=GraphQL const DOC: &str = r#"{ human { id @@ -680,6 +701,7 @@ mod generic_lifetime_async { #[tokio::test] async fn resolves_human_string() { + // language=GraphQL const DOC: &str = r#"{ humanString { id @@ -700,6 +722,7 @@ mod generic_lifetime_async { #[tokio::test] async fn uses_type_name_without_type_params() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Human") { name @@ -808,6 +831,7 @@ mod nested_generic_lifetime_async { #[tokio::test] async fn resolves_human() { + // language=GraphQL const DOC: &str = r#"{ human { id @@ -839,6 +863,7 @@ mod nested_generic_lifetime_async { #[tokio::test] async fn resolves_human_string() { + // language=GraphQL const DOC: &str = r#"{ humanString { id @@ -920,6 +945,7 @@ mod argument { #[tokio::test] async fn resolves() { + // language=GraphQL const DOC: &str = r#"{ human { id(arg: "human-32") @@ -940,6 +966,7 @@ mod argument { #[tokio::test] async fn has_correct_name() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Human") { fields { @@ -967,6 +994,7 @@ mod argument { #[tokio::test] async fn has_no_description() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Human") { fields { @@ -993,6 +1021,7 @@ mod argument { #[tokio::test] async fn has_no_defaults() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Human") { fields { @@ -1142,6 +1171,7 @@ mod default_argument { #[tokio::test] async fn has_defaults() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Human") { fields { @@ -1216,6 +1246,7 @@ mod description_from_doc_comment { #[tokio::test] async fn uses_doc_comment_as_description() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Human") { description @@ -1240,7 +1271,7 @@ mod description_from_doc_comment { } } -mod deprecation_from_attr { +mod field_deprecation_from_rust_attr { use super::*; struct Human; @@ -1273,6 +1304,7 @@ mod deprecation_from_attr { #[tokio::test] async fn resolves_id_field() { + // language=GraphQL const DOC: &str = r#"{ human { id @@ -1289,6 +1321,7 @@ mod deprecation_from_attr { #[tokio::test] async fn resolves_deprecated_fields() { + // language=GraphQL const DOC: &str = r#"{ human { a @@ -1306,6 +1339,7 @@ mod deprecation_from_attr { #[tokio::test] async fn deprecates_fields() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Human") { fields(includeDeprecated: true) { @@ -1332,6 +1366,7 @@ mod deprecation_from_attr { #[tokio::test] async fn provides_deprecation_reason() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Human") { fields(includeDeprecated: true) { @@ -1369,7 +1404,9 @@ mod explicit_name_description_and_deprecation { #[graphql(name = "myId", desc = "My human ID.", deprecated = "Not used.")] #[deprecated(note = "Should be omitted.")] fn id( - #[graphql(name = "myName", desc = "My argument.", default)] _n: prelude::String, + #[graphql(name = "myName", desc = "My name.", default, deprecated)] _n: prelude::String, + #[graphql(name = "myAge", desc = "My age.", deprecated = "No `age` anymore.")] + _a: prelude::Option, ) -> &'static str { "human-32" } @@ -1396,6 +1433,7 @@ mod explicit_name_description_and_deprecation { #[tokio::test] async fn resolves_fields() { + // language=GraphQL const DOC: &str = r#"{ human { myId @@ -1417,12 +1455,13 @@ mod explicit_name_description_and_deprecation { #[tokio::test] async fn uses_custom_name() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "MyHuman") { name fields(includeDeprecated: true) { name - args { + args(includeDeprecated: true) { name } } @@ -1437,7 +1476,7 @@ mod explicit_name_description_and_deprecation { graphql_value!({"__type": { "name": "MyHuman", "fields": [ - {"name": "myId", "args": [{"name": "myName"}]}, + {"name": "myId", "args": [{"name": "myName"}, {"name": "myAge"}]}, {"name": "a", "args": []}, {"name": "b", "args": []}, ], @@ -1449,13 +1488,14 @@ mod explicit_name_description_and_deprecation { #[tokio::test] async fn uses_custom_description() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "MyHuman") { description fields(includeDeprecated: true) { name description - args { + args(includeDeprecated: true) { description } } @@ -1472,7 +1512,7 @@ mod explicit_name_description_and_deprecation { "fields": [{ "name": "myId", "description": "My human ID.", - "args": [{"description": "My argument."}], + "args": [{"description": "My name."}, {"description": "My age."}], }, { "name": "a", "description": null, @@ -1490,12 +1530,18 @@ mod explicit_name_description_and_deprecation { #[tokio::test] async fn uses_custom_deprecation() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "MyHuman") { fields(includeDeprecated: true) { name isDeprecated deprecationReason + args(includeDeprecated: true) { + name + isDeprecated + deprecationReason + } } } }"#; @@ -1510,14 +1556,25 @@ mod explicit_name_description_and_deprecation { "name": "myId", "isDeprecated": true, "deprecationReason": "Not used.", + "args": [{ + "name": "myName", + "isDeprecated": true, + "deprecationReason": null, + }, { + "name": "myAge", + "isDeprecated": true, + "deprecationReason": "No `age` anymore.", + }], }, { "name": "a", "isDeprecated": true, "deprecationReason": null, + "args": [], }, { "name": "b", "isDeprecated": false, "deprecationReason": null, + "args": [], }], }}), vec![], @@ -1557,6 +1614,7 @@ mod renamed_all_fields_and_args { #[tokio::test] async fn resolves_fields() { + // language=GraphQL const DOC: &str = r#"{ human { id @@ -1582,6 +1640,7 @@ mod renamed_all_fields_and_args { #[tokio::test] async fn uses_correct_fields_and_args_names() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Human") { fields { @@ -1636,6 +1695,7 @@ mod explicit_scalar { #[tokio::test] async fn resolves_fields() { + // language=GraphQL const DOC: &str = r#"{ human { id @@ -1684,6 +1744,7 @@ mod custom_scalar { #[tokio::test] async fn resolves_fields() { + // language=GraphQL const DOC: &str = r#"{ human { id @@ -1732,6 +1793,7 @@ mod explicit_generic_scalar { #[tokio::test] async fn resolves_fields() { + // language=GraphQL const DOC: &str = r#"{ human { id @@ -1783,6 +1845,7 @@ mod bounded_generic_scalar { #[tokio::test] async fn resolves_fields() { + // language=GraphQL const DOC: &str = r#"{ human { id @@ -1842,6 +1905,7 @@ mod explicit_custom_context { #[tokio::test] async fn resolves_fields() { + // language=GraphQL const DOC: &str = r#"{ human { id @@ -1902,6 +1966,7 @@ mod inferred_custom_context_from_field { #[tokio::test] async fn resolves_fields() { + // language=GraphQL const DOC: &str = r#"{ human { id @@ -1965,6 +2030,7 @@ mod executor { #[tokio::test] async fn resolves_fields() { + // language=GraphQL const DOC: &str = r#"{ human { id @@ -1990,6 +2056,7 @@ mod executor { #[tokio::test] async fn not_arg() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Human") { fields { @@ -2072,6 +2139,7 @@ mod switched_context { #[tokio::test] async fn resolves_fields() { + // language=GraphQL const DOC: &str = r#"{ human { switchAlways { id } @@ -2100,6 +2168,7 @@ mod switched_context { #[tokio::test] async fn uses_correct_fields_types() { + // language=GraphQL const DOC: &str = r#"{ __type(name: "Human") { fields { diff --git a/tests/integration/tests/codegen_object_derive.rs b/tests/integration/tests/codegen_object_derive.rs index ead4f9f91..02713a55c 100644 --- a/tests/integration/tests/codegen_object_derive.rs +++ b/tests/integration/tests/codegen_object_derive.rs @@ -456,7 +456,7 @@ mod description_from_doc_comment { } } -mod deprecation_from_attr { +mod field_deprecation_from_rust_attr { use super::*; #[derive(GraphQLObject)] diff --git a/tests/integration/tests/codegen_scalar_attr_derive_input.rs b/tests/integration/tests/codegen_scalar_attr_derive_input.rs index 1014adec9..3faee7868 100644 --- a/tests/integration/tests/codegen_scalar_attr_derive_input.rs +++ b/tests/integration/tests/codegen_scalar_attr_derive_input.rs @@ -563,7 +563,7 @@ mod where_attribute { async fn has_specified_by_url() { const DOC: &str = r#"{ __type(name: "CustomDateTime") { - specifiedByUrl + specifiedByURL } }"#; @@ -572,7 +572,7 @@ mod where_attribute { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}), + graphql_value!({"__type": {"specifiedByURL": "https://tools.ietf.org/html/rfc3339"}}), vec![], )), ); @@ -718,7 +718,7 @@ mod with_module { async fn has_specified_by_url() { const DOC: &str = r#"{ __type(name: "CustomDateTime") { - specifiedByUrl + specifiedByURL } }"#; @@ -727,7 +727,7 @@ mod with_module { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}), + graphql_value!({"__type": {"specifiedByURL": "https://tools.ietf.org/html/rfc3339"}}), vec![], )), ); diff --git a/tests/integration/tests/codegen_scalar_attr_type_alias.rs b/tests/integration/tests/codegen_scalar_attr_type_alias.rs index 0f695a292..9d3685ddd 100644 --- a/tests/integration/tests/codegen_scalar_attr_type_alias.rs +++ b/tests/integration/tests/codegen_scalar_attr_type_alias.rs @@ -387,7 +387,7 @@ mod where_attribute { async fn has_specified_by_url() { const DOC: &str = r#"{ __type(name: "CustomDateTime") { - specifiedByUrl + specifiedByURL } }"#; @@ -396,7 +396,7 @@ mod where_attribute { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}), + graphql_value!({"__type": {"specifiedByURL": "https://tools.ietf.org/html/rfc3339"}}), vec![], )), ); @@ -546,7 +546,7 @@ mod with_module { async fn has_specified_by_url() { const DOC: &str = r#"{ __type(name: "CustomDateTime") { - specifiedByUrl + specifiedByURL } }"#; @@ -555,7 +555,7 @@ mod with_module { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}), + graphql_value!({"__type": {"specifiedByURL": "https://tools.ietf.org/html/rfc3339"}}), vec![], )), ); diff --git a/tests/integration/tests/codegen_scalar_derive.rs b/tests/integration/tests/codegen_scalar_derive.rs index ae37557e4..a21d3461a 100644 --- a/tests/integration/tests/codegen_scalar_derive.rs +++ b/tests/integration/tests/codegen_scalar_derive.rs @@ -558,7 +558,7 @@ mod where_attribute { async fn has_specified_by_url() { const DOC: &str = r#"{ __type(name: "CustomDateTime") { - specifiedByUrl + specifiedByURL } }"#; @@ -567,7 +567,7 @@ mod where_attribute { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}), + graphql_value!({"__type": {"specifiedByURL": "https://tools.ietf.org/html/rfc3339"}}), vec![], )), ); @@ -713,7 +713,7 @@ mod with_module { async fn has_specified_by_url() { const DOC: &str = r#"{ __type(name: "CustomDateTime") { - specifiedByUrl + specifiedByURL } }"#; @@ -722,7 +722,7 @@ mod with_module { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc3339"}}), + graphql_value!({"__type": {"specifiedByURL": "https://tools.ietf.org/html/rfc3339"}}), vec![], )), ); diff --git a/tests/integration/tests/codegen_subscription_attr.rs b/tests/integration/tests/codegen_subscription_attr.rs index e13d4fdd9..1c7082079 100644 --- a/tests/integration/tests/codegen_subscription_attr.rs +++ b/tests/integration/tests/codegen_subscription_attr.rs @@ -905,7 +905,7 @@ mod description_from_doc_comment { } } -mod deprecation_from_attr { +mod field_deprecation_from_rust_attr { use super::*; struct Human;