Merge branch 'graphql-2021'

This commit is contained in:
Sunli 2021-11-03 15:32:49 +08:00
commit 4793c362c3
39 changed files with 217 additions and 53 deletions

View File

@ -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<Tz>`, `Url`, `Uuid` and `Upload` scalars.
## [2.10.8] 2021-10-26

View File

@ -446,6 +446,7 @@ pub struct Scalar {
pub name: Option<String>,
pub use_type_description: bool,
pub visible: Option<Visible>,
pub specified_by_url: Option<String>,
}
#[derive(FromMeta, Default)]
@ -652,6 +653,8 @@ pub struct NewType {
pub name: NewTypeName,
#[darling(default)]
pub visible: Option<Visible>,
#[darling(default)]
pub specified_by_url: Option<String>,
}
#[derive(FromMeta, Default)]

View File

@ -90,6 +90,7 @@ pub fn generate(object_args: &args::MergedObject) -> GeneratorResult<TokenStream
extends: #extends,
keys: ::std::option::Option::None,
visible: #visible,
is_subscription: false,
}
})
}

View File

@ -72,6 +72,7 @@ pub fn generate(object_args: &args::MergedSubscription) -> GeneratorResult<Token
extends: #extends,
keys: ::std::option::Option::None,
visible: #visible,
is_subscription: true,
}
})
}

View File

@ -38,12 +38,18 @@ pub fn generate(newtype_args: &args::NewType) -> GeneratorResult<TokenStream> {
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 {

View File

@ -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)*

View File

@ -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,
})
}
}

View File

@ -293,6 +293,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
extends: #extends,
keys: ::std::option::Option::None,
visible: #visible,
is_subscription: false,
})
}
}
@ -343,6 +344,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
extends: #extends,
keys: ::std::option::Option::None,
visible: #visible,
is_subscription: false,
})
}

View File

@ -456,6 +456,7 @@ pub fn generate(
extends: #extends,
keys: ::std::option::Option::None,
visible: ::std::option::Option::None,
is_subscription: true,
})
}
}

View File

@ -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"
@ -115,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+ }
@ -164,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 | "_")* }

View File

@ -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,

View File

@ -312,3 +312,21 @@ fn parse_name(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positione
debug_assert_eq!(pair.as_rule(), Rule::name);
Ok(Positioned::new(Name::new(pair.as_str()), pc.step(&pair)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_number_lookahead_restrictions() {
GraphQLParser::parse(Rule::const_list, "[123 abc]").unwrap();
GraphQLParser::parse(Rule::const_list, "[123.0123 abc]").unwrap();
GraphQLParser::parse(Rule::const_list, "[123.0123e7 abc]").unwrap();
GraphQLParser::parse(Rule::const_list, "[123.0123e77 abc]").unwrap();
assert!(GraphQLParser::parse(Rule::const_list, "[123abc]").is_err());
assert!(GraphQLParser::parse(Rule::const_list, "[123.0123abc]").is_err());
assert!(GraphQLParser::parse(Rule::const_list, "[123.0123e7abc]").is_err());
assert!(GraphQLParser::parse(Rule::const_list, "[123.0123e77abc]").is_err());
}
}

View File

@ -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,

View File

@ -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.

View File

@ -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<Name>,
/// The type of the variable.
pub var_type: Positioned<Type>,
/// The variable's directives.
pub directives: Vec<Positioned<Directive>>,
/// The optional default value of the variable.
pub default_value: Option<Positioned<ConstValue>>,
}
@ -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.

View File

@ -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.

View File

@ -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,
}

View File

@ -0,0 +1,3 @@
query Foo($a: Int @directive = 10, $b: Int @directive) {
value
}

View File

@ -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

View File

@ -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
///

View File

@ -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
}
}
}

View File

@ -189,6 +189,7 @@ pub enum MetaType {
description: Option<&'static str>,
is_valid: fn(value: &Value) -> bool,
visible: Option<MetaVisibleFn>,
specified_by_url: Option<&'static str>,
},
Object {
name: String,
@ -198,6 +199,7 @@ pub enum MetaType {
extends: bool,
keys: Option<Vec<String>>,
visible: Option<MetaVisibleFn>,
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,
},
);

View File

@ -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,
})
}
}

View File

@ -190,6 +190,7 @@ where
extends: false,
keys: None,
visible: None,
is_subscription: false,
}
})
}

View File

@ -107,6 +107,7 @@ where
extends: false,
keys: None,
visible: None,
is_subscription: false,
}
})
}

View File

@ -45,6 +45,7 @@ impl Type for EmptyMutation {
extends: false,
keys: None,
visible: None,
is_subscription: false,
})
}
}

View File

@ -25,6 +25,7 @@ impl Type for EmptySubscription {
extends: false,
keys: None,
visible: None,
is_subscription: true,
})
}
}

View File

@ -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<Self> {
match value {

View File

@ -5,7 +5,11 @@ use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value};
/// Implement the DateTime<FixedOffset> 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<FixedOffset> {
fn parse(value: Value) -> InputValueResult<Self> {
match &value {
@ -22,7 +26,11 @@ impl ScalarType for DateTime<FixedOffset> {
/// Implement the DateTime<Local> 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<Local> {
fn parse(value: Value) -> InputValueResult<Self> {
match &value {
@ -39,7 +47,11 @@ impl ScalarType for DateTime<Local> {
/// Implement the DateTime<Utc> 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<Utc> {
fn parse(value: Value) -> InputValueResult<Self> {
match &value {

View File

@ -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<Self> {

View File

@ -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.

View File

@ -85,6 +85,7 @@ impl<T> Type for OutputJson<T> {
description: None,
is_valid: |_| true,
visible: None,
specified_by_url: None,
})
}
}

View File

@ -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:** <https://spec.graphql.org/June2018/#sec-Null-Value>
/// **Reference:** <https://spec.graphql.org/October2021/#sec-Null-Value>
///
/// # Examples
///

View File

@ -50,6 +50,7 @@ impl<A: Type, B: Type> Type for MergedObject<A, B> {
extends: false,
keys: None,
visible: None,
is_subscription: false,
}
})
}

View File

@ -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"),
})
}
}

View File

@ -329,4 +329,9 @@ mod tests {
"#,
);
}
#[test]
fn typename_in_subscription_root() {
expect_fails_rule!(factory, "subscription { __typename }");
}
}

View File

@ -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<Schema<QueryRoot, MutationRoot, EmptySubscription>> =
Lazy::new(|| Schema::new(QueryRoot, MutationRoot, EmptySubscription));
pub struct SubscriptionRoot;
#[Subscription(internal)]
impl SubscriptionRoot {
async fn values(&self) -> impl Stream<Item = i32> {
futures_util::stream::once(async move { 10 })
}
}
static TEST_HARNESS: Lazy<Schema<QueryRoot, MutationRoot, SubscriptionRoot>> =
Lazy::new(|| Schema::new(QueryRoot, MutationRoot, SubscriptionRoot));
pub(crate) fn validate<'a, V, F>(
doc: &'a ExecutableDocument,

View File

@ -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) => {

View File

@ -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",
}
})
);