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 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/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 { 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/object.rs b/derive/src/object.rs index 665a9976..94fa4e89 100644 --- a/derive/src/object.rs +++ b/derive/src/object.rs @@ -563,6 +563,7 @@ pub fn generate( extends: #extends, keys: ::std::option::Option::None, visible: #visible, + is_subscription: false, }); #(#create_entity_types)* #(#add_keys)* @@ -630,6 +631,7 @@ pub fn generate( extends: #extends, keys: ::std::option::Option::None, visible: #visible, + is_subscription: false, }); #(#create_entity_types)* #(#add_keys)* 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/derive/src/simple_object.rs b/derive/src/simple_object.rs index a170749d..a603e8cb 100644 --- a/derive/src/simple_object.rs +++ b/derive/src/simple_object.rs @@ -293,6 +293,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult GeneratorResult, pc: &mut PositionCalculator) -> Result 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 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..efbd4c9f 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, @@ -198,6 +199,7 @@ pub enum MetaType { extends: bool, keys: Option>, visible: Option, + is_subscription: bool, }, Interface { name: String, @@ -374,6 +376,7 @@ impl Registry { extends: false, keys: None, visible: None, + is_subscription: false, }, ); let ty = f(self); @@ -507,6 +510,7 @@ impl Registry { extends: false, keys: None, visible: None, + is_subscription: false, }, ); 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/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/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. 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/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/types/upload.rs b/src/types/upload.rs index 460f8d24..c6ef0fde 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: Some("https://github.com/jaydenseric/graphql-multipart-request-spec"), }) } } 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) => { 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", } }) );