From 91bb9e81fb8d6c86d26d9f04ee325a6433f802da Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 28 Oct 2021 15:21:42 +0800 Subject: [PATCH 1/7] Specified By - [GraphQL - October 2021] #677 --- derive/src/args.rs | 3 +++ derive/src/newtype.rs | 6 ++++++ derive/src/scalar.rs | 6 ++++++ src/lib.rs | 6 ++++-- src/model/type.rs | 12 ++++++++++++ src/registry/mod.rs | 1 + src/resolver_utils/scalar.rs | 36 ++++++++++++++++++++++++++++++++---- src/types/json.rs | 1 + src/types/upload.rs | 1 + tests/scalar.rs | 10 ++++++++-- 10 files changed, 74 insertions(+), 8 deletions(-) diff --git a/derive/src/args.rs b/derive/src/args.rs index e2ba5e86..4985953a 100644 --- a/derive/src/args.rs +++ b/derive/src/args.rs @@ -446,6 +446,7 @@ pub struct Scalar { pub name: Option, pub use_type_description: bool, pub visible: Option, + pub specified_by_url: Option, } #[derive(FromMeta, Default)] @@ -652,6 +653,8 @@ pub struct NewType { pub name: NewTypeName, #[darling(default)] pub visible: Option, + #[darling(default)] + pub specified_by_url: Option, } #[derive(FromMeta, Default)] diff --git a/derive/src/newtype.rs b/derive/src/newtype.rs index cc00ceec..d15ee1f5 100644 --- a/derive/src/newtype.rs +++ b/derive/src/newtype.rs @@ -38,12 +38,18 @@ pub fn generate(newtype_args: &args::NewType) -> GeneratorResult { None => quote! { <#inner_ty as #crate_name::Type>::type_name() }, }; let create_type_info = if let Some(name) = &gql_typename { + let specified_by_url = match &newtype_args.specified_by_url { + Some(specified_by_url) => quote! { ::std::option::Option::Some(#specified_by_url) }, + None => quote! { ::std::option::Option::None }, + }; + quote! { registry.create_type::<#ident, _>(|_| #crate_name::registry::MetaType::Scalar { name: ::std::borrow::ToOwned::to_owned(#name), description: #desc, is_valid: |value| <#ident as #crate_name::ScalarType>::is_valid(value), visible: #visible, + specified_by_url: #specified_by_url, }) } } else { diff --git a/derive/src/scalar.rs b/derive/src/scalar.rs index 561ee384..3aa55b0a 100644 --- a/derive/src/scalar.rs +++ b/derive/src/scalar.rs @@ -30,6 +30,11 @@ pub fn generate( let generic = &item_impl.generics; let where_clause = &item_impl.generics.where_clause; let visible = visible_fn(&scalar_args.visible); + let specified_by_url = match &scalar_args.specified_by_url { + Some(specified_by_url) => quote! { ::std::option::Option::Some(#specified_by_url) }, + None => quote! { ::std::option::Option::None }, + }; + let expanded = quote! { #item_impl @@ -45,6 +50,7 @@ pub fn generate( description: #desc, is_valid: |value| <#self_ty as #crate_name::ScalarType>::is_valid(value), visible: #visible, + specified_by_url: #specified_by_url, }) } } diff --git a/src/lib.rs b/src/lib.rs index 1d80fb24..4141c381 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -987,6 +987,7 @@ pub use async_graphql_derive::Subscription; /// | Attribute | description | Type | Optional | /// |-------------|---------------------------|----------|----------| /// | name | Scalar name | string | Y | +/// | specified_by_url | Provide a specification URL for this scalar type, it must link to a human-readable specification of the data format, serialization and coercion rules for this scalar. | string | Y | /// pub use async_graphql_derive::Scalar; @@ -1000,8 +1001,9 @@ pub use async_graphql_derive::Scalar; /// |-------------|---------------------------|----------|----------| /// | name | If this attribute is provided then define a new scalar, otherwise it is just a transparent proxy for the internal scalar. | string | Y | /// | name | If this attribute is provided then define a new scalar, otherwise it is just a transparent proxy for the internal scalar. | bool | Y | -/// | visible(Only valid for new scalars.) | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | -/// | visible(Only valid for new scalars.) | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | +/// | visible(Only valid for new scalars) | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y | +/// | visible(Only valid for new scalars) | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | +/// | specified_by_url(Only valid for new scalars) | Provide a specification URL for this scalar type, it must link to a human-readable specification of the data format, serialization and coercion rules for this scalar. | string | Y | /// /// # Examples /// diff --git a/src/model/type.rs b/src/model/type.rs index 4f20506c..b8ddfdb9 100644 --- a/src/model/type.rs +++ b/src/model/type.rs @@ -218,4 +218,16 @@ impl<'a> __Type<'a> { None } } + + #[graphql(name = "specifiedByURL")] + async fn specified_by_url(&self) -> Option<&'a str> { + if let TypeDetail::Named(registry::MetaType::Scalar { + specified_by_url, .. + }) = &self.detail + { + *specified_by_url + } else { + None + } + } } diff --git a/src/registry/mod.rs b/src/registry/mod.rs index 254cae2c..16b08433 100644 --- a/src/registry/mod.rs +++ b/src/registry/mod.rs @@ -189,6 +189,7 @@ pub enum MetaType { description: Option<&'static str>, is_valid: fn(value: &Value) -> bool, visible: Option, + specified_by_url: Option<&'static str>, }, Object { name: String, diff --git a/src/resolver_utils/scalar.rs b/src/resolver_utils/scalar.rs index 2a788345..062547b0 100644 --- a/src/resolver_utils/scalar.rs +++ b/src/resolver_utils/scalar.rs @@ -68,6 +68,9 @@ pub trait ScalarType: Sized + Send { /// // Rename to `MV` and add description. /// // scalar!(MyValue, "MV", "This is my value"); /// +/// // Rename to `MV`, add description and specifiedByURL. +/// // scalar!(MyValue, "MV", "This is my value", "https://tools.ietf.org/html/rfc4122"); +/// /// struct Query; /// /// #[Object] @@ -92,23 +95,47 @@ pub trait ScalarType: Sized + Send { /// ``` #[macro_export] macro_rules! scalar { + ($ty:ty, $name:literal, $desc:literal, $specified_by_url:literal) => { + $crate::scalar_internal!( + $ty, + $name, + ::std::option::Option::Some($desc), + ::std::option::Option::Some($specified_by_url) + ); + }; + ($ty:ty, $name:literal, $desc:literal) => { - $crate::scalar_internal!($ty, $name, ::std::option::Option::Some($desc)); + $crate::scalar_internal!( + $ty, + $name, + ::std::option::Option::Some($desc), + ::std::option::Option::None + ); }; ($ty:ty, $name:literal) => { - $crate::scalar_internal!($ty, $name, ::std::option::Option::None); + $crate::scalar_internal!( + $ty, + $name, + ::std::option::Option::None, + ::std::option::Option::None + ); }; ($ty:ty) => { - $crate::scalar_internal!($ty, ::std::stringify!($ty), ::std::option::Option::None); + $crate::scalar_internal!( + $ty, + ::std::stringify!($ty), + ::std::option::Option::None, + ::std::option::Option::None + ); }; } #[macro_export] #[doc(hidden)] macro_rules! scalar_internal { - ($ty:ty, $name:expr, $desc:expr) => { + ($ty:ty, $name:expr, $desc:expr, $specified_by_url:expr) => { impl $crate::Type for $ty { fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> { ::std::borrow::Cow::Borrowed($name) @@ -122,6 +149,7 @@ macro_rules! scalar_internal { description: $desc, is_valid: |value| <$ty as $crate::ScalarType>::is_valid(value), visible: ::std::option::Option::None, + specified_by_url: $specified_by_url, }) } } diff --git a/src/types/json.rs b/src/types/json.rs index 065397f9..2444d73f 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -85,6 +85,7 @@ impl Type for OutputJson { description: None, is_valid: |_| true, visible: None, + specified_by_url: None, }) } } diff --git a/src/types/upload.rs b/src/types/upload.rs index 460f8d24..a26e0511 100644 --- a/src/types/upload.rs +++ b/src/types/upload.rs @@ -111,6 +111,7 @@ impl Type for Upload { description: None, is_valid: |value| matches!(value, Value::String(_)), visible: None, + specified_by_url: None, }) } } diff --git a/tests/scalar.rs b/tests/scalar.rs index 414dd109..410d37bb 100644 --- a/tests/scalar.rs +++ b/tests/scalar.rs @@ -11,7 +11,12 @@ mod test_mod { #[tokio::test] pub async fn test_scalar_macro() { - scalar!(test_mod::MyValue, "MV", "DESC"); + scalar!( + test_mod::MyValue, + "MV", + "DESC", + "https://tools.ietf.org/html/rfc4122" + ); struct Query; @@ -26,7 +31,7 @@ pub async fn test_scalar_macro() { let schema = Schema::new(Query, EmptyMutation, EmptySubscription); assert_eq!( schema - .execute(r#"{ __type(name:"MV") { name description } }"#) + .execute(r#"{ __type(name:"MV") { name description specifiedByURL } }"#) .await .into_result() .unwrap() @@ -35,6 +40,7 @@ pub async fn test_scalar_macro() { "__type": { "name": "MV", "description": "DESC", + "specifiedByURL": "https://tools.ietf.org/html/rfc4122", } }) ); From a2a419430bc0f889a3bdf1aba4c474400226c85a Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 28 Oct 2021 16:02:51 +0800 Subject: [PATCH 2/7] Allow directive on variable definition - [GraphQL - October 2021] #678 --- parser/src/graphql.pest | 3 +- parser/src/parse/executable.rs | 3 ++ parser/src/parse/service.rs | 1 + parser/src/pos.rs | 2 +- parser/src/types/executable.rs | 22 ++++++------ parser/src/types/mod.rs | 10 +++--- parser/src/types/service.rs | 36 ++++++++++--------- .../executables/variable_directive.graphql | 3 ++ parser/tests/services/directive.graphql | 2 ++ 9 files changed, 48 insertions(+), 34 deletions(-) create mode 100644 parser/tests/executables/variable_directive.graphql diff --git a/parser/src/graphql.pest b/parser/src/graphql.pest index 1a7fcd5b..15fb2fc6 100644 --- a/parser/src/graphql.pest +++ b/parser/src/graphql.pest @@ -10,7 +10,7 @@ executable_definition = { operation_definition | fragment_definition } operation_definition = { named_operation_definition | selection_set } named_operation_definition = { operation_type ~ name? ~ variable_definitions? ~ directives? ~ selection_set } variable_definitions = { "(" ~ variable_definition* ~ ")" } -variable_definition = { variable ~ ":" ~ type_ ~ default_value? } +variable_definition = { variable ~ ":" ~ type_ ~ directives? ~ default_value? } selection_set = { "{" ~ selection+ ~ "}" } selection = { field | inline_fragment | fragment_spread } @@ -86,6 +86,7 @@ directive_location = { | "FRAGMENT_DEFINITION" | "FRAGMENT_SPREAD" | "INLINE_FRAGMENT" + | "VARIABLE_DEFINITION" | "SCHEMA" | "SCALAR" | "OBJECT" diff --git a/parser/src/parse/executable.rs b/parser/src/parse/executable.rs index 2fc2a1e6..6ec0207d 100644 --- a/parser/src/parse/executable.rs +++ b/parser/src/parse/executable.rs @@ -208,6 +208,8 @@ fn parse_variable_definition( let variable = parse_variable(pairs.next().unwrap(), pc)?; let var_type = parse_type(pairs.next().unwrap(), pc)?; + + let directives = parse_opt_directives(&mut pairs, pc)?; let default_value = parse_if_rule(&mut pairs, Rule::default_value, |pair| { parse_default_value(pair, pc) })?; @@ -218,6 +220,7 @@ fn parse_variable_definition( VariableDefinition { name: variable, var_type, + directives, default_value, }, pos, diff --git a/parser/src/parse/service.rs b/parser/src/parse/service.rs index 9b2c6206..fa71f235 100644 --- a/parser/src/parse/service.rs +++ b/parser/src/parse/service.rs @@ -323,6 +323,7 @@ fn parse_directive_definition( "FRAGMENT_DEFINITION" => DirectiveLocation::FragmentDefinition, "FRAGMENT_SPREAD" => DirectiveLocation::FragmentSpread, "INLINE_FRAGMENT" => DirectiveLocation::InlineFragment, + "VARIABLE_DEFINITION" => DirectiveLocation::VariableDefinition, "SCHEMA" => DirectiveLocation::Schema, "SCALAR" => DirectiveLocation::Scalar, "OBJECT" => DirectiveLocation::Object, diff --git a/parser/src/pos.rs b/parser/src/pos.rs index 3f37e3e7..a52b062f 100644 --- a/parser/src/pos.rs +++ b/parser/src/pos.rs @@ -10,7 +10,7 @@ use std::str::Chars; /// Original position of an element in source code. /// /// You can serialize and deserialize it to the GraphQL `locations` format -/// ([reference](https://spec.graphql.org/June2018/#sec-Errors)). +/// ([reference](https://spec.graphql.org/October2021/#sec-Errors)). #[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Default, Hash, Serialize, Deserialize)] pub struct Pos { /// One-based line number. diff --git a/parser/src/types/executable.rs b/parser/src/types/executable.rs index 88371ac1..0e70b13a 100644 --- a/parser/src/types/executable.rs +++ b/parser/src/types/executable.rs @@ -5,7 +5,7 @@ use async_graphql_value::{ConstValue, Name, Value}; /// An executable GraphQL file or request string. /// -/// [Reference](https://spec.graphql.org/June2018/#ExecutableDocument). +/// [Reference](https://spec.graphql.org/October2021/#ExecutableDocument). #[derive(Debug, Clone)] pub struct ExecutableDocument { /// The operations of the document. @@ -93,7 +93,7 @@ enum OperationsIterInner<'a> { /// A GraphQL operation, such as `mutation($content:String!) { makePost(content: $content) { id } }`. /// -/// [Reference](https://spec.graphql.org/June2018/#OperationDefinition). +/// [Reference](https://spec.graphql.org/October2021/#OperationDefinition). #[derive(Debug, Clone)] pub struct OperationDefinition { /// The type of operation. @@ -108,13 +108,15 @@ pub struct OperationDefinition { /// A variable definition inside a list of variable definitions, for example `$name:String!`. /// -/// [Reference](https://spec.graphql.org/June2018/#VariableDefinition). +/// [Reference](https://spec.graphql.org/October2021/#VariableDefinition). #[derive(Debug, Clone)] pub struct VariableDefinition { /// The name of the variable, without the preceding `$`. pub name: Positioned, /// The type of the variable. pub var_type: Positioned, + /// The variable's directives. + pub directives: Vec>, /// The optional default value of the variable. pub default_value: Option>, } @@ -139,7 +141,7 @@ impl VariableDefinition { /// A set of fields to be selected, for example `{ name age }`. /// -/// [Reference](https://spec.graphql.org/June2018/#SelectionSet). +/// [Reference](https://spec.graphql.org/October2021/#SelectionSet). #[derive(Debug, Default, Clone)] pub struct SelectionSet { /// The fields to be selected. @@ -148,7 +150,7 @@ pub struct SelectionSet { /// A part of an object to be selected; a single field, a fragment spread or an inline fragment. /// -/// [Reference](https://spec.graphql.org/June2018/#Selection). +/// [Reference](https://spec.graphql.org/October2021/#Selection). #[derive(Debug, Clone)] pub enum Selection { /// Select a single field, such as `name` or `weightKilos: weight(unit: KILOGRAMS)`. @@ -182,7 +184,7 @@ impl Selection { /// A field being selected on an object, such as `name` or `weightKilos: weight(unit: KILOGRAMS)`. /// -/// [Reference](https://spec.graphql.org/June2018/#Field). +/// [Reference](https://spec.graphql.org/October2021/#Field). #[derive(Debug, Clone)] pub struct Field { /// The optional field alias. @@ -217,7 +219,7 @@ impl Field { /// A fragment selector, such as `... userFields`. /// -/// [Reference](https://spec.graphql.org/June2018/#FragmentSpread). +/// [Reference](https://spec.graphql.org/October2021/#FragmentSpread). #[derive(Debug, Clone)] pub struct FragmentSpread { /// The name of the fragment being selected. @@ -228,7 +230,7 @@ pub struct FragmentSpread { /// An inline fragment selector, such as `... on User { name }`. /// -/// [Reference](https://spec.graphql.org/June2018/#InlineFragment). +/// [Reference](https://spec.graphql.org/October2021/#InlineFragment). #[derive(Debug, Clone)] pub struct InlineFragment { /// The type condition. @@ -241,7 +243,7 @@ pub struct InlineFragment { /// The definition of a fragment, such as `fragment userFields on User { name age }`. /// -/// [Reference](https://spec.graphql.org/June2018/#FragmentDefinition). +/// [Reference](https://spec.graphql.org/October2021/#FragmentDefinition). #[derive(Debug, Clone)] pub struct FragmentDefinition { /// The type this fragment operates on. @@ -254,7 +256,7 @@ pub struct FragmentDefinition { /// A type a fragment can apply to (`on` followed by the type). /// -/// [Reference](https://spec.graphql.org/June2018/#TypeCondition). +/// [Reference](https://spec.graphql.org/October2021/#TypeCondition). #[derive(Debug, Clone)] pub struct TypeCondition { /// The type this fragment applies to. diff --git a/parser/src/types/mod.rs b/parser/src/types/mod.rs index b52e35d2..151fa94f 100644 --- a/parser/src/types/mod.rs +++ b/parser/src/types/mod.rs @@ -4,7 +4,7 @@ //! [`ServiceDocument`](struct.ServiceDocument.html), representing an executable GraphQL query and a //! GraphQL service respectively. //! -//! This follows the [June 2018 edition of the GraphQL spec](https://spec.graphql.org/June2018/). +//! This follows the [June 2018 edition of the GraphQL spec](https://spec.graphql.org/October2021/). mod executable; mod service; @@ -19,7 +19,7 @@ pub use service::*; /// The type of an operation; `query`, `mutation` or `subscription`. /// -/// [Reference](https://spec.graphql.org/June2018/#OperationType). +/// [Reference](https://spec.graphql.org/October2021/#OperationType). #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum OperationType { /// A query. @@ -42,7 +42,7 @@ impl Display for OperationType { /// A GraphQL type, for example `String` or `[String!]!`. /// -/// [Reference](https://spec.graphql.org/June2018/#Type). +/// [Reference](https://spec.graphql.org/October2021/#Type). #[derive(Debug, PartialEq, Eq, Clone)] pub struct Type { /// The base type. @@ -105,7 +105,7 @@ impl Display for BaseType { /// from [`Directive`](struct.Directive.html) in that it uses [`ConstValue`](enum.ConstValue.html) /// instead of [`Value`](enum.Value.html). /// -/// [Reference](https://spec.graphql.org/June2018/#Directive). +/// [Reference](https://spec.graphql.org/October2021/#Directive). #[derive(Debug, Clone)] pub struct ConstDirective { /// The name of the directive. @@ -140,7 +140,7 @@ impl ConstDirective { /// A GraphQL directive, such as `@deprecated(reason: "Use the other field")`. /// -/// [Reference](https://spec.graphql.org/June2018/#Directive). +/// [Reference](https://spec.graphql.org/October2021/#Directive). #[derive(Debug, Clone)] pub struct Directive { /// The name of the directive. diff --git a/parser/src/types/service.rs b/parser/src/types/service.rs index 2a3e81f5..f5e2bcdb 100644 --- a/parser/src/types/service.rs +++ b/parser/src/types/service.rs @@ -5,7 +5,7 @@ use async_graphql_value::Name; /// A GraphQL file or request string defining a GraphQL service. /// -/// [Reference](https://spec.graphql.org/June2018/#Document). +/// [Reference](https://spec.graphql.org/October2021/#Document). #[derive(Debug, Clone)] pub struct ServiceDocument { /// The definitions of this document. @@ -14,8 +14,8 @@ pub struct ServiceDocument { /// A definition concerning the type system of a GraphQL service. /// -/// [Reference](https://spec.graphql.org/June2018/#TypeSystemDefinition). This enum also covers -/// [extensions](https://spec.graphql.org/June2018/#TypeSystemExtension). +/// [Reference](https://spec.graphql.org/October2021/#TypeSystemDefinition). This enum also covers +/// [extensions](https://spec.graphql.org/October2021/#TypeSystemExtension). #[derive(Debug, Clone)] pub enum TypeSystemDefinition { /// The definition of the schema of the service. @@ -28,8 +28,8 @@ pub enum TypeSystemDefinition { /// The definition of the schema in a GraphQL service. /// -/// [Reference](https://spec.graphql.org/June2018/#SchemaDefinition). This also covers -/// [extensions](https://spec.graphql.org/June2018/#SchemaExtension). +/// [Reference](https://spec.graphql.org/October2021/#SchemaDefinition). This also covers +/// [extensions](https://spec.graphql.org/October2021/#SchemaExtension). #[derive(Debug, Clone)] pub struct SchemaDefinition { /// Whether the schema is an extension of another schema. @@ -46,8 +46,8 @@ pub struct SchemaDefinition { /// The definition of a type in a GraphQL service. /// -/// [Reference](https://spec.graphql.org/June2018/#TypeDefinition). This also covers -/// [extensions](https://spec.graphql.org/June2018/#TypeExtension). +/// [Reference](https://spec.graphql.org/October2021/#TypeDefinition). This also covers +/// [extensions](https://spec.graphql.org/October2021/#TypeExtension). #[derive(Debug, Clone)] pub struct TypeDefinition { /// Whether the type is an extension of another type. @@ -81,7 +81,7 @@ pub enum TypeKind { /// The definition of an object type. /// -/// [Reference](https://spec.graphql.org/June2018/#ObjectType). +/// [Reference](https://spec.graphql.org/October2021/#ObjectType). #[derive(Debug, Clone)] pub struct ObjectType { /// The interfaces implemented by the object. @@ -92,7 +92,7 @@ pub struct ObjectType { /// The definition of a field inside an object or interface. /// -/// [Reference](https://spec.graphql.org/June2018/#FieldDefinition). +/// [Reference](https://spec.graphql.org/October2021/#FieldDefinition). #[derive(Debug, Clone)] pub struct FieldDefinition { /// The description of the field. @@ -109,7 +109,7 @@ pub struct FieldDefinition { /// The definition of an interface type. /// -/// [Reference](https://spec.graphql.org/June2018/#InterfaceType). +/// [Reference](https://spec.graphql.org/October2021/#InterfaceType). #[derive(Debug, Clone)] pub struct InterfaceType { /// The interfaces implemented by the interface. @@ -120,7 +120,7 @@ pub struct InterfaceType { /// The definition of a union type. /// -/// [Reference](https://spec.graphql.org/June2018/#UnionType). +/// [Reference](https://spec.graphql.org/October2021/#UnionType). #[derive(Debug, Clone)] pub struct UnionType { /// The member types of the union. @@ -129,7 +129,7 @@ pub struct UnionType { /// The definition of an enum. /// -/// [Reference](https://spec.graphql.org/June2018/#EnumType). +/// [Reference](https://spec.graphql.org/October2021/#EnumType). #[derive(Debug, Clone)] pub struct EnumType { /// The possible values of the enum. @@ -138,7 +138,7 @@ pub struct EnumType { /// The definition of a value inside an enum. /// -/// [Reference](https://spec.graphql.org/June2018/#EnumValueDefinition). +/// [Reference](https://spec.graphql.org/October2021/#EnumValueDefinition). #[derive(Debug, Clone)] pub struct EnumValueDefinition { /// The description of the argument. @@ -151,7 +151,7 @@ pub struct EnumValueDefinition { /// The definition of an input object. /// -/// [Reference](https://spec.graphql.org/June2018/#InputObjectType). +/// [Reference](https://spec.graphql.org/October2021/#InputObjectType). #[derive(Debug, Clone)] pub struct InputObjectType { /// The fields of the input object. @@ -160,7 +160,7 @@ pub struct InputObjectType { /// The definition of an input value inside the arguments of a field. /// -/// [Reference](https://spec.graphql.org/June2018/#InputValueDefinition). +/// [Reference](https://spec.graphql.org/October2021/#InputValueDefinition). #[derive(Debug, Clone)] pub struct InputValueDefinition { /// The description of the argument. @@ -177,7 +177,7 @@ pub struct InputValueDefinition { /// The definition of a directive in a service. /// -/// [Reference](https://spec.graphql.org/June2018/#DirectiveDefinition). +/// [Reference](https://spec.graphql.org/October2021/#DirectiveDefinition). #[derive(Debug, Clone)] pub struct DirectiveDefinition { /// The description of the directive. @@ -192,7 +192,7 @@ pub struct DirectiveDefinition { /// Where a directive can apply to. /// -/// [Reference](https://spec.graphql.org/June2018/#DirectiveLocation). +/// [Reference](https://spec.graphql.org/October2021/#DirectiveLocation). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DirectiveLocation { /// A [query](enum.OperationType.html#variant.Query) [operation](struct.OperationDefinition.html). @@ -233,4 +233,6 @@ pub enum DirectiveLocation { /// An [input value definition](struct.InputValueDefinition.html) on an input object but not a /// field. InputFieldDefinition, + /// An [variable definition](struct.VariableDefinition.html). + VariableDefinition, } diff --git a/parser/tests/executables/variable_directive.graphql b/parser/tests/executables/variable_directive.graphql new file mode 100644 index 00000000..b6a9d07b --- /dev/null +++ b/parser/tests/executables/variable_directive.graphql @@ -0,0 +1,3 @@ +query Foo($a: Int @directive = 10, $b: Int @directive) { + value +} diff --git a/parser/tests/services/directive.graphql b/parser/tests/services/directive.graphql index 401b6f43..456904bd 100644 --- a/parser/tests/services/directive.graphql +++ b/parser/tests/services/directive.graphql @@ -4,3 +4,5 @@ directive @test1(service: String!) on FIELD_DEFINITION directive @test2(service: String!) on FIELD directive @test3(service: String!) on ENUM_VALUE directive @test4(service: String!) on ENUM + +directive @test5(service: String!) on VARIABLE_DEFINITION From 3306f85a8ab46118da3606d72a40b6e0a4000367 Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 28 Oct 2021 16:56:08 +0800 Subject: [PATCH 3/7] Subscription typename - [GraphQL - October 2021] #681 --- derive/src/merged_object.rs | 1 + derive/src/merged_subscription.rs | 1 + derive/src/object.rs | 2 ++ derive/src/simple_object.rs | 2 ++ derive/src/subscription.rs | 1 + src/registry/mod.rs | 3 +++ src/types/connection/connection_type.rs | 1 + src/types/connection/edge.rs | 1 + src/types/empty_mutation.rs | 1 + src/types/empty_subscription.rs | 1 + src/types/maybe_undefined.rs | 2 +- src/types/merged_object.rs | 1 + src/validation/rules/fields_on_correct_type.rs | 5 +++++ src/validation/test_harness.rs | 14 ++++++++++++-- src/validation/visitor.rs | 11 +++++++++++ 15 files changed, 44 insertions(+), 3 deletions(-) diff --git a/derive/src/merged_object.rs b/derive/src/merged_object.rs index f6ccc3fb..3ea5b313 100644 --- a/derive/src/merged_object.rs +++ b/derive/src/merged_object.rs @@ -90,6 +90,7 @@ pub fn generate(object_args: &args::MergedObject) -> GeneratorResult GeneratorResult GeneratorResult GeneratorResult>, visible: Option, + is_subscription: bool, }, Interface { name: String, @@ -375,6 +376,7 @@ impl Registry { extends: false, keys: None, visible: None, + is_subscription: false, }, ); let ty = f(self); @@ -508,6 +510,7 @@ impl Registry { extends: false, keys: None, visible: None, + is_subscription: false, }, ); diff --git a/src/types/connection/connection_type.rs b/src/types/connection/connection_type.rs index 9080004a..ff0193c6 100644 --- a/src/types/connection/connection_type.rs +++ b/src/types/connection/connection_type.rs @@ -190,6 +190,7 @@ where extends: false, keys: None, visible: None, + is_subscription: false, } }) } diff --git a/src/types/connection/edge.rs b/src/types/connection/edge.rs index f6c893bb..afa69142 100644 --- a/src/types/connection/edge.rs +++ b/src/types/connection/edge.rs @@ -107,6 +107,7 @@ where extends: false, keys: None, visible: None, + is_subscription: false, } }) } diff --git a/src/types/empty_mutation.rs b/src/types/empty_mutation.rs index 481a3cb5..da90a82c 100644 --- a/src/types/empty_mutation.rs +++ b/src/types/empty_mutation.rs @@ -45,6 +45,7 @@ impl Type for EmptyMutation { extends: false, keys: None, visible: None, + is_subscription: false, }) } } diff --git a/src/types/empty_subscription.rs b/src/types/empty_subscription.rs index 0c98e473..10d28950 100644 --- a/src/types/empty_subscription.rs +++ b/src/types/empty_subscription.rs @@ -25,6 +25,7 @@ impl Type for EmptySubscription { extends: false, keys: None, visible: None, + is_subscription: true, }) } } diff --git a/src/types/maybe_undefined.rs b/src/types/maybe_undefined.rs index 81a6048f..b9768cb3 100644 --- a/src/types/maybe_undefined.rs +++ b/src/types/maybe_undefined.rs @@ -6,7 +6,7 @@ use crate::{registry, InputType, InputValueError, InputValueResult, Type, Value} /// Similar to `Option`, but it has three states, `undefined`, `null` and `x`. /// -/// **Reference:** +/// **Reference:** /// /// # Examples /// diff --git a/src/types/merged_object.rs b/src/types/merged_object.rs index c3cb026c..7b236ee4 100644 --- a/src/types/merged_object.rs +++ b/src/types/merged_object.rs @@ -50,6 +50,7 @@ impl Type for MergedObject { extends: false, keys: None, visible: None, + is_subscription: false, } }) } diff --git a/src/validation/rules/fields_on_correct_type.rs b/src/validation/rules/fields_on_correct_type.rs index 343f75f9..1b4ad9ef 100644 --- a/src/validation/rules/fields_on_correct_type.rs +++ b/src/validation/rules/fields_on_correct_type.rs @@ -329,4 +329,9 @@ mod tests { "#, ); } + + #[test] + fn typename_in_subscription_root() { + expect_fails_rule!(factory, "subscription { __typename }"); + } } diff --git a/src/validation/test_harness.rs b/src/validation/test_harness.rs index 30bf611b..c691be7a 100644 --- a/src/validation/test_harness.rs +++ b/src/validation/test_harness.rs @@ -4,6 +4,7 @@ use once_cell::sync::Lazy; +use crate::futures_util::Stream; use crate::parser::types::ExecutableDocument; use crate::validation::visitor::{visit, RuleError, Visitor, VisitorContext}; use crate::*; @@ -345,8 +346,17 @@ impl MutationRoot { } } -static TEST_HARNESS: Lazy> = - Lazy::new(|| Schema::new(QueryRoot, MutationRoot, EmptySubscription)); +pub struct SubscriptionRoot; + +#[Subscription(internal)] +impl SubscriptionRoot { + async fn values(&self) -> impl Stream { + futures_util::stream::once(async move { 10 }) + } +} + +static TEST_HARNESS: Lazy> = + Lazy::new(|| Schema::new(QueryRoot, MutationRoot, SubscriptionRoot)); pub(crate) fn validate<'a, V, F>( doc: &'a ExecutableDocument, diff --git a/src/validation/visitor.rs b/src/validation/visitor.rs index 772d4518..0a6024c7 100644 --- a/src/validation/visitor.rs +++ b/src/validation/visitor.rs @@ -611,6 +611,17 @@ fn visit_selection<'a, V: Visitor<'a>>( visit_field(v, ctx, field); }, ); + } else if ctx.current_type().map(|ty| match ty { + MetaType::Object { + is_subscription, .. + } => *is_subscription, + _ => false, + }) == Some(true) + { + ctx.report_error( + vec![field.pos], + "Unknown field \"__typename\" on type \"Subscription\".", + ); } } Selection::FragmentSpread(fragment_spread) => { From bb0c4624a618c4b9bab336eec8bcc7a186cc9e5d Mon Sep 17 00:00:00 2001 From: Sunli Date: Fri, 29 Oct 2021 14:06:46 +0800 Subject: [PATCH 4/7] Add `specified_by_url` for Tz & DateTime & Url & Uuid scalars --- src/types/external/chrono_tz.rs | 6 +++++- src/types/external/datetime.rs | 18 +++++++++++++++--- src/types/external/url.rs | 2 +- src/types/external/uuid.rs | 6 +++++- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/types/external/chrono_tz.rs b/src/types/external/chrono_tz.rs index 86d16372..fafefbef 100644 --- a/src/types/external/chrono_tz.rs +++ b/src/types/external/chrono_tz.rs @@ -2,7 +2,11 @@ use chrono_tz::Tz; use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; -#[Scalar(internal, name = "TimeZone")] +#[Scalar( + internal, + name = "TimeZone", + specified_by_url = "http://www.iana.org/time-zones" +)] impl ScalarType for Tz { fn parse(value: Value) -> InputValueResult { match value { diff --git a/src/types/external/datetime.rs b/src/types/external/datetime.rs index e56167e3..cdcb2de4 100644 --- a/src/types/external/datetime.rs +++ b/src/types/external/datetime.rs @@ -5,7 +5,11 @@ use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; /// Implement the DateTime scalar /// /// The input/output is a string in RFC3339 format. -#[Scalar(internal, name = "DateTime")] +#[Scalar( + internal, + name = "DateTime", + specified_by_url = "https://datatracker.ietf.org/doc/html/rfc3339" +)] impl ScalarType for DateTime { fn parse(value: Value) -> InputValueResult { match &value { @@ -22,7 +26,11 @@ impl ScalarType for DateTime { /// Implement the DateTime scalar /// /// The input/output is a string in RFC3339 format. -#[Scalar(internal, name = "DateTime")] +#[Scalar( + internal, + name = "DateTime", + specified_by_url = "https://datatracker.ietf.org/doc/html/rfc3339" +)] impl ScalarType for DateTime { fn parse(value: Value) -> InputValueResult { match &value { @@ -39,7 +47,11 @@ impl ScalarType for DateTime { /// Implement the DateTime scalar /// /// The input/output is a string in RFC3339 format. -#[Scalar(internal, name = "DateTime")] +#[Scalar( + internal, + name = "DateTime", + specified_by_url = "https://datatracker.ietf.org/doc/html/rfc3339" +)] impl ScalarType for DateTime { fn parse(value: Value) -> InputValueResult { match &value { diff --git a/src/types/external/url.rs b/src/types/external/url.rs index b9a8dda8..110a6e23 100644 --- a/src/types/external/url.rs +++ b/src/types/external/url.rs @@ -2,7 +2,7 @@ use url::Url; use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; -#[Scalar(internal)] +#[Scalar(internal, specified_by_url = "http://url.spec.whatwg.org/")] /// URL is a String implementing the [URL Standard](http://url.spec.whatwg.org/) impl ScalarType for Url { fn parse(value: Value) -> InputValueResult { diff --git a/src/types/external/uuid.rs b/src/types/external/uuid.rs index 917d4cd2..135c72bd 100644 --- a/src/types/external/uuid.rs +++ b/src/types/external/uuid.rs @@ -2,7 +2,11 @@ use uuid::Uuid; use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; -#[Scalar(internal, name = "UUID")] +#[Scalar( + internal, + name = "UUID", + specified_by_url = "http://tools.ietf.org/html/rfc4122" +)] /// A UUID is a unique 128-bit number, stored as 16 octets. UUIDs are parsed as Strings /// within GraphQL. UUIDs are used to assign unique identifiers to entities without requiring a central /// allocating authority. From e3aeb93872ac1cbcfdc47b2ae946a04e44166ce4 Mon Sep 17 00:00:00 2001 From: Sunli Date: Mon, 1 Nov 2021 15:37:28 +0800 Subject: [PATCH 5/7] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96150677..cf20798c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - Use Rust `2021` edition. +- Subscription typename - [GraphQL - October 2021] [#681](https://github.com/async-graphql/async-graphql/issues/681) +- Allow directive on variable definition - [GraphQL - October 2021] [#678](https://github.com/async-graphql/async-graphql/issues/678) +- Specified By - [GraphQL - October 2021] [#677](https://github.com/async-graphql/async-graphql/issues/677) +- Add `specified_by_url` for `Tz`, `DateTime`, `Url`, `Uuid` and `Upload` scalars. ## [2.10.8] 2021-10-26 From 51321b0e84f00d20234b7d20b52727ce3ec58c74 Mon Sep 17 00:00:00 2001 From: Sunli Date: Tue, 2 Nov 2021 19:37:21 +0800 Subject: [PATCH 6/7] Number value literal lookahead restrictions - [GraphQL - October 2021] #685 --- parser/src/graphql.pest | 5 +++-- parser/src/parse/mod.rs | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/parser/src/graphql.pest b/parser/src/graphql.pest index 15fb2fc6..12cff935 100644 --- a/parser/src/graphql.pest +++ b/parser/src/graphql.pest @@ -116,7 +116,7 @@ value = { variable | number | string | boolean | null | enum_value | variable = { "$" ~ name } -number = @{ float | int } +number = @{ (float | int) ~ !name_start } float = { int ~ ((fractional ~ exponent) | fractional | exponent) } fractional = { "." ~ ASCII_DIGIT+ } exponent = { ("E" | "e") ~ ("+" | "-")? ~ ASCII_DIGIT+ } @@ -165,4 +165,5 @@ arguments = { "(" ~ argument+ ~ ")" } const_argument = { name ~ ":" ~ const_value } argument = { name ~ ":" ~ value } -name = @{ (ASCII_ALPHA | "_") ~ (ASCII_ALPHA | ASCII_DIGIT | "_")* } +name_start = @{ (ASCII_ALPHA | "_") } +name = @{ name_start ~ (ASCII_ALPHA | ASCII_DIGIT | "_")* } diff --git a/parser/src/parse/mod.rs b/parser/src/parse/mod.rs index 54c10963..3a3e4259 100644 --- a/parser/src/parse/mod.rs +++ b/parser/src/parse/mod.rs @@ -312,3 +312,21 @@ fn parse_name(pair: Pair, pc: &mut PositionCalculator) -> Result Date: Tue, 2 Nov 2021 19:58:23 +0800 Subject: [PATCH 7/7] Add `specified_by_url` for Upload --- src/types/upload.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/upload.rs b/src/types/upload.rs index a26e0511..c6ef0fde 100644 --- a/src/types/upload.rs +++ b/src/types/upload.rs @@ -111,7 +111,7 @@ impl Type for Upload { description: None, is_valid: |value| matches!(value, Value::String(_)), visible: None, - specified_by_url: None, + specified_by_url: Some("https://github.com/jaydenseric/graphql-multipart-request-spec"), }) } }