From e1c577de440e94645e1741fb8d4dad59d82bf591 Mon Sep 17 00:00:00 2001 From: Miaxos Date: Fri, 22 Oct 2021 13:06:54 +0000 Subject: [PATCH 01/65] feat: add derived field for complex object --- derive/src/args.rs | 18 +++++-- derive/src/object.rs | 88 ++++++++++++++++++++++++++++++++++- docs/en/src/SUMMARY.md | 1 + docs/en/src/derived_fields.md | 61 ++++++++++++++++++++++++ src/lib.rs | 8 ++++ tests/derived_field.rs | 47 +++++++++++++++++++ 6 files changed, 218 insertions(+), 5 deletions(-) create mode 100644 docs/en/src/derived_fields.md create mode 100644 tests/derived_field.rs diff --git a/derive/src/args.rs b/derive/src/args.rs index 0cdcf5b2..9beffb47 100644 --- a/derive/src/args.rs +++ b/derive/src/args.rs @@ -6,7 +6,7 @@ use syn::{ Attribute, Generics, Ident, Lit, LitBool, LitStr, Meta, NestedMeta, Path, Type, Visibility, }; -#[derive(FromMeta)] +#[derive(FromMeta, Clone)] #[darling(default)] pub struct CacheControl { public: bool, @@ -46,7 +46,7 @@ impl FromMeta for DefaultValue { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Visible { None, HiddenAlways, @@ -86,7 +86,7 @@ pub struct ConcreteType { pub params: PathList, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Deprecation { NoDeprecated, Deprecated { reason: Option }, @@ -115,7 +115,7 @@ impl FromMeta for Deprecation { } } -#[derive(FromField)] +#[derive(FromField, Clone)] #[darling(attributes(graphql), forward_attrs(doc))] pub struct SimpleObjectField { pub ident: Option, @@ -243,6 +243,16 @@ pub struct ObjectField { pub guard: Option, pub visible: Option, pub complexity: Option, + #[darling(default, multiple)] + pub derived: Vec, +} + +#[derive(FromMeta, Default, Clone)] +#[darling(default)] +/// Derivied fields arguments: are used to generate derivied fields. +pub struct DerivedField { + pub name: Option, + pub into: Option, } #[derive(FromDeriveInput)] diff --git a/derive/src/object.rs b/derive/src/object.rs index 2f44632a..940ac4f4 100644 --- a/derive/src/object.rs +++ b/derive/src/object.rs @@ -1,7 +1,13 @@ use proc_macro::TokenStream; +use proc_macro2::Ident; use quote::quote; +use std::iter::FromIterator; +use std::str::FromStr; use syn::ext::IdentExt; -use syn::{Block, Error, ImplItem, ItemImpl, ReturnType}; +use syn::{ + punctuated::Punctuated, Block, Error, FnArg, ImplItem, ItemImpl, Pat, ReturnType, Token, Type, + TypeReference, +}; use crate::args::{self, ComplexityType, RenameRuleExt, RenameTarget}; use crate::output_type::OutputType; @@ -38,6 +44,86 @@ pub fn generate( let mut add_keys = Vec::new(); let mut create_entity_types = Vec::new(); + // Computation of the derivated fields + let mut derived_impls = vec![]; + for item in &mut item_impl.items { + if let ImplItem::Method(method) = item { + let method_args: args::ObjectField = + parse_graphql_attrs(&method.attrs)?.unwrap_or_default(); + + for derived in method_args.derived { + if derived.name.is_some() && derived.into.is_some() { + let base_function_name = &method.sig.ident; + let name = derived.name.unwrap(); + let into = Type::Verbatim( + proc_macro2::TokenStream::from_str(&derived.into.unwrap()).unwrap(), + ); + + let mut new_impl = method.clone(); + new_impl.sig.ident = name; + new_impl.sig.output = + syn::parse2::(quote! { -> #crate_name::Result<#into> }) + .expect("invalid result type"); + + let should_create_context = new_impl + .sig + .inputs + .iter() + .nth(1) + .map(|x| { + if let FnArg::Typed(pat) = x { + if let Type::Reference(TypeReference { elem, .. }) = &*pat.ty { + if let Type::Path(path) = elem.as_ref() { + return path.path.segments.last().unwrap().ident + != "Context"; + } + } + }; + true + }) + .unwrap_or(true); + + if should_create_context { + let arg_ctx = syn::parse2::(quote! { ctx: &Context<'_> }) + .expect("invalid arg type"); + new_impl.sig.inputs.insert(1, arg_ctx); + } + + let other_atts: Punctuated = Punctuated::from_iter( + new_impl + .sig + .inputs + .iter() + .filter_map(|x| match x { + FnArg::Typed(pat) => match &*pat.pat { + Pat::Ident(ident) => Some(Ok(ident.ident.clone())), + _ => Some(Err(Error::new_spanned( + &pat, + "Must be a simple argument", + ) + .into())), + }, + FnArg::Receiver(_) => None, + }) + .collect::, Error>>()? + .into_iter(), + ); + + let new_block = quote!({ + { + ::std::result::Result::Ok(#self_ty::#base_function_name(&self, #other_atts).await?.into()) + } + }); + + new_impl.block = syn::parse2::(new_block).expect("invalid block"); + + derived_impls.push(ImplItem::Method(new_impl)); + } + } + } + } + item_impl.items.append(&mut derived_impls); + for item in &mut item_impl.items { if let ImplItem::Method(method) = item { let method_args: args::ObjectField = diff --git a/docs/en/src/SUMMARY.md b/docs/en/src/SUMMARY.md index c11a8739..61cfb56c 100644 --- a/docs/en/src/SUMMARY.md +++ b/docs/en/src/SUMMARY.md @@ -8,6 +8,7 @@ - [Context](context.md) - [Error handling](error_handling.md) - [Merging Objects / Subscriptions](merging_objects.md) + - [Derived fields](derived_fields.md) - [Enum](define_enum.md) - [Interface](define_interface.md) - [Union](define_union.md) diff --git a/docs/en/src/derived_fields.md b/docs/en/src/derived_fields.md new file mode 100644 index 00000000..b8174ec9 --- /dev/null +++ b/docs/en/src/derived_fields.md @@ -0,0 +1,61 @@ +# Derived fields + +When you are working on a GraphQL project, you usually have to explain and share how your scalars should +be interpreted by your consumers. Sometimes, you event want to have the same data and the same logic exposing +the data in another type. + +Within `async-graphql` you can create derivated fields for objects to generate derivated fields. + +Consider you want to create a `Date` scalar, to represent an event of time. +How will you represent and format this date? You could create a scalar `Date` where you specified it's the RFCXXX +implemented to format it. + +With derivated fields there is a simple way to support multiple representation of a `Date` easily: + +```rust +struct DateRFC3339(chrono::DateTime); +struct DateRFC2822(chrono::DateTime); + +#[Scalar] +impl ScalarType for DateRFC3339 { + fn parse(value: Value) -> InputValueResult { ... } + + fn to_value(&self) -> Value { + Value::String(self.0.to_rfc3339()) + } +} + +#[Scalar] +impl ScalarType for DateRFC2822 { + fn parse(value: Value) -> InputValueResult { ... } + + fn to_value(&self) -> Value { + Value::String(self.0.to_rfc2822()) + } +} + +impl From for DateRFC3339 { + fn from(value: DateRFC2822) -> Self { + DateRFC3339(value.0) + } +} + +struct Query; + +#[Object] +impl Query { + #[graphql(derived(name = "date_rfc3339", into = "DateRFC3339"))] + async fn duration_rfc2822(&self, arg: String) -> DateRFC2822 { + todo!() + } +} +``` + +It will render a GraphQL like: + +```graphql +type Query { + duration_rfc2822(arg: String): DateRFC2822! + duration_rfc3339(arg: String): DateRFC3339! +} +``` diff --git a/src/lib.rs b/src/lib.rs index 37fa27b5..9b83da75 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -279,6 +279,7 @@ pub type FieldResult = Result; /// | default | Use `Default::default` for default value | none | Y | /// | default | Argument default value | literal | Y | /// | default_with | Expression to generate default value | code string | Y | +/// | derived | Generate derived fields *[See also the Book](https://async-graphql.github.io/async-graphql/en/derived_fields.html).* | object | Y | /// | validator | Input value validator | [`InputValueValidator`](validators/trait.InputValueValidator.html) | Y | /// | complexity | Custom field complexity. *[See also the Book](https://async-graphql.github.io/async-graphql/en/depth_and_complexity.html).* | bool | Y | /// | complexity | Custom field complexity. | string | Y | @@ -288,6 +289,13 @@ pub type FieldResult = Result; /// | serial | Resolve each field sequentially. | bool | Y | /// | key | Is entity key(for Federation) | bool | Y | /// +/// # Derived argument parameters +/// +/// | Attribute | description | Type | Optional | +/// |--------------|------------------------------------------|------------ |----------| +/// | name | Generated derived field name | string | N | +/// | into | Type to derived an into | string | Y | +/// /// # Valid field return types /// /// - Scalar values, such as `i32` and `bool`. `usize`, `isize`, `u128` and `i128` are not diff --git a/tests/derived_field.rs b/tests/derived_field.rs new file mode 100644 index 00000000..ee1860e6 --- /dev/null +++ b/tests/derived_field.rs @@ -0,0 +1,47 @@ +use async_graphql::*; + +#[tokio::test] +pub async fn test_derived_field() { + use serde::{Deserialize, Serialize}; + + struct Query; + + #[derive(Serialize, Deserialize)] + struct ValueDerived(String); + + scalar!(ValueDerived); + + impl From for ValueDerived { + fn from(value: i32) -> Self { + ValueDerived(format!("{}", value)) + } + } + + #[Object] + impl Query { + #[graphql(derived(name = "value2", into = "ValueDerived"))] + async fn value1(&self, #[graphql(default = 100)] input: i32) -> i32 { + input + } + } + + let query = "{ value1 value2 }"; + let schema = Schema::new(Query, EmptyMutation, EmptySubscription); + assert_eq!( + schema.execute(query).await.data, + value!({ + "value1": 100, + "value2": "100", + }) + ); + + let query = "{ value1(input: 1) value2(input: 2) }"; + let schema = Schema::new(Query, EmptyMutation, EmptySubscription); + assert_eq!( + schema.execute(query).await.data, + value!({ + "value1": 1, + "value2": "2", + }) + ); +} From e77fa153ff8779e5a5c87835d1f949c1246aba5c Mon Sep 17 00:00:00 2001 From: Miaxos Date: Fri, 22 Oct 2021 13:10:06 +0000 Subject: [PATCH 02/65] misc: update documentation --- docs/en/src/derived_fields.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/src/derived_fields.md b/docs/en/src/derived_fields.md index b8174ec9..23c1fa63 100644 --- a/docs/en/src/derived_fields.md +++ b/docs/en/src/derived_fields.md @@ -4,13 +4,13 @@ When you are working on a GraphQL project, you usually have to explain and share be interpreted by your consumers. Sometimes, you event want to have the same data and the same logic exposing the data in another type. -Within `async-graphql` you can create derivated fields for objects to generate derivated fields. +Within `async-graphql` you can create derived fields for objects to generate derived fields. Consider you want to create a `Date` scalar, to represent an event of time. How will you represent and format this date? You could create a scalar `Date` where you specified it's the RFCXXX implemented to format it. -With derivated fields there is a simple way to support multiple representation of a `Date` easily: +With derived fields there is a simple way to support multiple representation of a `Date` easily: ```rust struct DateRFC3339(chrono::DateTime); From 6e71fac257456f67342241ae1b77cf770a1839a9 Mon Sep 17 00:00:00 2001 From: Jeff Registre Date: Fri, 22 Oct 2021 16:27:03 -0400 Subject: [PATCH 03/65] Update cargo edition to 2021 --- .rustfmt.toml | 2 +- Cargo.toml | 2 +- derive/Cargo.toml | 2 +- integrations/actix-web/Cargo.toml | 2 +- integrations/axum/Cargo.toml | 2 +- integrations/poem/Cargo.toml | 2 +- integrations/rocket/Cargo.toml | 2 +- integrations/tide/Cargo.toml | 2 +- integrations/warp/Cargo.toml | 2 +- parser/Cargo.toml | 2 +- value/Cargo.toml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.rustfmt.toml b/.rustfmt.toml index 50ff879a..9d25d3fc 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,2 +1,2 @@ -edition = "2018" +edition = "2021" newline_style = "unix" diff --git a/Cargo.toml b/Cargo.toml index 5766996c..869cf9a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "async-graphql" version = "2.10.4" authors = ["sunli ", "Koxiaet"] -edition = "2018" +edition = "2021" description = "A GraphQL server library implemented in Rust" license = "MIT/Apache-2.0" documentation = "https://docs.rs/async-graphql/" diff --git a/derive/Cargo.toml b/derive/Cargo.toml index ee246810..45613c65 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -2,7 +2,7 @@ name = "async-graphql-derive" version = "2.10.4" authors = ["sunli ", "Koxiaet"] -edition = "2018" +edition = "2021" description = "Macros for async-graphql" license = "MIT/Apache-2.0" documentation = "https://docs.rs/async-graphql/" diff --git a/integrations/actix-web/Cargo.toml b/integrations/actix-web/Cargo.toml index 87adb2c9..d5d12886 100644 --- a/integrations/actix-web/Cargo.toml +++ b/integrations/actix-web/Cargo.toml @@ -2,7 +2,7 @@ name = "async-graphql-actix-web" version = "2.10.4" authors = ["sunli ", "Koxiaet"] -edition = "2018" +edition = "2021" description = "async-graphql for actix-web" license = "MIT/Apache-2.0" documentation = "https://docs.rs/async-graphql-actix-web/" diff --git a/integrations/axum/Cargo.toml b/integrations/axum/Cargo.toml index d747e1f2..5a67728a 100644 --- a/integrations/axum/Cargo.toml +++ b/integrations/axum/Cargo.toml @@ -2,7 +2,7 @@ name = "async-graphql-axum" version = "2.10.4" authors = ["sunli "] -edition = "2018" +edition = "2021" description = "async-graphql for axum" license = "MIT/Apache-2.0" documentation = "https://docs.rs/async-graphql-axum/" diff --git a/integrations/poem/Cargo.toml b/integrations/poem/Cargo.toml index 4cbd349e..2cca06a1 100644 --- a/integrations/poem/Cargo.toml +++ b/integrations/poem/Cargo.toml @@ -2,7 +2,7 @@ name = "async-graphql-poem" version = "2.10.4" authors = ["sunli "] -edition = "2018" +edition = "2021" description = "async-graphql for poem" license = "MIT/Apache-2.0" documentation = "https://docs.rs/async-graphql-poem/" diff --git a/integrations/rocket/Cargo.toml b/integrations/rocket/Cargo.toml index 1cc81c93..cf1f4da0 100644 --- a/integrations/rocket/Cargo.toml +++ b/integrations/rocket/Cargo.toml @@ -2,7 +2,7 @@ name = "async-graphql-rocket" version = "2.10.4" authors = ["Daniel Wiesenberg "] -edition = "2018" +edition = "2021" description = "async-graphql for Rocket.rs" license = "MIT/Apache-2.0" documentation = "https://docs.rs/async-graphql/" diff --git a/integrations/tide/Cargo.toml b/integrations/tide/Cargo.toml index f0f91a0c..05499374 100644 --- a/integrations/tide/Cargo.toml +++ b/integrations/tide/Cargo.toml @@ -2,7 +2,7 @@ name = "async-graphql-tide" version = "2.10.4" authors = ["vkill "] -edition = "2018" +edition = "2021" description = "async-graphql for tide" license = "MIT/Apache-2.0" documentation = "https://docs.rs/async-graphql-tide/" diff --git a/integrations/warp/Cargo.toml b/integrations/warp/Cargo.toml index f200ee01..577d2261 100644 --- a/integrations/warp/Cargo.toml +++ b/integrations/warp/Cargo.toml @@ -2,7 +2,7 @@ name = "async-graphql-warp" version = "2.10.4" authors = ["sunli ", "Koxiaet"] -edition = "2018" +edition = "2021" description = "async-graphql for warp" license = "MIT/Apache-2.0" documentation = "https://docs.rs/async-graphql-warp/" diff --git a/parser/Cargo.toml b/parser/Cargo.toml index 4de70439..e3960c60 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -2,7 +2,7 @@ name = "async-graphql-parser" version = "2.10.4" authors = ["sunli ", "Koxiaet"] -edition = "2018" +edition = "2021" description = "GraphQL query parser for async-graphql" license = "MIT/Apache-2.0" documentation = "https://docs.rs/async-graphql/" diff --git a/value/Cargo.toml b/value/Cargo.toml index ab403c89..d890ab68 100644 --- a/value/Cargo.toml +++ b/value/Cargo.toml @@ -2,7 +2,7 @@ name = "async-graphql-value" version = "2.10.4" authors = ["sunli ", "Koxiaet"] -edition = "2018" +edition = "2021" description = "GraphQL value for async-graphql" license = "MIT/Apache-2.0" documentation = "https://docs.rs/async-graphql/" From 30009005d119306d07c6b19cbbaead0ab7ec70cc Mon Sep 17 00:00:00 2001 From: Jeff Registre Date: Fri, 22 Oct 2021 16:27:23 -0400 Subject: [PATCH 04/65] Removed import for new prelude members: TryFrom, TryInto, FromIterator --- derive/src/utils.rs | 4 ++-- integrations/axum/src/response.rs | 2 -- integrations/warp/src/batch_request.rs | 1 - src/look_ahead.rs | 1 - src/request.rs | 1 - src/types/external/list/array.rs | 1 - src/types/id.rs | 1 - value/src/lib.rs | 2 -- 8 files changed, 2 insertions(+), 11 deletions(-) diff --git a/derive/src/utils.rs b/derive/src/utils.rs index 394a30d4..dc0dfcbf 100644 --- a/derive/src/utils.rs +++ b/derive/src/utils.rs @@ -316,11 +316,11 @@ fn generate_default_value(lit: &Lit) -> GeneratorResult { } Lit::Int(value) => { let value = value.base10_parse::()?; - Ok(quote!({ ::std::convert::TryInto::try_into(#value).unwrap() })) + Ok(quote!({ ::TryInto::try_into(#value).unwrap() })) } Lit::Float(value) => { let value = value.base10_parse::()?; - Ok(quote!({ ::std::convert::TryInto::try_into(#value) })) + Ok(quote!({ ::TryInto::try_into(#value) })) } Lit::Bool(value) => { let value = value.value; diff --git a/integrations/axum/src/response.rs b/integrations/axum/src/response.rs index d124c358..ed3d7b51 100644 --- a/integrations/axum/src/response.rs +++ b/integrations/axum/src/response.rs @@ -1,5 +1,3 @@ -use std::convert::TryFrom; - use axum::body::Body; use axum::response::IntoResponse; use headers::HeaderName; diff --git a/integrations/warp/src/batch_request.rs b/integrations/warp/src/batch_request.rs index b9152ee8..e0ff25cf 100644 --- a/integrations/warp/src/batch_request.rs +++ b/integrations/warp/src/batch_request.rs @@ -1,4 +1,3 @@ -use std::convert::TryInto; use std::io; use std::io::ErrorKind; diff --git a/src/look_ahead.rs b/src/look_ahead.rs index ccd874a8..a142221c 100644 --- a/src/look_ahead.rs +++ b/src/look_ahead.rs @@ -1,5 +1,4 @@ use std::collections::HashMap; -use std::convert::TryFrom; use crate::parser::types::{Field, FragmentDefinition, Selection, SelectionSet}; use crate::Context; diff --git a/src/request.rs b/src/request.rs index 53c83978..183ec798 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,6 +1,5 @@ use std::any::Any; use std::collections::HashMap; -use std::convert::TryFrom; use std::fmt::{self, Debug, Formatter}; use serde::{Deserialize, Deserializer, Serialize}; diff --git a/src/types/external/list/array.rs b/src/types/external/list/array.rs index 4d832431..bc870539 100644 --- a/src/types/external/list/array.rs +++ b/src/types/external/list/array.rs @@ -1,5 +1,4 @@ use std::borrow::Cow; -use std::convert::TryInto; use crate::parser::types::Field; use crate::resolver_utils::resolve_list; diff --git a/src/types/id.rs b/src/types/id.rs index f0bec09f..04807019 100644 --- a/src/types/id.rs +++ b/src/types/id.rs @@ -1,4 +1,3 @@ -use std::convert::TryFrom; use std::num::ParseIntError; use std::ops::{Deref, DerefMut}; diff --git a/value/src/lib.rs b/value/src/lib.rs index fd44e9e6..62d6af73 100644 --- a/value/src/lib.rs +++ b/value/src/lib.rs @@ -11,9 +11,7 @@ mod variables; use std::borrow::{Borrow, Cow}; use std::collections::BTreeMap; -use std::convert::{TryFrom, TryInto}; use std::fmt::{self, Display, Formatter, Write}; -use std::iter::FromIterator; use std::ops::Deref; use std::sync::Arc; From ed04f61e185f8f7b6d5ad8775eafbb3d4aeab76c Mon Sep 17 00:00:00 2001 From: Sunli Date: Sat, 23 Oct 2021 13:33:21 +0800 Subject: [PATCH 05/65] Bump poem from `0.6.6` to `1.0.7` --- integrations/poem/Cargo.toml | 3 +-- integrations/poem/src/extractor.rs | 4 ++-- integrations/poem/src/query.rs | 4 ++-- integrations/poem/src/subscription.rs | 4 ++-- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/integrations/poem/Cargo.toml b/integrations/poem/Cargo.toml index 4cbd349e..afc73946 100644 --- a/integrations/poem/Cargo.toml +++ b/integrations/poem/Cargo.toml @@ -11,11 +11,10 @@ repository = "https://github.com/async-graphql/async-graphql" keywords = ["futures", "async", "graphql", "poem"] categories = ["network-programming", "asynchronous"] - [dependencies] async-graphql = { path = "../..", version = "=2.10.4" } -poem = { version = "0.6.6", features = ["websocket"] } +poem = { version = "1.0.7", features = ["websocket"] } futures-util = { version = "0.3.13", default-features = false } serde_json = "1.0.66" tokio-util = { version = "0.6.7", features = ["compat"] } diff --git a/integrations/poem/src/extractor.rs b/integrations/poem/src/extractor.rs index ed9e36c3..f0a602f0 100644 --- a/integrations/poem/src/extractor.rs +++ b/integrations/poem/src/extractor.rs @@ -12,7 +12,7 @@ use tokio_util::compat::TokioAsyncReadCompatExt; /// # Example /// /// ``` -/// use poem::{handler, RouteMethod, route, EndpointExt}; +/// use poem::{handler, Route, post, EndpointExt}; /// use poem::web::{Json, Data}; /// use poem::middleware::AddData; /// use async_graphql_poem::GraphQLRequest; @@ -35,7 +35,7 @@ use tokio_util::compat::TokioAsyncReadCompatExt; /// } /// /// let schema = Schema::new(Query, EmptyMutation, EmptySubscription); -/// let app = route().at("/", RouteMethod::new().post(index.with(AddData::new(schema)))); +/// let app = Route::new().at("/", post(index.with(AddData::new(schema)))); /// ``` pub struct GraphQLRequest(pub async_graphql::Request); diff --git a/integrations/poem/src/query.rs b/integrations/poem/src/query.rs index 1b5d1d5e..6dbc0478 100644 --- a/integrations/poem/src/query.rs +++ b/integrations/poem/src/query.rs @@ -9,7 +9,7 @@ use crate::GraphQLBatchRequest; /// # Example /// /// ``` -/// use poem::{route, RouteMethod}; +/// use poem::{Route, post}; /// use async_graphql_poem::GraphQL; /// use async_graphql::{EmptyMutation, EmptySubscription, Object, Schema}; /// @@ -25,7 +25,7 @@ use crate::GraphQLBatchRequest; /// type MySchema = Schema; /// /// let schema = Schema::new(Query, EmptyMutation, EmptySubscription); -/// let app = route().at("/", RouteMethod::new().post(GraphQL::new(schema))); +/// let app = Route::new().at("/", post(GraphQL::new(schema))); /// ``` pub struct GraphQL { schema: Schema, diff --git a/integrations/poem/src/subscription.rs b/integrations/poem/src/subscription.rs index ed7a73ec..92cc025f 100644 --- a/integrations/poem/src/subscription.rs +++ b/integrations/poem/src/subscription.rs @@ -12,7 +12,7 @@ use poem::{http, Endpoint, FromRequest, IntoResponse, Request, Response, Result} /// # Example /// /// ``` -/// use poem::{route, RouteMethod}; +/// use poem::{Route, get}; /// use async_graphql_poem::GraphQLSubscription; /// use async_graphql::{EmptyMutation, Object, Schema, Subscription}; /// use futures_util::{Stream, stream}; @@ -38,7 +38,7 @@ use poem::{http, Endpoint, FromRequest, IntoResponse, Request, Response, Result} /// type MySchema = Schema; /// /// let schema = Schema::new(Query, EmptyMutation, Subscription); -/// let app = route().at("/ws", RouteMethod::new().get(GraphQLSubscription::new(schema))); +/// let app = Route::new().at("/ws", get(GraphQLSubscription::new(schema))); /// ``` pub struct GraphQLSubscription { schema: Schema, From c1459b9b2246656d9223ceda209180b38b29adc7 Mon Sep 17 00:00:00 2001 From: Sunli Date: Sat, 23 Oct 2021 14:08:20 +0800 Subject: [PATCH 06/65] Update examples --- derive/src/enum.rs | 10 +++------- examples | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/derive/src/enum.rs b/derive/src/enum.rs index 781d8815..f5510988 100644 --- a/derive/src/enum.rs +++ b/derive/src/enum.rs @@ -71,13 +71,9 @@ pub fn generate(enum_args: &args::Enum) -> GeneratorResult { } let remote_conversion = if let Some(remote) = &enum_args.remote { - let remote_ty = if let Ok(ty) = syn::parse_str::(remote) { - ty - } else { - return Err( - Error::new_spanned(remote, format!("Invalid remote type: '{}'", remote)).into(), - ); - }; + let remote_ty = syn::parse_str::(remote).map_err(|_| { + Error::new_spanned(remote, format!("Invalid remote type: '{}'", remote)) + })?; let local_to_remote_items = enum_items.iter().map(|item| { quote! { diff --git a/examples b/examples index df8cfdff..290ef715 160000 --- a/examples +++ b/examples @@ -1 +1 @@ -Subproject commit df8cfdff35d25d1cef3c9fda4d98e639a55fffc2 +Subproject commit 290ef715b2a6be346acc16ddd406f844e8ed3bfd From b2cf9cfd98a27f4589465ab3ea8695eb08807022 Mon Sep 17 00:00:00 2001 From: Sunli Date: Sat, 23 Oct 2021 19:34:26 +0800 Subject: [PATCH 07/65] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1da8f201..509a16c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.10.5] 2021-10-22 + +- Bump poem from `0.6.6` to `1.0.7`. + ## [2.10.4] 2021-10-22 - Implement `Default` for ID #659. From 91cf37e28da662b412c07faa697eb47458442bb7 Mon Sep 17 00:00:00 2001 From: Sunli Date: Sat, 23 Oct 2021 19:34:57 +0800 Subject: [PATCH 08/65] Release 2.10.5 async-graphql@2.10.5 async-graphql-actix-web@2.10.5 async-graphql-axum@2.10.5 async-graphql-derive@2.10.5 async-graphql-parser@2.10.5 async-graphql-poem@2.10.5 async-graphql-rocket@2.10.5 async-graphql-tide@2.10.5 async-graphql-value@2.10.5 async-graphql-warp@2.10.5 Generated by cargo-workspaces --- Cargo.toml | 8 ++++---- derive/Cargo.toml | 4 ++-- integrations/actix-web/Cargo.toml | 4 ++-- integrations/axum/Cargo.toml | 4 ++-- integrations/poem/Cargo.toml | 4 ++-- integrations/rocket/Cargo.toml | 4 ++-- integrations/tide/Cargo.toml | 4 ++-- integrations/warp/Cargo.toml | 4 ++-- parser/Cargo.toml | 4 ++-- value/Cargo.toml | 2 +- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5766996c..da7114db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql" -version = "2.10.4" +version = "2.10.5" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "A GraphQL server library implemented in Rust" @@ -24,9 +24,9 @@ decimal = ["rust_decimal"] cbor = ["serde_cbor"] [dependencies] -async-graphql-derive = { path = "derive", version = "=2.10.4" } -async-graphql-value = { path = "value", version = "=2.10.4" } -async-graphql-parser = { path = "parser", version = "=2.10.4" } +async-graphql-derive = { path = "derive", version = "=2.10.5" } +async-graphql-value = { path = "value", version = "=2.10.5" } +async-graphql-parser = { path = "parser", version = "=2.10.5" } async-stream = "0.3.0" async-trait = "0.1.48" diff --git a/derive/Cargo.toml b/derive/Cargo.toml index ee246810..256b44c3 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-derive" -version = "2.10.4" +version = "2.10.5" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "Macros for async-graphql" @@ -15,7 +15,7 @@ categories = ["network-programming", "asynchronous"] proc-macro = true [dependencies] -async-graphql-parser = { path = "../parser", version = "=2.10.4" } +async-graphql-parser = { path = "../parser", version = "=2.10.5" } proc-macro2 = "1.0.24" syn = { version = "1.0.64", features = ["full", "extra-traits", "visit-mut", "visit"] } quote = "1.0.9" diff --git a/integrations/actix-web/Cargo.toml b/integrations/actix-web/Cargo.toml index 87adb2c9..19fc1ed3 100644 --- a/integrations/actix-web/Cargo.toml +++ b/integrations/actix-web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-actix-web" -version = "2.10.4" +version = "2.10.5" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "async-graphql for actix-web" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "=2.10.4" } +async-graphql = { path = "../..", version = "=2.10.5" } actix = "0.10.0" actix-http = "2.2.0" diff --git a/integrations/axum/Cargo.toml b/integrations/axum/Cargo.toml index d747e1f2..9da1322d 100644 --- a/integrations/axum/Cargo.toml +++ b/integrations/axum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-axum" -version = "2.10.4" +version = "2.10.5" authors = ["sunli "] edition = "2018" description = "async-graphql for axum" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql", "axum"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "=2.10.4" } +async-graphql = { path = "../..", version = "=2.10.5" } async-trait = "0.1.51" axum = { version = "0.2.5", features = ["ws", "headers"] } diff --git a/integrations/poem/Cargo.toml b/integrations/poem/Cargo.toml index afc73946..ef5dcad6 100644 --- a/integrations/poem/Cargo.toml +++ b/integrations/poem/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-poem" -version = "2.10.4" +version = "2.10.5" authors = ["sunli "] edition = "2018" description = "async-graphql for poem" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql", "poem"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "=2.10.4" } +async-graphql = { path = "../..", version = "=2.10.5" } poem = { version = "1.0.7", features = ["websocket"] } futures-util = { version = "0.3.13", default-features = false } diff --git a/integrations/rocket/Cargo.toml b/integrations/rocket/Cargo.toml index 1cc81c93..1a9f905d 100644 --- a/integrations/rocket/Cargo.toml +++ b/integrations/rocket/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-rocket" -version = "2.10.4" +version = "2.10.5" authors = ["Daniel Wiesenberg "] edition = "2018" description = "async-graphql for Rocket.rs" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql", "rocket"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "=2.10.4" } +async-graphql = { path = "../..", version = "=2.10.5" } rocket = { version = "0.5.0-rc.1", default-features = false } serde = "1.0.126" diff --git a/integrations/tide/Cargo.toml b/integrations/tide/Cargo.toml index f0f91a0c..4c43137b 100644 --- a/integrations/tide/Cargo.toml +++ b/integrations/tide/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-tide" -version = "2.10.4" +version = "2.10.5" authors = ["vkill "] edition = "2018" description = "async-graphql for tide" @@ -16,7 +16,7 @@ default = ["websocket"] websocket = ["tide-websockets"] [dependencies] -async-graphql = { path = "../..", version = "=2.10.4" } +async-graphql = { path = "../..", version = "=2.10.5" } async-trait = "0.1.48" futures-util = "0.3.13" serde_json = "1.0.64" diff --git a/integrations/warp/Cargo.toml b/integrations/warp/Cargo.toml index f200ee01..d0e94ad0 100644 --- a/integrations/warp/Cargo.toml +++ b/integrations/warp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-warp" -version = "2.10.4" +version = "2.10.5" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "async-graphql for warp" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "=2.10.4" } +async-graphql = { path = "../..", version = "=2.10.5" } warp = { version = "0.3.0", default-features = false, features = ["websocket"] } futures-util = { version = "0.3.13", default-features = false, features = ["sink"] } diff --git a/parser/Cargo.toml b/parser/Cargo.toml index 4de70439..dfe547c9 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-parser" -version = "2.10.4" +version = "2.10.5" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "GraphQL query parser for async-graphql" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql-value = { path = "../value", version = "=2.10.4" } +async-graphql-value = { path = "../value", version = "=2.10.5" } pest = "2.1.3" pest_derive = "2.1.0" serde_json = "1.0.64" diff --git a/value/Cargo.toml b/value/Cargo.toml index ab403c89..f3949a73 100644 --- a/value/Cargo.toml +++ b/value/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-value" -version = "2.10.4" +version = "2.10.5" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "GraphQL value for async-graphql" From e9e73f489dea86197deca0e0ecd8ad07434402e7 Mon Sep 17 00:00:00 2001 From: Miaxos Date: Mon, 25 Oct 2021 10:56:33 +0000 Subject: [PATCH 09/65] feat: add derived on simple objects --- derive/src/args.rs | 2 + derive/src/simple_object.rs | 95 ++++++++++++++++++++++++++++++------- src/lib.rs | 9 ++++ tests/derived_field.rs | 58 +++++++++++++++++++++- 4 files changed, 147 insertions(+), 17 deletions(-) diff --git a/derive/src/args.rs b/derive/src/args.rs index 9beffb47..6b753c22 100644 --- a/derive/src/args.rs +++ b/derive/src/args.rs @@ -143,6 +143,8 @@ pub struct SimpleObjectField { pub guard: Option, #[darling(default)] pub visible: Option, + #[darling(default, multiple)] + pub derived: Vec, } #[derive(FromDeriveInput)] diff --git a/derive/src/simple_object.rs b/derive/src/simple_object.rs index d0665abc..1750e0b8 100644 --- a/derive/src/simple_object.rs +++ b/derive/src/simple_object.rs @@ -1,14 +1,25 @@ use darling::ast::Data; use proc_macro::TokenStream; use quote::quote; +use std::str::FromStr; use syn::ext::IdentExt; -use syn::Error; +use syn::{Error, Ident, Type}; -use crate::args::{self, RenameRuleExt, RenameTarget}; +use crate::args::{self, RenameRuleExt, RenameTarget, SimpleObjectField}; use crate::utils::{ gen_deprecation, generate_guards, get_crate_name, get_rustdoc, visible_fn, GeneratorResult, }; +struct DerivedFieldMetadata { + ident: Ident, + into: Type, +} + +struct SimpleObjectFieldGenerator<'a> { + field: &'a SimpleObjectField, + derived: Option, +} + pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult { let crate_name = get_crate_name(object_args.internal); let ident = &object_args.ident; @@ -37,15 +48,48 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult = vec![]; + + // Before processing the fields, we generate the derivated fields for field in &s.fields { + processed_fields.push(SimpleObjectFieldGenerator { + field: &field, + derived: None, + }); + + for derived in &field.derived { + if derived.name.is_some() && derived.into.is_some() { + let name = derived.name.clone().unwrap(); + let into = Type::Verbatim( + proc_macro2::TokenStream::from_str(&derived.into.clone().unwrap()).unwrap(), + ); + let derived = DerivedFieldMetadata { ident: name, into }; + + processed_fields.push(SimpleObjectFieldGenerator { + field: &field, + derived: Some(derived), + }) + } + } + } + + for SimpleObjectFieldGenerator { field, derived } in &processed_fields { if field.skip { continue; } - let ident = match &field.ident { + + let base_ident = match &field.ident { Some(ident) => ident, None => return Err(Error::new_spanned(&ident, "All fields must be named.").into()), }; + let is_derived = derived.is_some(); + let ident = if let Some(derived) = derived { + &derived.ident + } else { + base_ident + }; + let field_name = field.name.clone().unwrap_or_else(|| { object_args .rename_fields @@ -65,7 +109,11 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult quote! { ::std::option::Option::None }, }; let vis = &field.vis; - let ty = &field.ty; + let ty = if let Some(derived) = derived { + &derived.into + } else { + &field.ty + }; let cache_control = { let public = field.cache_control.is_public(); @@ -104,23 +152,38 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult quote! { + &self.#base_ident + }, + false => quote! { + ::std::clone::Clone::clone(&self.#base_ident) + }, + }; + + block = match is_derived { + false => quote! { + #block + }, + true => quote! { + ::std::convert::Into::into(#block) + }, + }; + + let ty = match !field.owned { + true => quote! { &#ty }, + false => quote! { #ty }, + }; + + getters.push( quote! { #[inline] #[allow(missing_docs)] - #vis async fn #ident(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::Result<&#ty> { - ::std::result::Result::Ok(&self.#ident) + #vis async fn #ident(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::Result<#ty> { + ::std::result::Result::Ok(#block) } } - } else { - quote! { - #[inline] - #[allow(missing_docs)] - #vis async fn #ident(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::Result<#ty> { - ::std::result::Result::Ok(::std::clone::Clone::clone(&self.#ident)) - } - } - }); + ); resolvers.push(quote! { if ctx.item.node.name.node == #field_name { diff --git a/src/lib.rs b/src/lib.rs index 9b83da75..158743f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -451,6 +451,7 @@ pub use async_graphql_derive::Object; /// | name | Field name | string | Y | /// | deprecation | Field deprecated | bool | Y | /// | deprecation | Field deprecation reason | string | Y | +/// | derived | Generate derived fields *[See also the Book](https://async-graphql.github.io/async-graphql/en/derived_fields.html).* | object | Y | /// | owned | Field resolver return a ownedship value | bool | Y | /// | cache_control | Field cache control | [`CacheControl`](struct.CacheControl.html) | Y | /// | external | Mark a field as owned by another service. This allows service A to use fields from service B while also knowing at runtime the types of that field. | bool | Y | @@ -460,6 +461,14 @@ pub use async_graphql_derive::Object; /// | visible | 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 | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | /// +/// # Derived argument parameters +/// +/// | Attribute | description | Type | Optional | +/// |--------------|------------------------------------------|------------ |----------| +/// | name | Generated derived field name | string | N | +/// | into | Type to derived an into | string | Y | +/// +/// /// # Examples /// /// ```rust diff --git a/tests/derived_field.rs b/tests/derived_field.rs index ee1860e6..3f582d55 100644 --- a/tests/derived_field.rs +++ b/tests/derived_field.rs @@ -1,7 +1,7 @@ use async_graphql::*; #[tokio::test] -pub async fn test_derived_field() { +pub async fn test_derived_field_object() { use serde::{Deserialize, Serialize}; struct Query; @@ -45,3 +45,59 @@ pub async fn test_derived_field() { }) ); } + +#[tokio::test] +pub async fn test_derived_field_simple_object() { + use serde::{Deserialize, Serialize}; + + struct Query; + + #[derive(Serialize, Deserialize)] + struct ValueDerived(String); + + scalar!(ValueDerived); + + impl From for ValueDerived { + fn from(value: i32) -> Self { + ValueDerived(format!("{}", value)) + } + } + + #[derive(SimpleObject)] + struct TestObj { + #[graphql(owned, derived(name = "value2", into = "ValueDerived"))] + pub value1: i32, + } + + #[Object] + impl Query { + async fn test(&self, #[graphql(default = 100)] input: i32) -> TestObj { + TestObj { value1: input } + } + } + + let query = "{ test { value1 value2 } }"; + let schema = Schema::new(Query, EmptyMutation, EmptySubscription); + assert_eq!( + schema.execute(query).await.data, + value!({ + "test": { + "value1": 100, + "value2": "100", + } + }) + ); + + let query = "{ test(input: 2) { value1 value2 }}"; + let schema = Schema::new(Query, EmptyMutation, EmptySubscription); + dbg!(schema.execute(query).await); + assert_eq!( + schema.execute(query).await.data, + value!({ + "test": { + "value1": 2, + "value2": "2", + } + }) + ); +} From b7232cea654e572fa0d3c50b1df838fbdf00dbee Mon Sep 17 00:00:00 2001 From: Miaxos Date: Mon, 25 Oct 2021 12:23:12 +0000 Subject: [PATCH 10/65] feat: add derived for simple object & complex object --- derive/src/complex_object.rs | 88 +++++++++++++++++++++++++++++++++++- src/lib.rs | 8 ++++ tests/derived_field.rs | 62 +++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 1 deletion(-) diff --git a/derive/src/complex_object.rs b/derive/src/complex_object.rs index d88d8922..105d2ee8 100644 --- a/derive/src/complex_object.rs +++ b/derive/src/complex_object.rs @@ -1,7 +1,13 @@ use proc_macro::TokenStream; +use proc_macro2::Ident; use quote::quote; +use std::iter::FromIterator; +use std::str::FromStr; use syn::ext::IdentExt; -use syn::{Block, Error, ImplItem, ItemImpl, ReturnType}; +use syn::{ + punctuated::Punctuated, Block, Error, FnArg, ImplItem, ItemImpl, Pat, ReturnType, Token, Type, + TypeReference, +}; use crate::args::{self, ComplexityType, RenameRuleExt, RenameTarget}; use crate::output_type::OutputType; @@ -23,6 +29,86 @@ pub fn generate( let mut resolvers = Vec::new(); let mut schema_fields = Vec::new(); + // Computation of the derivated fields + let mut derived_impls = vec![]; + for item in &mut item_impl.items { + if let ImplItem::Method(method) = item { + let method_args: args::ObjectField = + parse_graphql_attrs(&method.attrs)?.unwrap_or_default(); + + for derived in method_args.derived { + if derived.name.is_some() && derived.into.is_some() { + let base_function_name = &method.sig.ident; + let name = derived.name.unwrap(); + let into = Type::Verbatim( + proc_macro2::TokenStream::from_str(&derived.into.unwrap()).unwrap(), + ); + + let mut new_impl = method.clone(); + new_impl.sig.ident = name; + new_impl.sig.output = + syn::parse2::(quote! { -> #crate_name::Result<#into> }) + .expect("invalid result type"); + + let should_create_context = new_impl + .sig + .inputs + .iter() + .nth(1) + .map(|x| { + if let FnArg::Typed(pat) = x { + if let Type::Reference(TypeReference { elem, .. }) = &*pat.ty { + if let Type::Path(path) = elem.as_ref() { + return path.path.segments.last().unwrap().ident + != "Context"; + } + } + }; + true + }) + .unwrap_or(true); + + if should_create_context { + let arg_ctx = syn::parse2::(quote! { ctx: &Context<'_> }) + .expect("invalid arg type"); + new_impl.sig.inputs.insert(1, arg_ctx); + } + + let other_atts: Punctuated = Punctuated::from_iter( + new_impl + .sig + .inputs + .iter() + .filter_map(|x| match x { + FnArg::Typed(pat) => match &*pat.pat { + Pat::Ident(ident) => Some(Ok(ident.ident.clone())), + _ => Some(Err(Error::new_spanned( + &pat, + "Must be a simple argument", + ) + .into())), + }, + FnArg::Receiver(_) => None, + }) + .collect::, Error>>()? + .into_iter(), + ); + + let new_block = quote!({ + { + ::std::result::Result::Ok(#self_ty::#base_function_name(&self, #other_atts).await?.into()) + } + }); + + new_impl.block = syn::parse2::(new_block).expect("invalid block"); + + derived_impls.push(ImplItem::Method(new_impl)); + } + } + } + } + item_impl.items.append(&mut derived_impls); + for item in &mut item_impl.items { if let ImplItem::Method(method) = item { let method_args: args::ObjectField = diff --git a/src/lib.rs b/src/lib.rs index 158743f0..123672b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -515,6 +515,7 @@ pub use async_graphql_derive::SimpleObject; /// | name | Field name | string | Y | /// | deprecation | Field deprecated | bool | Y | /// | deprecation | Field deprecation reason | string | Y | +/// | derived | Generate derived fields *[See also the Book](https://async-graphql.github.io/async-graphql/en/derived_fields.html).* | object | Y | /// | cache_control | Field cache control | [`CacheControl`](struct.CacheControl.html) | Y | /// | external | Mark a field as owned by another service. This allows service A to use fields from service B while also knowing at runtime the types of that field. | bool | Y | /// | provides | Annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway. | string | Y | @@ -524,6 +525,13 @@ pub use async_graphql_derive::SimpleObject; /// | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y | /// | secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y | /// +/// # Derived argument parameters +/// +/// | Attribute | description | Type | Optional | +/// |--------------|------------------------------------------|------------ |----------| +/// | name | Generated derived field name | string | N | +/// | into | Type to derived an into | string | Y | +/// /// # Examples /// /// ```rust diff --git a/tests/derived_field.rs b/tests/derived_field.rs index 3f582d55..304802e1 100644 --- a/tests/derived_field.rs +++ b/tests/derived_field.rs @@ -101,3 +101,65 @@ pub async fn test_derived_field_simple_object() { }) ); } + +#[tokio::test] +pub async fn test_derived_field_complex_object() { + use serde::{Deserialize, Serialize}; + + #[derive(SimpleObject)] + #[graphql(complex)] + struct MyObj { + a: i32, + #[graphql(owned, derived(name = "f", into = "ValueDerived"))] + b: i32, + } + + #[derive(Serialize, Deserialize)] + struct ValueDerived(String); + + scalar!(ValueDerived); + + impl From for ValueDerived { + fn from(value: i32) -> Self { + ValueDerived(format!("{}", value)) + } + } + + #[ComplexObject] + impl MyObj { + async fn c(&self) -> i32 { + self.a + self.b + } + + #[graphql(derived(name = "e", into = "ValueDerived"))] + async fn d(&self, v: i32) -> i32 { + self.a + self.b + v + } + } + + struct Query; + + #[Object] + impl Query { + async fn obj(&self) -> MyObj { + MyObj { a: 10, b: 20 } + } + } + + let query = "{ obj { a b c d(v:100) e(v: 200) f } }"; + let schema = Schema::new(Query, EmptyMutation, EmptySubscription); + dbg!(schema.execute(query).await); + assert_eq!( + schema.execute(query).await.data, + value!({ + "obj": { + "a": 10, + "b": 20, + "c": 30, + "d": 130, + "e": "230", + "f": "20", + }, + }) + ); +} From 9b5149ee23314fba50af0e53b6160843e8eb7d25 Mon Sep 17 00:00:00 2001 From: Sunli Date: Tue, 26 Oct 2021 18:57:39 +0800 Subject: [PATCH 11/65] Respect query object field order. #612 --- CHANGELOG.md | 5 ++ derive/src/complex_object.rs | 3 +- derive/src/input_object.rs | 4 +- derive/src/object.rs | 3 +- derive/src/simple_object.rs | 4 +- derive/src/subscription.rs | 2 +- src/http/playground_source.rs | 4 +- src/resolver_utils/container.rs | 7 +-- src/response.rs | 1 + src/types/external/json_object/btreemap.rs | 4 +- src/types/external/json_object/hashmap.rs | 6 ++- tests/preserve_order.rs | 60 ++++++++++++++++++++++ value/Cargo.toml | 1 + value/src/deserializer.rs | 9 ++-- value/src/lib.rs | 12 +++-- value/src/macros.rs | 6 +-- value/src/serializer.rs | 20 ++++---- value/src/value_serde.rs | 6 +-- value/src/variables.rs | 4 +- 19 files changed, 117 insertions(+), 44 deletions(-) create mode 100644 tests/preserve_order.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 509a16c6..1fe6711d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +- Add derived for object & simple object & complex object. [#667](https://github.com/async-graphql/async-graphql/pull/667) [#670](https://github.com/async-graphql/async-graphql/pull/670) +- Respect query object field order. [#612](https://github.com/async-graphql/async-graphql/issues/612) + ## [2.10.5] 2021-10-22 - Bump poem from `0.6.6` to `1.0.7`. diff --git a/derive/src/complex_object.rs b/derive/src/complex_object.rs index 105d2ee8..8d282690 100644 --- a/derive/src/complex_object.rs +++ b/derive/src/complex_object.rs @@ -85,8 +85,7 @@ pub fn generate( _ => Some(Err(Error::new_spanned( &pat, "Must be a simple argument", - ) - .into())), + ))), }, FnArg::Receiver(_) => None, }) diff --git a/derive/src/input_object.rs b/derive/src/input_object.rs index cd7680d7..228938e3 100644 --- a/derive/src/input_object.rs +++ b/derive/src/input_object.rs @@ -231,7 +231,7 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult } fn to_value(&self) -> #crate_name::Value { - let mut map = ::std::collections::BTreeMap::new(); + let mut map = #crate_name::indexmap::IndexMap::new(); #(#put_fields)* #crate_name::Value::Object(map) } @@ -272,7 +272,7 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult } fn __internal_to_value(&self) -> #crate_name::Value where Self: #crate_name::InputType { - let mut map = ::std::collections::BTreeMap::new(); + let mut map = #crate_name::indexmap::IndexMap::new(); #(#put_fields)* #crate_name::Value::Object(map) } diff --git a/derive/src/object.rs b/derive/src/object.rs index 940ac4f4..6cfe0b79 100644 --- a/derive/src/object.rs +++ b/derive/src/object.rs @@ -100,8 +100,7 @@ pub fn generate( _ => Some(Err(Error::new_spanned( &pat, "Must be a simple argument", - ) - .into())), + ))), }, FnArg::Receiver(_) => None, }) diff --git a/derive/src/simple_object.rs b/derive/src/simple_object.rs index 1750e0b8..784f46d9 100644 --- a/derive/src/simple_object.rs +++ b/derive/src/simple_object.rs @@ -53,7 +53,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult GeneratorResult GraphQLPlaygroundConfig<'a> { #[cfg(test)] mod tests { use super::*; - use std::collections::BTreeMap; + use indexmap::IndexMap; #[test] fn test_with_setting_can_use_any_json_value() { @@ -634,7 +634,7 @@ mod tests { .with_setting("number", 10) .with_setting("null", Value::Null) .with_setting("array", Vec::from([1, 2, 3])) - .with_setting("object", BTreeMap::new()); + .with_setting("object", IndexMap::new()); let json = serde_json::to_value(settings).unwrap(); let settings = json["settings"].as_object().unwrap(); diff --git a/src/resolver_utils/container.rs b/src/resolver_utils/container.rs index dfe30795..d0dc3048 100644 --- a/src/resolver_utils/container.rs +++ b/src/resolver_utils/container.rs @@ -1,7 +1,8 @@ -use std::collections::BTreeMap; use std::future::Future; use std::pin::Pin; +use indexmap::IndexMap; + use crate::extensions::ResolveInfo; use crate::parser::types::Selection; use crate::registry::MetaType; @@ -74,7 +75,7 @@ pub async fn resolve_container_serial<'a, T: ContainerType + ?Sized>( resolve_container_inner(ctx, root, false).await } -fn insert_value(target: &mut BTreeMap, name: Name, value: Value) { +fn insert_value(target: &mut IndexMap, name: Name, value: Value) { if let Some(prev_value) = target.get_mut(&name) { if let Value::Object(target_map) = prev_value { if let Value::Object(obj) = value { @@ -118,7 +119,7 @@ async fn resolve_container_inner<'a, T: ContainerType + ?Sized>( results }; - let mut map = BTreeMap::new(); + let mut map = IndexMap::new(); for (name, value) in res { insert_value(&mut map, name, value); } diff --git a/src/response.rs b/src/response.rs index 1a3fea0a..d851cca9 100644 --- a/src/response.rs +++ b/src/response.rs @@ -98,6 +98,7 @@ impl Response { } /// Response for batchable queries +#[allow(clippy::large_enum_variant)] #[derive(Debug, Serialize)] #[serde(untagged)] pub enum BatchResponse { diff --git a/src/types/external/json_object/btreemap.rs b/src/types/external/json_object/btreemap.rs index c3f14660..bfaff892 100644 --- a/src/types/external/json_object/btreemap.rs +++ b/src/types/external/json_object/btreemap.rs @@ -2,6 +2,8 @@ use std::collections::BTreeMap; use std::fmt::Display; use std::str::FromStr; +use indexmap::IndexMap; + use crate::{ InputType, InputValueError, InputValueResult, Name, OutputType, Scalar, ScalarType, Value, }; @@ -33,7 +35,7 @@ where } fn to_value(&self) -> Value { - let mut map = BTreeMap::new(); + let mut map = IndexMap::new(); for (name, value) in self { map.insert(Name::new(name.to_string()), value.to_value()); } diff --git a/src/types/external/json_object/hashmap.rs b/src/types/external/json_object/hashmap.rs index d805f6ca..9fcc1f05 100644 --- a/src/types/external/json_object/hashmap.rs +++ b/src/types/external/json_object/hashmap.rs @@ -1,8 +1,10 @@ -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; use std::fmt::Display; use std::hash::Hash; use std::str::FromStr; +use indexmap::IndexMap; + use crate::{ InputType, InputValueError, InputValueResult, Name, OutputType, Scalar, ScalarType, Value, }; @@ -34,7 +36,7 @@ where } fn to_value(&self) -> Value { - let mut map = BTreeMap::new(); + let mut map = IndexMap::new(); for (name, value) in self { map.insert(Name::new(name.to_string()), value.to_value()); } diff --git a/tests/preserve_order.rs b/tests/preserve_order.rs new file mode 100644 index 00000000..f4aa96bd --- /dev/null +++ b/tests/preserve_order.rs @@ -0,0 +1,60 @@ +use async_graphql::*; + +#[tokio::test] +pub async fn test_preserve_order() { + #[derive(SimpleObject)] + struct Root { + a: i32, + b: i32, + c: i32, + } + + let schema = Schema::new(Root { a: 1, b: 2, c: 3 }, EmptyMutation, EmptySubscription); + assert_eq!( + schema + .execute("{ a c b }") + .await + .into_result() + .unwrap() + .data, + value!({ + "a": 1, "c": 3, "b": 2 + }) + ); + assert_eq!( + serde_json::to_string( + &schema + .execute("{ a c b }") + .await + .into_result() + .unwrap() + .data + ) + .unwrap(), + r#"{"a":1,"c":3,"b":2}"# + ); + + assert_eq!( + schema + .execute("{ c b a }") + .await + .into_result() + .unwrap() + .data, + value!({ + "c": 3, "b": 2, "a": 1 + }) + ); + assert_eq!( + serde_json::to_string( + &schema + .execute("{ c b a }") + .await + .into_result() + .unwrap() + .data + ) + .unwrap(), + r#"{"c":3,"b":2,"a":1}"# + ); +} diff --git a/value/Cargo.toml b/value/Cargo.toml index f3949a73..0d0f1374 100644 --- a/value/Cargo.toml +++ b/value/Cargo.toml @@ -15,3 +15,4 @@ categories = ["network-programming", "asynchronous"] serde_json = "1.0.64" serde = { version = "1.0.125", features = ["derive"] } bytes = { version = "1.0.1", features = ["serde"] } +indexmap = { version = "1.7.0", features = ["serde"] } diff --git a/value/src/deserializer.rs b/value/src/deserializer.rs index e473cae1..e0cf01e7 100644 --- a/value/src/deserializer.rs +++ b/value/src/deserializer.rs @@ -1,6 +1,7 @@ -use std::collections::BTreeMap; use std::{fmt, vec}; +use indexmap::IndexMap; + use crate::{ConstValue, Name}; use serde::de::{ @@ -78,7 +79,7 @@ where } fn visit_object<'de, V>( - object: BTreeMap, + object: IndexMap, visitor: V, ) -> Result where @@ -365,13 +366,13 @@ impl<'de> SeqAccess<'de> for SeqDeserializer { } struct MapDeserializer { - iter: as IntoIterator>::IntoIter, + iter: as IntoIterator>::IntoIter, value: Option, } impl MapDeserializer { #[inline] - fn new(map: BTreeMap) -> Self { + fn new(map: IndexMap) -> Self { MapDeserializer { iter: map.into_iter(), value: None, diff --git a/value/src/lib.rs b/value/src/lib.rs index fd44e9e6..c04da07f 100644 --- a/value/src/lib.rs +++ b/value/src/lib.rs @@ -10,7 +10,6 @@ mod value_serde; mod variables; use std::borrow::{Borrow, Cow}; -use std::collections::BTreeMap; use std::convert::{TryFrom, TryInto}; use std::fmt::{self, Display, Formatter, Write}; use std::iter::FromIterator; @@ -18,9 +17,12 @@ use std::ops::Deref; use std::sync::Arc; use bytes::Bytes; +use indexmap::IndexMap; use serde::{Deserialize, Deserializer, Serialize, Serializer}; pub use deserializer::{from_value, DeserializerError}; +#[doc(hidden)] +pub use indexmap; pub use serde_json::Number; pub use serializer::{to_value, SerializerError}; @@ -139,7 +141,7 @@ pub enum ConstValue { /// A list of values. List(Vec), /// An object. This is a map of keys to values. - Object(BTreeMap), + Object(IndexMap), } impl PartialEq for ConstValue { @@ -254,8 +256,8 @@ impl> From> for ConstValue { } } -impl From> for ConstValue { - fn from(f: BTreeMap) -> Self { +impl From> for ConstValue { + fn from(f: IndexMap) -> Self { ConstValue::Object(f) } } @@ -364,7 +366,7 @@ pub enum Value { /// A list of values. List(Vec), /// An object. This is a map of keys to values. - Object(BTreeMap), + Object(IndexMap), } impl Value { diff --git a/value/src/macros.rs b/value/src/macros.rs index d39da636..4be7cc12 100644 --- a/value/src/macros.rs +++ b/value/src/macros.rs @@ -191,7 +191,7 @@ macro_rules! value_internal { ({ $($tt:tt)+ }) => { $crate::ConstValue::Object({ - let mut object = std::collections::BTreeMap::new(); + let mut object = $crate::indexmap::IndexMap::new(); $crate::value_internal!(@object object () ($($tt)+) ($($tt)+)); object }) @@ -227,7 +227,7 @@ macro_rules! value_expect_expr_comma { #[cfg(test)] mod tests { use crate::{ConstValue, Name}; - use std::collections::BTreeMap; + use indexmap::IndexMap; #[test] fn test_macro() { @@ -254,7 +254,7 @@ mod tests { ) ); assert_eq!(value!({"a": 10, "b": true}), { - let mut map = BTreeMap::new(); + let mut map = IndexMap::new(); map.insert(Name::new("a"), ConstValue::Number(10.into())); map.insert(Name::new("b"), ConstValue::Boolean(true)); ConstValue::Object(map) diff --git a/value/src/serializer.rs b/value/src/serializer.rs index 1db14dfe..ac198822 100644 --- a/value/src/serializer.rs +++ b/value/src/serializer.rs @@ -1,7 +1,7 @@ -use std::collections::BTreeMap; use std::error::Error; use std::fmt; +use indexmap::IndexMap; use serde::ser::{self, Impossible}; use serde::Serialize; @@ -180,7 +180,7 @@ impl ser::Serializer for Serializer { T: ser::Serialize, { value.serialize(self).map(|v| { - let mut map = BTreeMap::new(); + let mut map = IndexMap::new(); map.insert(Name::new(variant), v); ConstValue::Object(map) }) @@ -222,7 +222,7 @@ impl ser::Serializer for Serializer { #[inline] fn serialize_map(self, _len: Option) -> Result { Ok(SerializeMap { - map: BTreeMap::new(), + map: IndexMap::new(), key: None, }) } @@ -233,7 +233,7 @@ impl ser::Serializer for Serializer { _name: &'static str, _len: usize, ) -> Result { - Ok(SerializeStruct(BTreeMap::new())) + Ok(SerializeStruct(IndexMap::new())) } #[inline] @@ -244,7 +244,7 @@ impl ser::Serializer for Serializer { variant: &'static str, _len: usize, ) -> Result { - Ok(SerializeStructVariant(Name::new(variant), BTreeMap::new())) + Ok(SerializeStructVariant(Name::new(variant), IndexMap::new())) } #[inline] @@ -337,14 +337,14 @@ impl ser::SerializeTupleVariant for SerializeTupleVariant { #[inline] fn end(self) -> Result { - let mut map = BTreeMap::new(); + let mut map = IndexMap::new(); map.insert(self.0, ConstValue::List(self.1)); Ok(ConstValue::Object(map)) } } struct SerializeMap { - map: BTreeMap, + map: IndexMap, key: Option, } @@ -378,7 +378,7 @@ impl ser::SerializeMap for SerializeMap { } } -struct SerializeStruct(BTreeMap); +struct SerializeStruct(IndexMap); impl ser::SerializeStruct for SerializeStruct { type Ok = ConstValue; @@ -405,7 +405,7 @@ impl ser::SerializeStruct for SerializeStruct { } } -struct SerializeStructVariant(Name, BTreeMap); +struct SerializeStructVariant(Name, IndexMap); impl ser::SerializeStructVariant for SerializeStructVariant { type Ok = ConstValue; @@ -428,7 +428,7 @@ impl ser::SerializeStructVariant for SerializeStructVariant { #[inline] fn end(self) -> Result { - let mut map = BTreeMap::new(); + let mut map = IndexMap::new(); map.insert(self.0, ConstValue::Object(self.1)); Ok(ConstValue::Object(map)) } diff --git a/value/src/value_serde.rs b/value/src/value_serde.rs index 7e2ffb7a..3d6aa76a 100644 --- a/value/src/value_serde.rs +++ b/value/src/value_serde.rs @@ -1,6 +1,6 @@ -use std::collections::BTreeMap; use std::fmt::{self, Formatter}; +use indexmap::IndexMap; use serde::de::{Error as DeError, MapAccess, SeqAccess, Visitor}; use serde::ser::Error as SerError; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -137,7 +137,7 @@ impl<'de> Deserialize<'de> for ConstValue { where A: MapAccess<'de>, { - let mut map = BTreeMap::new(); + let mut map = IndexMap::new(); while let Some((name, value)) = visitor.next_entry()? { map.insert(name, value); } @@ -280,7 +280,7 @@ impl<'de> Deserialize<'de> for Value { where A: MapAccess<'de>, { - let mut map = BTreeMap::new(); + let mut map = IndexMap::new(); while let Some((name, value)) = visitor.next_entry()? { map.insert(name, value); } diff --git a/value/src/variables.rs b/value/src/variables.rs index 7e9d7d1e..8599085c 100644 --- a/value/src/variables.rs +++ b/value/src/variables.rs @@ -36,7 +36,7 @@ impl Variables { #[must_use] pub fn from_value(value: ConstValue) -> Self { match value { - ConstValue::Object(obj) => Self(obj), + ConstValue::Object(obj) => Self(obj.into_iter().collect()), _ => Self::default(), } } @@ -55,7 +55,7 @@ impl Variables { /// Get the variables as a GraphQL value. #[must_use] pub fn into_value(self) -> ConstValue { - ConstValue::Object(self.0) + ConstValue::Object(self.0.into_iter().collect()) } } From fb38b548d10b7501f07df05c4f174e36b55ef6d7 Mon Sep 17 00:00:00 2001 From: Sunli Date: Tue, 26 Oct 2021 20:44:45 +0800 Subject: [PATCH 12/65] Release 2.10.6 async-graphql@2.10.6 async-graphql-actix-web@2.10.6 async-graphql-axum@2.10.6 async-graphql-derive@2.10.6 async-graphql-parser@2.10.6 async-graphql-poem@2.10.6 async-graphql-rocket@2.10.6 async-graphql-tide@2.10.6 async-graphql-value@2.10.6 async-graphql-warp@2.10.6 Generated by cargo-workspaces --- Cargo.toml | 8 ++++---- derive/Cargo.toml | 4 ++-- integrations/actix-web/Cargo.toml | 4 ++-- integrations/axum/Cargo.toml | 4 ++-- integrations/poem/Cargo.toml | 4 ++-- integrations/rocket/Cargo.toml | 4 ++-- integrations/tide/Cargo.toml | 4 ++-- integrations/warp/Cargo.toml | 4 ++-- parser/Cargo.toml | 4 ++-- value/Cargo.toml | 2 +- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index da7114db..e277a5c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql" -version = "2.10.5" +version = "2.10.6" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "A GraphQL server library implemented in Rust" @@ -24,9 +24,9 @@ decimal = ["rust_decimal"] cbor = ["serde_cbor"] [dependencies] -async-graphql-derive = { path = "derive", version = "=2.10.5" } -async-graphql-value = { path = "value", version = "=2.10.5" } -async-graphql-parser = { path = "parser", version = "=2.10.5" } +async-graphql-derive = { path = "derive", version = "2.10.6" } +async-graphql-value = { path = "value", version = "2.10.6" } +async-graphql-parser = { path = "parser", version = "2.10.6" } async-stream = "0.3.0" async-trait = "0.1.48" diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 256b44c3..9e3de277 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-derive" -version = "2.10.5" +version = "2.10.6" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "Macros for async-graphql" @@ -15,7 +15,7 @@ categories = ["network-programming", "asynchronous"] proc-macro = true [dependencies] -async-graphql-parser = { path = "../parser", version = "=2.10.5" } +async-graphql-parser = { path = "../parser", version = "2.10.6" } proc-macro2 = "1.0.24" syn = { version = "1.0.64", features = ["full", "extra-traits", "visit-mut", "visit"] } quote = "1.0.9" diff --git a/integrations/actix-web/Cargo.toml b/integrations/actix-web/Cargo.toml index 19fc1ed3..1f159e32 100644 --- a/integrations/actix-web/Cargo.toml +++ b/integrations/actix-web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-actix-web" -version = "2.10.5" +version = "2.10.6" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "async-graphql for actix-web" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "=2.10.5" } +async-graphql = { path = "../..", version = "2.10.6" } actix = "0.10.0" actix-http = "2.2.0" diff --git a/integrations/axum/Cargo.toml b/integrations/axum/Cargo.toml index 9da1322d..1cb776b7 100644 --- a/integrations/axum/Cargo.toml +++ b/integrations/axum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-axum" -version = "2.10.5" +version = "2.10.6" authors = ["sunli "] edition = "2018" description = "async-graphql for axum" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql", "axum"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "=2.10.5" } +async-graphql = { path = "../..", version = "2.10.6" } async-trait = "0.1.51" axum = { version = "0.2.5", features = ["ws", "headers"] } diff --git a/integrations/poem/Cargo.toml b/integrations/poem/Cargo.toml index ef5dcad6..a8740457 100644 --- a/integrations/poem/Cargo.toml +++ b/integrations/poem/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-poem" -version = "2.10.5" +version = "2.10.6" authors = ["sunli "] edition = "2018" description = "async-graphql for poem" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql", "poem"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "=2.10.5" } +async-graphql = { path = "../..", version = "2.10.6" } poem = { version = "1.0.7", features = ["websocket"] } futures-util = { version = "0.3.13", default-features = false } diff --git a/integrations/rocket/Cargo.toml b/integrations/rocket/Cargo.toml index 1a9f905d..68a3aa56 100644 --- a/integrations/rocket/Cargo.toml +++ b/integrations/rocket/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-rocket" -version = "2.10.5" +version = "2.10.6" authors = ["Daniel Wiesenberg "] edition = "2018" description = "async-graphql for Rocket.rs" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql", "rocket"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "=2.10.5" } +async-graphql = { path = "../..", version = "2.10.6" } rocket = { version = "0.5.0-rc.1", default-features = false } serde = "1.0.126" diff --git a/integrations/tide/Cargo.toml b/integrations/tide/Cargo.toml index 4c43137b..54ff3437 100644 --- a/integrations/tide/Cargo.toml +++ b/integrations/tide/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-tide" -version = "2.10.5" +version = "2.10.6" authors = ["vkill "] edition = "2018" description = "async-graphql for tide" @@ -16,7 +16,7 @@ default = ["websocket"] websocket = ["tide-websockets"] [dependencies] -async-graphql = { path = "../..", version = "=2.10.5" } +async-graphql = { path = "../..", version = "2.10.6" } async-trait = "0.1.48" futures-util = "0.3.13" serde_json = "1.0.64" diff --git a/integrations/warp/Cargo.toml b/integrations/warp/Cargo.toml index d0e94ad0..3674fee3 100644 --- a/integrations/warp/Cargo.toml +++ b/integrations/warp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-warp" -version = "2.10.5" +version = "2.10.6" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "async-graphql for warp" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "=2.10.5" } +async-graphql = { path = "../..", version = "2.10.6" } warp = { version = "0.3.0", default-features = false, features = ["websocket"] } futures-util = { version = "0.3.13", default-features = false, features = ["sink"] } diff --git a/parser/Cargo.toml b/parser/Cargo.toml index dfe547c9..78d291c1 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-parser" -version = "2.10.5" +version = "2.10.6" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "GraphQL query parser for async-graphql" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql-value = { path = "../value", version = "=2.10.5" } +async-graphql-value = { path = "../value", version = "2.10.6" } pest = "2.1.3" pest_derive = "2.1.0" serde_json = "1.0.64" diff --git a/value/Cargo.toml b/value/Cargo.toml index 0d0f1374..deb58344 100644 --- a/value/Cargo.toml +++ b/value/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-value" -version = "2.10.5" +version = "2.10.6" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "GraphQL value for async-graphql" From 16f216c18f758fbf0e8bdd69e80c2e6f595370c2 Mon Sep 17 00:00:00 2001 From: Sunli Date: Tue, 26 Oct 2021 20:45:34 +0800 Subject: [PATCH 13/65] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fe6711d..b200f5ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## [2.10.6] 2021-10-26 - Add derived for object & simple object & complex object. [#667](https://github.com/async-graphql/async-graphql/pull/667) [#670](https://github.com/async-graphql/async-graphql/pull/670) - Respect query object field order. [#612](https://github.com/async-graphql/async-graphql/issues/612) From 886eeb71294b7b57db2492c12beaf5db0e59c406 Mon Sep 17 00:00:00 2001 From: Sunli Date: Wed, 27 Oct 2021 13:57:05 +0800 Subject: [PATCH 14/65] Bump poem from `1.0.7` to `1.0.11` --- integrations/poem/Cargo.toml | 2 +- integrations/poem/src/extractor.rs | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/integrations/poem/Cargo.toml b/integrations/poem/Cargo.toml index a8740457..130bb17d 100644 --- a/integrations/poem/Cargo.toml +++ b/integrations/poem/Cargo.toml @@ -14,7 +14,7 @@ categories = ["network-programming", "asynchronous"] [dependencies] async-graphql = { path = "../..", version = "2.10.6" } -poem = { version = "1.0.7", features = ["websocket"] } +poem = { version = "1.0.11", features = ["websocket"] } futures-util = { version = "0.3.13", default-features = false } serde_json = "1.0.66" tokio-util = { version = "0.6.7", features = ["compat"] } diff --git a/integrations/poem/src/extractor.rs b/integrations/poem/src/extractor.rs index f0a602f0..4776b2e2 100644 --- a/integrations/poem/src/extractor.rs +++ b/integrations/poem/src/extractor.rs @@ -1,4 +1,5 @@ use async_graphql::http::MultipartOptions; +use poem::error::BadRequest; use poem::http::{header, Method}; use poem::web::Query; use poem::{async_trait, Error, FromRequest, Request, RequestBody, Result}; @@ -49,7 +50,7 @@ impl<'a> FromRequest<'a> for GraphQLRequest { .await? .0 .into_single() - .map_err(Error::bad_request)?, + .map_err(BadRequest)?, )) } } @@ -63,10 +64,7 @@ impl<'a> FromRequest<'a> for GraphQLBatchRequest { async fn from_request(req: &'a Request, body: &mut RequestBody) -> Result { if req.method() == Method::GET { - let req = Query::from_request(req, body) - .await - .map_err(Error::bad_request)? - .0; + let req = Query::from_request(req, body).await.map_err(BadRequest)?.0; Ok(Self(async_graphql::BatchRequest::Single(req))) } else { let content_type = req @@ -81,7 +79,7 @@ impl<'a> FromRequest<'a> for GraphQLBatchRequest { MultipartOptions::default(), ) .await - .map_err(Error::bad_request)?, + .map_err(BadRequest)?, )) } } From f1fda411b9a97e1b0ab108a3293995c854b2190c Mon Sep 17 00:00:00 2001 From: Sunli Date: Wed, 27 Oct 2021 13:58:47 +0800 Subject: [PATCH 15/65] Release 2.10.7 async-graphql@2.10.7 async-graphql-actix-web@2.10.7 async-graphql-axum@2.10.7 async-graphql-derive@2.10.7 async-graphql-parser@2.10.7 async-graphql-poem@2.10.7 async-graphql-rocket@2.10.7 async-graphql-tide@2.10.7 async-graphql-value@2.10.7 async-graphql-warp@2.10.7 Generated by cargo-workspaces --- Cargo.toml | 8 ++++---- derive/Cargo.toml | 4 ++-- integrations/actix-web/Cargo.toml | 4 ++-- integrations/axum/Cargo.toml | 4 ++-- integrations/poem/Cargo.toml | 4 ++-- integrations/rocket/Cargo.toml | 4 ++-- integrations/tide/Cargo.toml | 4 ++-- integrations/warp/Cargo.toml | 4 ++-- parser/Cargo.toml | 4 ++-- value/Cargo.toml | 2 +- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e277a5c2..7319e01f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql" -version = "2.10.6" +version = "2.10.7" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "A GraphQL server library implemented in Rust" @@ -24,9 +24,9 @@ decimal = ["rust_decimal"] cbor = ["serde_cbor"] [dependencies] -async-graphql-derive = { path = "derive", version = "2.10.6" } -async-graphql-value = { path = "value", version = "2.10.6" } -async-graphql-parser = { path = "parser", version = "2.10.6" } +async-graphql-derive = { path = "derive", version = "=2.10.7" } +async-graphql-value = { path = "value", version = "=2.10.7" } +async-graphql-parser = { path = "parser", version = "=2.10.7" } async-stream = "0.3.0" async-trait = "0.1.48" diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 9e3de277..4f50f32e 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-derive" -version = "2.10.6" +version = "2.10.7" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "Macros for async-graphql" @@ -15,7 +15,7 @@ categories = ["network-programming", "asynchronous"] proc-macro = true [dependencies] -async-graphql-parser = { path = "../parser", version = "2.10.6" } +async-graphql-parser = { path = "../parser", version = "=2.10.7" } proc-macro2 = "1.0.24" syn = { version = "1.0.64", features = ["full", "extra-traits", "visit-mut", "visit"] } quote = "1.0.9" diff --git a/integrations/actix-web/Cargo.toml b/integrations/actix-web/Cargo.toml index 1f159e32..223a726a 100644 --- a/integrations/actix-web/Cargo.toml +++ b/integrations/actix-web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-actix-web" -version = "2.10.6" +version = "2.10.7" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "async-graphql for actix-web" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "2.10.6" } +async-graphql = { path = "../..", version = "=2.10.7" } actix = "0.10.0" actix-http = "2.2.0" diff --git a/integrations/axum/Cargo.toml b/integrations/axum/Cargo.toml index 1cb776b7..df52a5c1 100644 --- a/integrations/axum/Cargo.toml +++ b/integrations/axum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-axum" -version = "2.10.6" +version = "2.10.7" authors = ["sunli "] edition = "2018" description = "async-graphql for axum" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql", "axum"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "2.10.6" } +async-graphql = { path = "../..", version = "=2.10.7" } async-trait = "0.1.51" axum = { version = "0.2.5", features = ["ws", "headers"] } diff --git a/integrations/poem/Cargo.toml b/integrations/poem/Cargo.toml index 130bb17d..78594a7a 100644 --- a/integrations/poem/Cargo.toml +++ b/integrations/poem/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-poem" -version = "2.10.6" +version = "2.10.7" authors = ["sunli "] edition = "2018" description = "async-graphql for poem" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql", "poem"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "2.10.6" } +async-graphql = { path = "../..", version = "=2.10.7" } poem = { version = "1.0.11", features = ["websocket"] } futures-util = { version = "0.3.13", default-features = false } diff --git a/integrations/rocket/Cargo.toml b/integrations/rocket/Cargo.toml index 68a3aa56..f4263c51 100644 --- a/integrations/rocket/Cargo.toml +++ b/integrations/rocket/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-rocket" -version = "2.10.6" +version = "2.10.7" authors = ["Daniel Wiesenberg "] edition = "2018" description = "async-graphql for Rocket.rs" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql", "rocket"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "2.10.6" } +async-graphql = { path = "../..", version = "=2.10.7" } rocket = { version = "0.5.0-rc.1", default-features = false } serde = "1.0.126" diff --git a/integrations/tide/Cargo.toml b/integrations/tide/Cargo.toml index 54ff3437..8cf429cb 100644 --- a/integrations/tide/Cargo.toml +++ b/integrations/tide/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-tide" -version = "2.10.6" +version = "2.10.7" authors = ["vkill "] edition = "2018" description = "async-graphql for tide" @@ -16,7 +16,7 @@ default = ["websocket"] websocket = ["tide-websockets"] [dependencies] -async-graphql = { path = "../..", version = "2.10.6" } +async-graphql = { path = "../..", version = "=2.10.7" } async-trait = "0.1.48" futures-util = "0.3.13" serde_json = "1.0.64" diff --git a/integrations/warp/Cargo.toml b/integrations/warp/Cargo.toml index 3674fee3..f032e1cb 100644 --- a/integrations/warp/Cargo.toml +++ b/integrations/warp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-warp" -version = "2.10.6" +version = "2.10.7" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "async-graphql for warp" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "2.10.6" } +async-graphql = { path = "../..", version = "=2.10.7" } warp = { version = "0.3.0", default-features = false, features = ["websocket"] } futures-util = { version = "0.3.13", default-features = false, features = ["sink"] } diff --git a/parser/Cargo.toml b/parser/Cargo.toml index 78d291c1..fafd08be 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-parser" -version = "2.10.6" +version = "2.10.7" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "GraphQL query parser for async-graphql" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql-value = { path = "../value", version = "2.10.6" } +async-graphql-value = { path = "../value", version = "=2.10.7" } pest = "2.1.3" pest_derive = "2.1.0" serde_json = "1.0.64" diff --git a/value/Cargo.toml b/value/Cargo.toml index deb58344..967d055f 100644 --- a/value/Cargo.toml +++ b/value/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-value" -version = "2.10.6" +version = "2.10.7" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "GraphQL value for async-graphql" From f34334bbd2fb4c1af70d438085e4ceca7d4b142b Mon Sep 17 00:00:00 2001 From: Sunli Date: Wed, 27 Oct 2021 20:06:33 +0800 Subject: [PATCH 16/65] Bump poem to `1.0.13` --- examples | 2 +- integrations/poem/Cargo.toml | 2 +- integrations/poem/src/extractor.rs | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/examples b/examples index 290ef715..1683aac1 160000 --- a/examples +++ b/examples @@ -1 +1 @@ -Subproject commit 290ef715b2a6be346acc16ddd406f844e8ed3bfd +Subproject commit 1683aac1883a6acf27747bf3e22491fa13aa538f diff --git a/integrations/poem/Cargo.toml b/integrations/poem/Cargo.toml index 78594a7a..a8ad7713 100644 --- a/integrations/poem/Cargo.toml +++ b/integrations/poem/Cargo.toml @@ -14,7 +14,7 @@ categories = ["network-programming", "asynchronous"] [dependencies] async-graphql = { path = "../..", version = "=2.10.7" } -poem = { version = "1.0.11", features = ["websocket"] } +poem = { version = "1.0.13", features = ["websocket"] } futures-util = { version = "0.3.13", default-features = false } serde_json = "1.0.66" tokio-util = { version = "0.6.7", features = ["compat"] } diff --git a/integrations/poem/src/extractor.rs b/integrations/poem/src/extractor.rs index 4776b2e2..4e09a66e 100644 --- a/integrations/poem/src/extractor.rs +++ b/integrations/poem/src/extractor.rs @@ -64,7 +64,7 @@ impl<'a> FromRequest<'a> for GraphQLBatchRequest { async fn from_request(req: &'a Request, body: &mut RequestBody) -> Result { if req.method() == Method::GET { - let req = Query::from_request(req, body).await.map_err(BadRequest)?.0; + let req = Query::from_request(req, body).await?.0; Ok(Self(async_graphql::BatchRequest::Single(req))) } else { let content_type = req @@ -78,8 +78,7 @@ impl<'a> FromRequest<'a> for GraphQLBatchRequest { body.take()?.into_async_read().compat(), MultipartOptions::default(), ) - .await - .map_err(BadRequest)?, + .await?, )) } } From 619dcd7854fd0f4636c7cb2103ab470348d5b7f7 Mon Sep 17 00:00:00 2001 From: Sunli Date: Wed, 27 Oct 2021 20:08:12 +0800 Subject: [PATCH 17/65] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b200f5ee..c683eee1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.10.8] 2021-10-26 + +- [async-graphql-poem] Bump poem to `1.0.13`. + ## [2.10.6] 2021-10-26 - Add derived for object & simple object & complex object. [#667](https://github.com/async-graphql/async-graphql/pull/667) [#670](https://github.com/async-graphql/async-graphql/pull/670) From 9610ae4695e08c20122af284968a16ae76ca4fc4 Mon Sep 17 00:00:00 2001 From: Sunli Date: Wed, 27 Oct 2021 20:08:35 +0800 Subject: [PATCH 18/65] Release 2.10.8 async-graphql@2.10.8 async-graphql-actix-web@2.10.8 async-graphql-axum@2.10.8 async-graphql-derive@2.10.8 async-graphql-parser@2.10.8 async-graphql-poem@2.10.8 async-graphql-rocket@2.10.8 async-graphql-tide@2.10.8 async-graphql-value@2.10.8 async-graphql-warp@2.10.8 Generated by cargo-workspaces --- Cargo.toml | 8 ++++---- derive/Cargo.toml | 4 ++-- integrations/actix-web/Cargo.toml | 4 ++-- integrations/axum/Cargo.toml | 4 ++-- integrations/poem/Cargo.toml | 4 ++-- integrations/rocket/Cargo.toml | 4 ++-- integrations/tide/Cargo.toml | 4 ++-- integrations/warp/Cargo.toml | 4 ++-- parser/Cargo.toml | 4 ++-- value/Cargo.toml | 2 +- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7319e01f..0be7b582 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql" -version = "2.10.7" +version = "2.10.8" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "A GraphQL server library implemented in Rust" @@ -24,9 +24,9 @@ decimal = ["rust_decimal"] cbor = ["serde_cbor"] [dependencies] -async-graphql-derive = { path = "derive", version = "=2.10.7" } -async-graphql-value = { path = "value", version = "=2.10.7" } -async-graphql-parser = { path = "parser", version = "=2.10.7" } +async-graphql-derive = { path = "derive", version = "2.10.8" } +async-graphql-value = { path = "value", version = "2.10.8" } +async-graphql-parser = { path = "parser", version = "2.10.8" } async-stream = "0.3.0" async-trait = "0.1.48" diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 4f50f32e..98546a2e 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-derive" -version = "2.10.7" +version = "2.10.8" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "Macros for async-graphql" @@ -15,7 +15,7 @@ categories = ["network-programming", "asynchronous"] proc-macro = true [dependencies] -async-graphql-parser = { path = "../parser", version = "=2.10.7" } +async-graphql-parser = { path = "../parser", version = "2.10.8" } proc-macro2 = "1.0.24" syn = { version = "1.0.64", features = ["full", "extra-traits", "visit-mut", "visit"] } quote = "1.0.9" diff --git a/integrations/actix-web/Cargo.toml b/integrations/actix-web/Cargo.toml index 223a726a..7641ee7b 100644 --- a/integrations/actix-web/Cargo.toml +++ b/integrations/actix-web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-actix-web" -version = "2.10.7" +version = "2.10.8" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "async-graphql for actix-web" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "=2.10.7" } +async-graphql = { path = "../..", version = "2.10.8" } actix = "0.10.0" actix-http = "2.2.0" diff --git a/integrations/axum/Cargo.toml b/integrations/axum/Cargo.toml index df52a5c1..1bc4b616 100644 --- a/integrations/axum/Cargo.toml +++ b/integrations/axum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-axum" -version = "2.10.7" +version = "2.10.8" authors = ["sunli "] edition = "2018" description = "async-graphql for axum" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql", "axum"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "=2.10.7" } +async-graphql = { path = "../..", version = "2.10.8" } async-trait = "0.1.51" axum = { version = "0.2.5", features = ["ws", "headers"] } diff --git a/integrations/poem/Cargo.toml b/integrations/poem/Cargo.toml index a8ad7713..82418d13 100644 --- a/integrations/poem/Cargo.toml +++ b/integrations/poem/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-poem" -version = "2.10.7" +version = "2.10.8" authors = ["sunli "] edition = "2018" description = "async-graphql for poem" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql", "poem"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "=2.10.7" } +async-graphql = { path = "../..", version = "2.10.8" } poem = { version = "1.0.13", features = ["websocket"] } futures-util = { version = "0.3.13", default-features = false } diff --git a/integrations/rocket/Cargo.toml b/integrations/rocket/Cargo.toml index f4263c51..f5b90af7 100644 --- a/integrations/rocket/Cargo.toml +++ b/integrations/rocket/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-rocket" -version = "2.10.7" +version = "2.10.8" authors = ["Daniel Wiesenberg "] edition = "2018" description = "async-graphql for Rocket.rs" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql", "rocket"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "=2.10.7" } +async-graphql = { path = "../..", version = "2.10.8" } rocket = { version = "0.5.0-rc.1", default-features = false } serde = "1.0.126" diff --git a/integrations/tide/Cargo.toml b/integrations/tide/Cargo.toml index 8cf429cb..daeb17fb 100644 --- a/integrations/tide/Cargo.toml +++ b/integrations/tide/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-tide" -version = "2.10.7" +version = "2.10.8" authors = ["vkill "] edition = "2018" description = "async-graphql for tide" @@ -16,7 +16,7 @@ default = ["websocket"] websocket = ["tide-websockets"] [dependencies] -async-graphql = { path = "../..", version = "=2.10.7" } +async-graphql = { path = "../..", version = "2.10.8" } async-trait = "0.1.48" futures-util = "0.3.13" serde_json = "1.0.64" diff --git a/integrations/warp/Cargo.toml b/integrations/warp/Cargo.toml index f032e1cb..79cc1501 100644 --- a/integrations/warp/Cargo.toml +++ b/integrations/warp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-warp" -version = "2.10.7" +version = "2.10.8" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "async-graphql for warp" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "=2.10.7" } +async-graphql = { path = "../..", version = "2.10.8" } warp = { version = "0.3.0", default-features = false, features = ["websocket"] } futures-util = { version = "0.3.13", default-features = false, features = ["sink"] } diff --git a/parser/Cargo.toml b/parser/Cargo.toml index fafd08be..f28bf7ec 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-parser" -version = "2.10.7" +version = "2.10.8" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "GraphQL query parser for async-graphql" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql-value = { path = "../value", version = "=2.10.7" } +async-graphql-value = { path = "../value", version = "2.10.8" } pest = "2.1.3" pest_derive = "2.1.0" serde_json = "1.0.64" diff --git a/value/Cargo.toml b/value/Cargo.toml index 967d055f..1891a069 100644 --- a/value/Cargo.toml +++ b/value/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-value" -version = "2.10.7" +version = "2.10.8" authors = ["sunli ", "Koxiaet"] edition = "2018" description = "GraphQL value for async-graphql" From fa34a8ae683110eb81d7bbc22a2d277dfb59f6af Mon Sep 17 00:00:00 2001 From: Miaxos Date: Wed, 27 Oct 2021 13:37:13 +0000 Subject: [PATCH 19/65] feat: add basic coercion for SimpleObject derived arg --- derive/Cargo.toml | 2 + derive/src/args.rs | 2 + derive/src/simple_object.rs | 99 ++++++++++++++++++++++++++++++----- derive/src/utils.rs | 60 +++++++++++++++++++++ docs/en/src/derived_fields.md | 44 ++++++++++++++++ src/lib.rs | 2 + tests/derived_field.rs | 62 ++++++++++++++++++++++ 7 files changed, 259 insertions(+), 12 deletions(-) diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 9e3de277..6fb64876 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -23,3 +23,5 @@ Inflector = "0.11.4" proc-macro-crate = "1.0.0" darling = "0.12.2" thiserror = "1.0.24" +regex = "1.5" +once_cell = "1.8.0" diff --git a/derive/src/args.rs b/derive/src/args.rs index 6b753c22..5bc5123b 100644 --- a/derive/src/args.rs +++ b/derive/src/args.rs @@ -255,6 +255,8 @@ pub struct ObjectField { pub struct DerivedField { pub name: Option, pub into: Option, + #[darling(default)] + pub owned: Option, } #[derive(FromDeriveInput)] diff --git a/derive/src/simple_object.rs b/derive/src/simple_object.rs index 784f46d9..8a072a29 100644 --- a/derive/src/simple_object.rs +++ b/derive/src/simple_object.rs @@ -7,12 +7,27 @@ use syn::{Error, Ident, Type}; use crate::args::{self, RenameRuleExt, RenameTarget, SimpleObjectField}; use crate::utils::{ - gen_deprecation, generate_guards, get_crate_name, get_rustdoc, visible_fn, GeneratorResult, + derive_type_coercion, gen_deprecation, generate_guards, get_crate_name, get_rustdoc, + visible_fn, DerivedIntoCoercion, GeneratorResult, }; +#[derive(Debug)] struct DerivedFieldMetadata { ident: Ident, into: Type, + owned: Option, + // The into argument for a derive field won't be able to transform everythings: + // Without the specialization from Rust, we can't implement things like From between Vec -> + // Vec or Option -> Option. + // But there are cases which you want to have this coercion derived, so to have it working + // until the specialization feature comes, we manually check coercion for the most usual cases + // which are: + // + // - Vec -> Vec + // - Option -> Option + // - Option> -> Option> + // - Vec> -> Vec> + coercion: DerivedIntoCoercion, } struct SimpleObjectFieldGenerator<'a> { @@ -60,10 +75,30 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult( proc_macro2::TokenStream::from_str(&derived.into.clone().unwrap()).unwrap(), - ); - let derived = DerivedFieldMetadata { ident: name, into }; + ) { + Ok(e) => e, + _ => { + return Err(Error::new_spanned( + &name, + "derived into must be a valid type.", + ) + .into()); + } + }; + + let ty = &field.ty; + + let derived_type = quote! { #into }.to_string(); + let base_type = quote! { #ty }.to_string(); + + let derived = DerivedFieldMetadata { + ident: name, + into, + owned: derived.owned, + coercion: derive_type_coercion(&base_type, &derived_type), + }; processed_fields.push(SimpleObjectFieldGenerator { field, @@ -83,7 +118,6 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult return Err(Error::new_spanned(&ident, "All fields must be named.").into()), }; - let is_derived = derived.is_some(); let ident = if let Some(derived) = derived { &derived.ident } else { @@ -109,12 +143,24 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult quote! { ::std::option::Option::None }, }; let vis = &field.vis; + let derived_coercion = if let Some(derived) = derived { + Some(&derived.coercion) + } else { + None + }; + let ty = if let Some(derived) = derived { &derived.into } else { &field.ty }; + let owned = if let Some(derived) = derived { + derived.owned.unwrap_or(field.owned) + } else { + field.owned + }; + let cache_control = { let public = field.cache_control.is_public(); let max_age = field.cache_control.max_age; @@ -152,7 +198,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult quote! { &self.#base_ident }, @@ -161,16 +207,45 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult quote! { - #block - }, - true => quote! { + // If the field is derived, it means it has a derived_coercion, depending on the coercion + // we have to implement several way to coerce it. + block = match derived_coercion { + Some(DerivedIntoCoercion::Unknown) => quote! { ::std::convert::Into::into(#block) }, + Some(DerivedIntoCoercion::OptionToOption) => quote! { + ::std::option::Option::and_then(#block, |value| ::std::option::Option::Some(::std::convert::Into::into(value))) + }, + Some(DerivedIntoCoercion::VecToVec) => quote! { + { + let mut result = vec![]; + ::std::iter::Extend::extend(&mut result, ::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(#block), |x| ::std::convert::Into::into(::std::clone::Clone::clone(&x)))); + result + } + }, + Some(DerivedIntoCoercion::OptionVecToOptionVec) => quote! { + ::std::option::Option::and_then(#block, |value| ::std::option::Option::Some({ + let mut result = vec![]; + ::std::iter::Extend::extend(&mut result, ::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(value), |x| ::std::convert::Into::into(::std::clone::Clone::clone(&x)))); + result + })) + }, + Some(DerivedIntoCoercion::VecOptionToVecOption) => quote! { + { + let mut result = vec![]; + ::std::iter::Extend::extend(&mut result, ::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(#block), |x| ::std::option::Option::and_then(x, |value| ::std::option::Option::Some( + ::std::convert::Into::into(value) + )))); + result + } + }, + // If the field is not derived, we follow the normal process. + _ => quote! { + #block + }, }; - let ty = match !field.owned { + let ty = match !owned { true => quote! { &#ty }, false => quote! { #ty }, }; diff --git a/derive/src/utils.rs b/derive/src/utils.rs index 394a30d4..6843c056 100644 --- a/derive/src/utils.rs +++ b/derive/src/utils.rs @@ -1,9 +1,11 @@ use std::collections::HashSet; use darling::FromMeta; +use once_cell::sync::Lazy; use proc_macro2::{Span, TokenStream, TokenTree}; use proc_macro_crate::{crate_name, FoundCrate}; use quote::quote; +use regex::Regex; use syn::visit::Visit; use syn::{ Attribute, Error, Expr, ExprPath, FnArg, Ident, ImplItemMethod, Lit, LitStr, Meta, NestedMeta, @@ -533,3 +535,61 @@ pub fn extract_input_args( Ok(args) } + +#[derive(Debug)] +pub enum DerivedIntoCoercion { + Unknown = 1, + VecToVec = 2, + OptionToOption = 3, + OptionVecToOptionVec = 4, + VecOptionToVecOption = 5, +} + +static CHECK_OPTION: Lazy = Lazy::new(|| Regex::new("^Option <(.*?) >$").unwrap()); +static CHECK_VEC: Lazy = Lazy::new(|| Regex::new("^Vec <(.*?) >$").unwrap()); +static CHECK_VEC_OPTION: Lazy = + Lazy::new(|| Regex::new("^Vec < Option <(.*?)> >$").unwrap()); +static CHECK_OPTION_VEC: Lazy = + Lazy::new(|| Regex::new("^Option < Vec <(.*?)> >$").unwrap()); + +/// The into argument for a derive field won't be able to transform everythings: +/// Without the specialization from Rust, we can't implement things like From between Vec -> +/// Vec or Option -> Option. +/// But there are cases which you want to have this coercion derived, so to have it working +/// until the specialization feature comes, we manually check coercion for the most usual cases +/// which are: +/// +/// - Vec -> Vec +/// - Option -> Option +/// - Option> -> Option> +/// - Vec> -> Vec> +pub fn derive_type_coercion, S2: AsRef>( + base_type: S1, + target_type: S2, +) -> DerivedIntoCoercion { + if CHECK_OPTION_VEC.find(base_type.as_ref()).is_some() + && CHECK_OPTION_VEC.find(target_type.as_ref()).is_some() + { + return DerivedIntoCoercion::OptionVecToOptionVec; + } + + if CHECK_VEC_OPTION.find(base_type.as_ref()).is_some() + && CHECK_VEC_OPTION.find(target_type.as_ref()).is_some() + { + return DerivedIntoCoercion::VecOptionToVecOption; + } + + if CHECK_VEC.find(base_type.as_ref()).is_some() + && CHECK_VEC.find(target_type.as_ref()).is_some() + { + return DerivedIntoCoercion::VecToVec; + } + + if CHECK_OPTION.find(base_type.as_ref()).is_some() + && CHECK_OPTION.find(target_type.as_ref()).is_some() + { + return DerivedIntoCoercion::OptionToOption; + } + + DerivedIntoCoercion::Unknown +} diff --git a/docs/en/src/derived_fields.md b/docs/en/src/derived_fields.md index 23c1fa63..6ec4c016 100644 --- a/docs/en/src/derived_fields.md +++ b/docs/en/src/derived_fields.md @@ -59,3 +59,47 @@ type Query { duration_rfc3339(arg: String): DateRFC3339! } ``` + +## Wrapper types + +A derived field won't be able to manage everythings easily: without the specialization from the Rust language, you won't be able to implement specialized trait like: +``` +impl From> for Vec { + ... +} +``` + +So you wouldn't be able to generate derived fields for existing wrapper type structures like `Vec` or `Option`. But when you implement a `From for T` you should be able to derived a `From> for Vec` and a `From> for Option`, so a coercion mecanism has been included so you'll be able to use the derived macro argument with `Vec` and `Option`. + +This coercion mecanism impose these derived to be `owned`. + +### Example + +```rust +#[derive(Serialize, Deserialize, Clone)] +struct ValueDerived(String); + +#[derive(Serialize, Deserialize, Clone)] +struct ValueDerived2(String); + +scalar!(ValueDerived); +scalar!(ValueDerived2); + +impl From for ValueDerived2 { + fn from(value: ValueDerived) -> Self { + ValueDerived2(value.0) + } +} + +#[derive(SimpleObject)] +struct TestObj { + #[graphql(derived(owned, name = "value2", into = "Option"))] + pub value1: Option, + #[graphql(derived(owned, name = "value_vec_2", into = "Vec"))] + pub value_vec_1: Vec, + #[graphql(derived(owned, name = "value_opt_vec_2", into = "Option>"))] + pub value_opt_vec_1: Option>, + #[graphql(derived(owned, name = "value_vec_opt_2", into = "Vec>"))] + pub value_vec_opt_1: Vec>, +} +``` diff --git a/src/lib.rs b/src/lib.rs index 123672b0..20d422a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -467,6 +467,8 @@ pub use async_graphql_derive::Object; /// |--------------|------------------------------------------|------------ |----------| /// | name | Generated derived field name | string | N | /// | into | Type to derived an into | string | Y | +/// | into | Type to derived an into | string | Y | +/// | owned | Field resolver return a ownedship value | bool | Y | /// /// /// # Examples diff --git a/tests/derived_field.rs b/tests/derived_field.rs index 304802e1..03f718ed 100644 --- a/tests/derived_field.rs +++ b/tests/derived_field.rs @@ -102,6 +102,68 @@ pub async fn test_derived_field_simple_object() { ); } +#[tokio::test] +pub async fn test_derived_field_simple_object_option() { + use serde::{Deserialize, Serialize}; + + struct Query; + + #[derive(Serialize, Deserialize, Clone)] + struct ValueDerived(String); + + #[derive(Serialize, Deserialize, Clone)] + struct ValueDerived2(String); + + scalar!(ValueDerived); + scalar!(ValueDerived2); + + impl From for ValueDerived2 { + fn from(value: ValueDerived) -> Self { + ValueDerived2(value.0) + } + } + + #[derive(SimpleObject)] + struct TestObj { + #[graphql(derived(owned, name = "value2", into = "Option"))] + pub value1: Option, + #[graphql(derived(owned, name = "value_vec_2", into = "Vec"))] + pub value_vec_1: Vec, + #[graphql(derived(owned, name = "value_opt_vec_2", into = "Option>"))] + pub value_opt_vec_1: Option>, + #[graphql(derived(owned, name = "value_vec_opt_2", into = "Vec>"))] + pub value_vec_opt_1: Vec>, + } + + #[Object] + impl Query { + async fn test(&self) -> TestObj { + TestObj { + value1: Some(ValueDerived("Test".to_string())), + value_vec_1: vec![ValueDerived("Test".to_string())], + value_opt_vec_1: Some(vec![ValueDerived("Test".to_string())]), + value_vec_opt_1: vec![Some(ValueDerived("Test".to_string()))], + } + } + } + + let query = "{ test { value1 value2 valueVec1 valueVec2 valueOptVec1 valueOptVec2} }"; + let schema = Schema::new(Query, EmptyMutation, EmptySubscription); + assert_eq!( + schema.execute(query).await.data, + value!({ + "test": { + "value1": "Test", + "value2": "Test", + "valueVec1": vec!["Test"], + "valueVec2": vec!["Test"], + "valueOptVec1": vec!["Test"], + "valueOptVec2": vec!["Test"], + } + }) + ); +} + #[tokio::test] pub async fn test_derived_field_complex_object() { use serde::{Deserialize, Serialize}; From cf407adce07e14fbb73536ba38ee65d50c76b86c Mon Sep 17 00:00:00 2001 From: Miaxos Date: Thu, 28 Oct 2021 01:42:06 +0000 Subject: [PATCH 20/65] fix: do not use extend, useless --- derive/src/simple_object.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/derive/src/simple_object.rs b/derive/src/simple_object.rs index 8a072a29..46340afb 100644 --- a/derive/src/simple_object.rs +++ b/derive/src/simple_object.rs @@ -218,25 +218,19 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult quote! { { - let mut result = vec![]; - ::std::iter::Extend::extend(&mut result, ::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(#block), |x| ::std::convert::Into::into(::std::clone::Clone::clone(&x)))); - result + ::std::iter::Iterator::collect(::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(#block), |x| ::std::convert::Into::into(::std::clone::Clone::clone(&x)))) } }, Some(DerivedIntoCoercion::OptionVecToOptionVec) => quote! { ::std::option::Option::and_then(#block, |value| ::std::option::Option::Some({ - let mut result = vec![]; - ::std::iter::Extend::extend(&mut result, ::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(value), |x| ::std::convert::Into::into(::std::clone::Clone::clone(&x)))); - result + ::std::iter::Iterator::collect(::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(value), |x| ::std::convert::Into::into(::std::clone::Clone::clone(&x)))) })) }, Some(DerivedIntoCoercion::VecOptionToVecOption) => quote! { { - let mut result = vec![]; - ::std::iter::Extend::extend(&mut result, ::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(#block), |x| ::std::option::Option::and_then(x, |value| ::std::option::Option::Some( + ::std::iter::Iterator::collect(::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(#block), |x| ::std::option::Option::and_then(x, |value| ::std::option::Option::Some( ::std::convert::Into::into(value) - )))); - result + )))) } }, // If the field is not derived, we follow the normal process. From f3ef60033f0c90c07607394a26337f25ce8b8c0a Mon Sep 17 00:00:00 2001 From: Miaxos Date: Thu, 28 Oct 2021 10:22:39 +0000 Subject: [PATCH 21/65] feat: use with for simple object --- derive/Cargo.toml | 2 -- derive/src/args.rs | 1 + derive/src/simple_object.rs | 66 +++++++------------------------------ derive/src/utils.rs | 60 --------------------------------- tests/derived_field.rs | 46 +++++++++++++++++++++++--- 5 files changed, 54 insertions(+), 121 deletions(-) diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 6fb64876..9e3de277 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -23,5 +23,3 @@ Inflector = "0.11.4" proc-macro-crate = "1.0.0" darling = "0.12.2" thiserror = "1.0.24" -regex = "1.5" -once_cell = "1.8.0" diff --git a/derive/src/args.rs b/derive/src/args.rs index 5bc5123b..e2ba5e86 100644 --- a/derive/src/args.rs +++ b/derive/src/args.rs @@ -255,6 +255,7 @@ pub struct ObjectField { pub struct DerivedField { pub name: Option, pub into: Option, + pub with: Option, #[darling(default)] pub owned: Option, } diff --git a/derive/src/simple_object.rs b/derive/src/simple_object.rs index 46340afb..a170749d 100644 --- a/derive/src/simple_object.rs +++ b/derive/src/simple_object.rs @@ -3,12 +3,11 @@ use proc_macro::TokenStream; use quote::quote; use std::str::FromStr; use syn::ext::IdentExt; -use syn::{Error, Ident, Type}; +use syn::{Error, Ident, Path, Type}; use crate::args::{self, RenameRuleExt, RenameTarget, SimpleObjectField}; use crate::utils::{ - derive_type_coercion, gen_deprecation, generate_guards, get_crate_name, get_rustdoc, - visible_fn, DerivedIntoCoercion, GeneratorResult, + gen_deprecation, generate_guards, get_crate_name, get_rustdoc, visible_fn, GeneratorResult, }; #[derive(Debug)] @@ -16,18 +15,7 @@ struct DerivedFieldMetadata { ident: Ident, into: Type, owned: Option, - // The into argument for a derive field won't be able to transform everythings: - // Without the specialization from Rust, we can't implement things like From between Vec -> - // Vec or Option -> Option. - // But there are cases which you want to have this coercion derived, so to have it working - // until the specialization feature comes, we manually check coercion for the most usual cases - // which are: - // - // - Vec -> Vec - // - Option -> Option - // - Option> -> Option> - // - Vec> -> Vec> - coercion: DerivedIntoCoercion, + with: Option, } struct SimpleObjectFieldGenerator<'a> { @@ -88,16 +76,11 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult GeneratorResult quote! { ::std::option::Option::None }, }; let vis = &field.vis; - let derived_coercion = if let Some(derived) = derived { - Some(&derived.coercion) - } else { - None - }; let ty = if let Some(derived) = derived { &derived.into @@ -198,6 +176,8 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult quote! { &self.#base_ident @@ -207,36 +187,14 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult quote! { + block = match (derived, with_function) { + (Some(_), Some(with)) => quote! { + #with(#block) + }, + (Some(_), None) => quote! { ::std::convert::Into::into(#block) }, - Some(DerivedIntoCoercion::OptionToOption) => quote! { - ::std::option::Option::and_then(#block, |value| ::std::option::Option::Some(::std::convert::Into::into(value))) - }, - Some(DerivedIntoCoercion::VecToVec) => quote! { - { - ::std::iter::Iterator::collect(::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(#block), |x| ::std::convert::Into::into(::std::clone::Clone::clone(&x)))) - } - }, - Some(DerivedIntoCoercion::OptionVecToOptionVec) => quote! { - ::std::option::Option::and_then(#block, |value| ::std::option::Option::Some({ - ::std::iter::Iterator::collect(::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(value), |x| ::std::convert::Into::into(::std::clone::Clone::clone(&x)))) - })) - }, - Some(DerivedIntoCoercion::VecOptionToVecOption) => quote! { - { - ::std::iter::Iterator::collect(::std::iter::Iterator::map(::std::iter::IntoIterator::into_iter(#block), |x| ::std::option::Option::and_then(x, |value| ::std::option::Option::Some( - ::std::convert::Into::into(value) - )))) - } - }, - // If the field is not derived, we follow the normal process. - _ => quote! { - #block - }, + (_, _) => block, }; let ty = match !owned { diff --git a/derive/src/utils.rs b/derive/src/utils.rs index 6843c056..394a30d4 100644 --- a/derive/src/utils.rs +++ b/derive/src/utils.rs @@ -1,11 +1,9 @@ use std::collections::HashSet; use darling::FromMeta; -use once_cell::sync::Lazy; use proc_macro2::{Span, TokenStream, TokenTree}; use proc_macro_crate::{crate_name, FoundCrate}; use quote::quote; -use regex::Regex; use syn::visit::Visit; use syn::{ Attribute, Error, Expr, ExprPath, FnArg, Ident, ImplItemMethod, Lit, LitStr, Meta, NestedMeta, @@ -535,61 +533,3 @@ pub fn extract_input_args( Ok(args) } - -#[derive(Debug)] -pub enum DerivedIntoCoercion { - Unknown = 1, - VecToVec = 2, - OptionToOption = 3, - OptionVecToOptionVec = 4, - VecOptionToVecOption = 5, -} - -static CHECK_OPTION: Lazy = Lazy::new(|| Regex::new("^Option <(.*?) >$").unwrap()); -static CHECK_VEC: Lazy = Lazy::new(|| Regex::new("^Vec <(.*?) >$").unwrap()); -static CHECK_VEC_OPTION: Lazy = - Lazy::new(|| Regex::new("^Vec < Option <(.*?)> >$").unwrap()); -static CHECK_OPTION_VEC: Lazy = - Lazy::new(|| Regex::new("^Option < Vec <(.*?)> >$").unwrap()); - -/// The into argument for a derive field won't be able to transform everythings: -/// Without the specialization from Rust, we can't implement things like From between Vec -> -/// Vec or Option -> Option. -/// But there are cases which you want to have this coercion derived, so to have it working -/// until the specialization feature comes, we manually check coercion for the most usual cases -/// which are: -/// -/// - Vec -> Vec -/// - Option -> Option -/// - Option> -> Option> -/// - Vec> -> Vec> -pub fn derive_type_coercion, S2: AsRef>( - base_type: S1, - target_type: S2, -) -> DerivedIntoCoercion { - if CHECK_OPTION_VEC.find(base_type.as_ref()).is_some() - && CHECK_OPTION_VEC.find(target_type.as_ref()).is_some() - { - return DerivedIntoCoercion::OptionVecToOptionVec; - } - - if CHECK_VEC_OPTION.find(base_type.as_ref()).is_some() - && CHECK_VEC_OPTION.find(target_type.as_ref()).is_some() - { - return DerivedIntoCoercion::VecOptionToVecOption; - } - - if CHECK_VEC.find(base_type.as_ref()).is_some() - && CHECK_VEC.find(target_type.as_ref()).is_some() - { - return DerivedIntoCoercion::VecToVec; - } - - if CHECK_OPTION.find(base_type.as_ref()).is_some() - && CHECK_OPTION.find(target_type.as_ref()).is_some() - { - return DerivedIntoCoercion::OptionToOption; - } - - DerivedIntoCoercion::Unknown -} diff --git a/tests/derived_field.rs b/tests/derived_field.rs index 03f718ed..1d6abbe7 100644 --- a/tests/derived_field.rs +++ b/tests/derived_field.rs @@ -123,15 +123,51 @@ pub async fn test_derived_field_simple_object_option() { } } + fn option_to_option>(value: Option) -> Option { + value.map(|x| x.into()) + } + + fn vec_to_vec>(value: Vec) -> Vec { + value.into_iter().map(|x| x.into()).collect() + } + + fn vecopt_to_vecopt>(value: Vec>) -> Vec> { + value.into_iter().map(|x| x.map(|opt| opt.into())).collect() + } + + fn optvec_to_optvec>(value: Option>) -> Option> { + value.map(|x| x.into_iter().map(|y| y.into()).collect()) + } + #[derive(SimpleObject)] struct TestObj { - #[graphql(derived(owned, name = "value2", into = "Option"))] + #[graphql(derived( + owned, + name = "value2", + into = "Option", + with = "option_to_option" + ))] pub value1: Option, - #[graphql(derived(owned, name = "value_vec_2", into = "Vec"))] + #[graphql(derived( + owned, + name = "value_vec_2", + into = "Vec", + with = "vec_to_vec" + ))] pub value_vec_1: Vec, - #[graphql(derived(owned, name = "value_opt_vec_2", into = "Option>"))] + #[graphql(derived( + owned, + name = "value_opt_vec_2", + into = "Option>", + with = "optvec_to_optvec" + ))] pub value_opt_vec_1: Option>, - #[graphql(derived(owned, name = "value_vec_opt_2", into = "Vec>"))] + #[graphql(derived( + owned, + name = "value_vec_opt_2", + into = "Vec>", + with = "vecopt_to_vecopt" + ))] pub value_vec_opt_1: Vec>, } @@ -147,7 +183,7 @@ pub async fn test_derived_field_simple_object_option() { } } - let query = "{ test { value1 value2 valueVec1 valueVec2 valueOptVec1 valueOptVec2} }"; + let query = "{ test { value1 value2 valueVec1 valueVec2 valueOptVec1 valueOptVec2 } }"; let schema = Schema::new(Query, EmptyMutation, EmptySubscription); assert_eq!( schema.execute(query).await.data, From b361119ca01a147aee6b7ac76edeaaa2dc92d693 Mon Sep 17 00:00:00 2001 From: Miaxos Date: Thu, 28 Oct 2021 12:42:13 +0000 Subject: [PATCH 22/65] feat: add with for object & complex object & update documentation --- derive/src/complex_object.rs | 16 +++-- derive/src/object.rs | 16 +++-- docs/en/src/derived_fields.md | 16 ++--- src/lib.rs | 4 +- tests/derived_field.rs | 120 ++++++++++++++++++++++++++++++++++ 5 files changed, 152 insertions(+), 20 deletions(-) diff --git a/derive/src/complex_object.rs b/derive/src/complex_object.rs index 8d282690..2926518a 100644 --- a/derive/src/complex_object.rs +++ b/derive/src/complex_object.rs @@ -40,6 +40,7 @@ pub fn generate( if derived.name.is_some() && derived.into.is_some() { let base_function_name = &method.sig.ident; let name = derived.name.unwrap(); + let with = derived.with; let into = Type::Verbatim( proc_macro2::TokenStream::from_str(&derived.into.unwrap()).unwrap(), ); @@ -93,11 +94,16 @@ pub fn generate( .into_iter(), ); - let new_block = quote!({ - { - ::std::result::Result::Ok(#self_ty::#base_function_name(&self, #other_atts).await?.into()) - } - }); + let new_block = match with { + Some(with) => quote!({ + ::std::result::Result::Ok(#with(#self_ty::#base_function_name(&self, #other_atts).await?)) + }), + None => quote!({ + { + ::std::result::Result::Ok(#self_ty::#base_function_name(&self, #other_atts).await?.into()) + } + }), + }; new_impl.block = syn::parse2::(new_block).expect("invalid block"); diff --git a/derive/src/object.rs b/derive/src/object.rs index 6cfe0b79..665a9976 100644 --- a/derive/src/object.rs +++ b/derive/src/object.rs @@ -55,6 +55,7 @@ pub fn generate( if derived.name.is_some() && derived.into.is_some() { let base_function_name = &method.sig.ident; let name = derived.name.unwrap(); + let with = derived.with; let into = Type::Verbatim( proc_macro2::TokenStream::from_str(&derived.into.unwrap()).unwrap(), ); @@ -108,11 +109,16 @@ pub fn generate( .into_iter(), ); - let new_block = quote!({ - { - ::std::result::Result::Ok(#self_ty::#base_function_name(&self, #other_atts).await?.into()) - } - }); + let new_block = match with { + Some(with) => quote!({ + ::std::result::Result::Ok(#with(#self_ty::#base_function_name(&self, #other_atts).await?)) + }), + None => quote!({ + { + ::std::result::Result::Ok(#self_ty::#base_function_name(&self, #other_atts).await?.into()) + } + }), + }; new_impl.block = syn::parse2::(new_block).expect("invalid block"); diff --git a/docs/en/src/derived_fields.md b/docs/en/src/derived_fields.md index 6ec4c016..6214a58a 100644 --- a/docs/en/src/derived_fields.md +++ b/docs/en/src/derived_fields.md @@ -69,9 +69,9 @@ impl From> for Vec { } ``` -So you wouldn't be able to generate derived fields for existing wrapper type structures like `Vec` or `Option`. But when you implement a `From for T` you should be able to derived a `From> for Vec` and a `From> for Option`, so a coercion mecanism has been included so you'll be able to use the derived macro argument with `Vec` and `Option`. +So you wouldn't be able to generate derived fields for existing wrapper type structures like `Vec` or `Option`. But when you implement a `From for T` you should be able to derived a `From> for Vec` and a `From> for Option`. +We included a `with` parameter to help you define a function to call instead of using the `Into` trait implementation between wrapper structures. -This coercion mecanism impose these derived to be `owned`. ### Example @@ -91,15 +91,13 @@ impl From for ValueDerived2 { } } +fn option_to_option>(value: Option) -> Option { + value.map(|x| x.into()) +} + #[derive(SimpleObject)] struct TestObj { - #[graphql(derived(owned, name = "value2", into = "Option"))] + #[graphql(derived(owned, name = "value2", into = "Option", with = "option_to_option"))] pub value1: Option, - #[graphql(derived(owned, name = "value_vec_2", into = "Vec"))] - pub value_vec_1: Vec, - #[graphql(derived(owned, name = "value_opt_vec_2", into = "Option>"))] - pub value_opt_vec_1: Option>, - #[graphql(derived(owned, name = "value_vec_opt_2", into = "Vec>"))] - pub value_vec_opt_1: Vec>, } ``` diff --git a/src/lib.rs b/src/lib.rs index 20d422a5..1d80fb24 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -295,6 +295,7 @@ pub type FieldResult = Result; /// |--------------|------------------------------------------|------------ |----------| /// | name | Generated derived field name | string | N | /// | into | Type to derived an into | string | Y | +/// | with | Function to apply to manage advanced use cases | string| Y | /// /// # Valid field return types /// @@ -467,8 +468,8 @@ pub use async_graphql_derive::Object; /// |--------------|------------------------------------------|------------ |----------| /// | name | Generated derived field name | string | N | /// | into | Type to derived an into | string | Y | -/// | into | Type to derived an into | string | Y | /// | owned | Field resolver return a ownedship value | bool | Y | +/// | with | Function to apply to manage advanced use cases | string| Y | /// /// /// # Examples @@ -533,6 +534,7 @@ pub use async_graphql_derive::SimpleObject; /// |--------------|------------------------------------------|------------ |----------| /// | name | Generated derived field name | string | N | /// | into | Type to derived an into | string | Y | +/// | with | Function to apply to manage advanced use cases | string| Y | /// /// # Examples /// diff --git a/tests/derived_field.rs b/tests/derived_field.rs index 1d6abbe7..142d23d2 100644 --- a/tests/derived_field.rs +++ b/tests/derived_field.rs @@ -46,6 +46,60 @@ pub async fn test_derived_field_object() { ); } +#[tokio::test] +pub async fn test_derived_field_object_with() { + use serde::{Deserialize, Serialize}; + + struct Query; + + #[derive(Serialize, Deserialize)] + struct ValueDerived(String); + + scalar!(ValueDerived); + + impl From for ValueDerived { + fn from(value: i32) -> Self { + ValueDerived(format!("{}", value)) + } + } + + fn option_to_option>(value: Option) -> Option { + value.map(|x| x.into()) + } + + #[Object] + impl Query { + #[graphql(derived( + name = "value2", + into = "Option", + with = "option_to_option" + ))] + async fn value1(&self, #[graphql(default = 100)] input: i32) -> Option { + Some(input) + } + } + + let query = "{ value1 value2 }"; + let schema = Schema::new(Query, EmptyMutation, EmptySubscription); + assert_eq!( + schema.execute(query).await.data, + value!({ + "value1": 100, + "value2": "100", + }) + ); + + let query = "{ value1(input: 1) value2(input: 2) }"; + let schema = Schema::new(Query, EmptyMutation, EmptySubscription); + assert_eq!( + schema.execute(query).await.data, + value!({ + "value1": 1, + "value2": "2", + }) + ); +} + #[tokio::test] pub async fn test_derived_field_simple_object() { use serde::{Deserialize, Serialize}; @@ -261,3 +315,69 @@ pub async fn test_derived_field_complex_object() { }) ); } + +#[tokio::test] +pub async fn test_derived_field_complex_object_derived() { + use serde::{Deserialize, Serialize}; + + #[derive(SimpleObject)] + #[graphql(complex)] + struct MyObj { + a: i32, + #[graphql(owned, derived(name = "f", into = "ValueDerived"))] + b: i32, + } + + #[derive(Serialize, Deserialize)] + struct ValueDerived(String); + + scalar!(ValueDerived); + + impl From for ValueDerived { + fn from(value: i32) -> Self { + ValueDerived(format!("{}", value)) + } + } + + fn option_to_option>(value: Option) -> Option { + value.map(|x| x.into()) + } + + #[ComplexObject] + impl MyObj { + async fn c(&self) -> i32 { + self.a + self.b + } + + #[graphql(derived(name = "e", into = "Option", with = "option_to_option"))] + async fn d(&self, v: i32) -> Option { + Some(self.a + self.b + v) + } + } + + struct Query; + + #[Object] + impl Query { + async fn obj(&self) -> MyObj { + MyObj { a: 10, b: 20 } + } + } + + let query = "{ obj { a b c d(v:100) e(v: 200) f } }"; + let schema = Schema::new(Query, EmptyMutation, EmptySubscription); + dbg!(schema.execute(query).await); + assert_eq!( + schema.execute(query).await.data, + value!({ + "obj": { + "a": 10, + "b": 20, + "c": 30, + "d": 130, + "e": "230", + "f": "20", + }, + }) + ); +} From 717a0da19228d78910fd0b955fa95175e6e8d3d1 Mon Sep 17 00:00:00 2001 From: Miaxos Date: Thu, 28 Oct 2021 15:55:00 +0000 Subject: [PATCH 23/65] misc: dbg missed --- tests/derived_field.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/derived_field.rs b/tests/derived_field.rs index 142d23d2..36724c81 100644 --- a/tests/derived_field.rs +++ b/tests/derived_field.rs @@ -366,7 +366,6 @@ pub async fn test_derived_field_complex_object_derived() { let query = "{ obj { a b c d(v:100) e(v: 200) f } }"; let schema = Schema::new(Query, EmptyMutation, EmptySubscription); - dbg!(schema.execute(query).await); assert_eq!( schema.execute(query).await.data, value!({ From 38dcc7d58231a4f4c5b372b90540c7a9d6b621ca Mon Sep 17 00:00:00 2001 From: Sunli Date: Tue, 2 Nov 2021 20:34:41 +0800 Subject: [PATCH 24/65] update ci --- .github/workflows/ci.yml | 4 ++-- CHANGELOG.md | 4 ++++ README.md | 2 +- derive/src/utils.rs | 4 ++-- examples | 2 +- value/src/lib.rs | 1 - 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3f1532f..1277ce0d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,8 +16,8 @@ jobs: fail-fast: false matrix: include: - - { rust: stable, os: ubuntu-latest } - - { rust: 1.54.0, os: ubuntu-latest } +# - { rust: stable, os: ubuntu-latest } + - { rust: 1.56.1, os: ubuntu-latest } steps: - name: Checkout uses: actions/checkout@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index c683eee1..96150677 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +- Use Rust `2021` edition. + ## [2.10.8] 2021-10-26 - [async-graphql-poem] Bump poem to `1.0.13`. diff --git a/README.md b/README.md index 6954e70f..09a94ec8 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ * [Docs](https://docs.rs/async-graphql) * [GitHub repository](https://github.com/async-graphql/async-graphql) * [Cargo package](https://crates.io/crates/async-graphql) -* Minimum supported Rust version: 1.54.0 or later +* Minimum supported Rust version: 1.56.1 or later ## Safety diff --git a/derive/src/utils.rs b/derive/src/utils.rs index dc0dfcbf..394a30d4 100644 --- a/derive/src/utils.rs +++ b/derive/src/utils.rs @@ -316,11 +316,11 @@ fn generate_default_value(lit: &Lit) -> GeneratorResult { } Lit::Int(value) => { let value = value.base10_parse::()?; - Ok(quote!({ ::TryInto::try_into(#value).unwrap() })) + Ok(quote!({ ::std::convert::TryInto::try_into(#value).unwrap() })) } Lit::Float(value) => { let value = value.base10_parse::()?; - Ok(quote!({ ::TryInto::try_into(#value) })) + Ok(quote!({ ::std::convert::TryInto::try_into(#value) })) } Lit::Bool(value) => { let value = value.value; diff --git a/examples b/examples index 1683aac1..b1faa106 160000 --- a/examples +++ b/examples @@ -1 +1 @@ -Subproject commit 1683aac1883a6acf27747bf3e22491fa13aa538f +Subproject commit b1faa1061fb895ba0a325f01a09effea5fad50c8 diff --git a/value/src/lib.rs b/value/src/lib.rs index 033c5376..bf40e624 100644 --- a/value/src/lib.rs +++ b/value/src/lib.rs @@ -10,7 +10,6 @@ mod value_serde; mod variables; use std::borrow::{Borrow, Cow}; -use std::collections::BTreeMap; use std::fmt::{self, Display, Formatter, Write}; use std::ops::Deref; use std::sync::Arc; From c6d26884a9edbb20116cc50af1fe4b7612bdb92e Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 28 Oct 2021 15:21:42 +0800 Subject: [PATCH 25/65] 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 e9624f8093028d9dc2aedbad93aa7e89bc76e6f1 Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 28 Oct 2021 16:02:51 +0800 Subject: [PATCH 26/65] 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 057d6aebfd671a69e82f30120c12e5e609ffef8f Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 28 Oct 2021 16:56:08 +0800 Subject: [PATCH 27/65] 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 fa6b7964c4b9297b796b51032e1784978facd7df Mon Sep 17 00:00:00 2001 From: Sunli Date: Fri, 29 Oct 2021 14:06:46 +0800 Subject: [PATCH 28/65] 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 9ce89683d3224dacaaccb6584ac51dc3b5c3a586 Mon Sep 17 00:00:00 2001 From: Sunli Date: Mon, 1 Nov 2021 15:37:28 +0800 Subject: [PATCH 29/65] 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 c77de81194792404fac60598ed9c8141f7d98d71 Mon Sep 17 00:00:00 2001 From: Sunli Date: Tue, 2 Nov 2021 19:37:21 +0800 Subject: [PATCH 30/65] 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 31/65] 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"), }) } } From ecc6810aa510aca82fee0c88c5543d3c6617a747 Mon Sep 17 00:00:00 2001 From: Sunli Date: Tue, 2 Nov 2021 20:39:34 +0800 Subject: [PATCH 32/65] Update code-coverage.yml --- .github/workflows/code-coverage.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index f1a3b0e0..889879f1 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -10,6 +10,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: 1.56.1 + override: true - name: Run cargo-tarpaulin uses: actions-rs/tarpaulin@v0.1 with: From a880617906f4e8739cbda80d5125fc41a07dde19 Mon Sep 17 00:00:00 2001 From: Miaxos Date: Tue, 2 Nov 2021 20:15:43 +0000 Subject: [PATCH 33/65] misc(doc): add extension documentation --- docs/en/src/SUMMARY.md | 5 +- docs/en/src/extensions.md | 3 + docs/en/src/extensions_available.md | 54 ++++++++ docs/en/src/extensions_inner_working.md | 162 ++++++++++++++++++++++++ 4 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 docs/en/src/extensions.md create mode 100644 docs/en/src/extensions_available.md create mode 100644 docs/en/src/extensions_inner_working.md diff --git a/docs/en/src/SUMMARY.md b/docs/en/src/SUMMARY.md index 61cfb56c..198f7bae 100644 --- a/docs/en/src/SUMMARY.md +++ b/docs/en/src/SUMMARY.md @@ -9,6 +9,7 @@ - [Error handling](error_handling.md) - [Merging Objects / Subscriptions](merging_objects.md) - [Derived fields](derived_fields.md) + - [Complex Object](define_complex_object.md) - [Enum](define_enum.md) - [Interface](define_interface.md) - [Union](define_union.md) @@ -27,6 +28,9 @@ - [Apollo Tracing](apollo_tracing.md) - [Query complexity and depth](depth_and_complexity.md) - [Hide content in introspection](visibility.md) +- [Extensions](extensions.md) + - [How extensions are working](extensions_inner_working.md) + - [Available extensions](extensions_available.md) - [Integrations](integrations.md) - [Tide](integrations_to_tide.md) - [Warp](integrations_to_warp.md) @@ -34,5 +38,4 @@ - [Advanced topics](advanced_topics.md) - [Custom scalars](custom_scalars.md) - [Optimizing N+1 queries](dataloader.md) - - [Custom extensions](custom_extensions.md) - [Apollo Federation](apollo_federation.md) diff --git a/docs/en/src/extensions.md b/docs/en/src/extensions.md new file mode 100644 index 00000000..6bb21dfd --- /dev/null +++ b/docs/en/src/extensions.md @@ -0,0 +1,3 @@ +# Extensions + +`async-graphql` has the capability to be extended with extensions without having to modify the original source code. A lot of features can be added this way, and a lot of extensions already exists. diff --git a/docs/en/src/extensions_available.md b/docs/en/src/extensions_available.md new file mode 100644 index 00000000..c29e69a4 --- /dev/null +++ b/docs/en/src/extensions_available.md @@ -0,0 +1,54 @@ +# Extensions available + +There are a lot of available extensions in the `async-graphql` to empower your GraphQL Server, some of these documentations are documented here. + +## Analyzer +*Available in the repository* + +The `analyzer` extension will output a field containing `complexity` and `depth` in the response extension field of each query. + + +## Apollo Persisted Queries +*Available in the repository* + +To improve network performance for large queries, you can enable this Persisted Queries extension. With this extension enabled, each unique query is associated to a unique identifier, so clients can send this identifier instead of the corresponding query string to reduce requests sizes. + +This extension doesn't force you to use some cache strategy, you can choose the caching strategy you want, you'll just have to implement the `CacheStorage` trait: +```rust +#[async_trait::async_trait] +pub trait CacheStorage: Send + Sync + Clone + 'static { + /// Load the query by `key`. + async fn get(&self, key: String) -> Option; + /// Save the query by `key`. + async fn set(&self, key: String, query: String); +} +``` + +### References + +[Apollo doc - Persisted Queries](https://www.apollographql.com/docs/react/api/link/persisted-queries/) + +## Apollo Tracing +*Available in the repository* + +Apollo Tracing is an extension which includes analytics data for your queries. This extension works to follow the old and now deprecated [Apollo Tracing Spec](https://github.com/apollographql/apollo-tracing). If you want to check the newer Apollo Reporting Protocol, it's implemented by [async-graphql Apollo studio extension](https://github.com/async-graphql/async_graphql_apollo_studio_extension) for Apollo Studio. + +## Apollo Studio +*Available at [async-graphql/async_graphql_apollo_studio_extension](https://github.com/async-graphql/async_graphql_apollo_studio_extension)* + +Apollo Studio is a cloud platform that helps you build, validate, and secure your organization's graph (description from the official documentation). It's a service allowing you to monitor & work with your team around your GraphQL Schema. `async-graphql` provides an extension implementing the official [Apollo Specification](https://www.apollographql.com/docs/studio/setup-analytics/#third-party-support) available at [async-graphql-extension-apollo-tracing](https://github.com/async-graphql/async_graphql_apollo_studio_extension) and [Crates.io](https://crates.io/crates/async-graphql-extension-apollo-tracing). + +## Logger +*Available in the repository* + +Logger is a simple extension allowing you to add some logging feature to `async-graphql`. It's also a good example to learn how to create your own extension. + +## OpenTelemetry +*Available in the repository* + +OpenTelemetry is an extension providing an integration with the [opentelemetry crate](https://crates.io/crates/opentelemetry) to allow your application to capture distributed traces and metrics from `async-grraphql`. + +## Tracing +*Available in the repository* + +Tracing is a simple extension allowing you to add some tracing feature to `async-graphql`. A little like the `Logger` extension. diff --git a/docs/en/src/extensions_inner_working.md b/docs/en/src/extensions_inner_working.md new file mode 100644 index 00000000..14c63788 --- /dev/null +++ b/docs/en/src/extensions_inner_working.md @@ -0,0 +1,162 @@ +# How extensions are defined + +An `async-graphql` extension is defined by implementing the trait `Extension` associated. The `Extension` trait allow you to insert custom code to some several steps used to respond to GraphQL's queries through `async-graphql`. With `Extensions`, your application can hook into the GraphQL's requests lifecycle to add behaviors about incoming requests or outgoing response. + +`Extensions` are a lot like middleware from other frameworks, be careful when using those: when you use an extension **it'll be run for every GraphQL requests**. + +Across every step, you'll have the `ExtensionContext` supplied with data about your current request execution. Feel free to check how it's constructed in the code, documentation about it will soon come. + +## A word about middleware + +For those who don't know, let's dig deeper into what is a middleware: + +```rust +async fn middleware(&self, ctx: &ExtensionContext<'_>, next: NextMiddleware<'_>) -> MiddlewareResult { + // Logic to your middleware. + + /* + * Final step to your middleware, we call the next function which will trigger + * the execution of the next middleware. It's like a `callback` in JavaScript. + */ + next.run(ctx).await +} +``` + +As you have seen, a `Middleware` is only a function calling the next function at the end, but we could also do a middleware with the `next` function at the start. This is where it's becoming tricky: depending on where you put your logics and where is the `next` call, your logic won't have the same execution order. + + +Depending on your logic code, you'll want to process it before or after the `next` call. If you need more information about middlewares, there are a lot of things in the web. + +## Processing of a query + +There are several steps to go to process a query to completion, you'll be able to create extension based on these hooks. + +### request + +First, when we receive a request, if it's not a subscription, the first function to be called will be `request`, it's the first step, it's the function called at the incoming request, and it's also the function which will output the response to the user. + +Default implementation for `request`: + +```rust +async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response { + next.run(ctx).await +} +``` + +Depending on where you put your logic code, it'll be executed at the beginning or at the ending of the query being processed. + + +```rust +async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response { + // The code here will be run before the prepare_request is executed. + let result = next.run(ctx).await; + // The code after the completion of this futue will be after the processing, just before sending the result to the user. + result +} +``` + +### prepare_request + +Just after the `request`, we will have the `prepare_request` lifecycle, which will be hooked. + +```rust +async fn prepare_request( + &self, + ctx: &ExtensionContext<'_>, + request: Request, + next: NextPrepareRequest<'_>, +) -> ServerResult { + // The code here will be un before the prepare_request is executed, just after the request lifecycle hook. + let result = next.run(ctx, request).await; + // The code here will be run just after the prepare_request + result +} +``` + +### parse_query + +The `parse_query` will create a GraphQL `ExecutableDocument` on your query, it'll check if the query is valid for the GraphQL Spec. Usually the implemented spec in `async-graphql` tends to be the last stable one (October2021). + +```rust +/// Called at parse query. +async fn parse_query( + &self, + ctx: &ExtensionContext<'_>, + // The raw query + query: &str, + // The variables + variables: &Variables, + next: NextParseQuery<'_>, +) -> ServerResult { + next.run(ctx, query, variables).await +} +``` + +### validation + +The `validation` step will check (depending on your `validation_mode`) rules the query should abide to and give the client data about why the query is not valid. + +```rust +/// Called at validation query. +async fn validation( + &self, + ctx: &ExtensionContext<'_>, + next: NextValidation<'_>, +) -> Result> { + next.run(ctx).await +} +``` + +### execute + +The `execution` step is a huge one, it'll start the execution of the query by calling each resolver concurrently for a `Query` and serially for a `Mutation`. + +```rust +/// Called at execute query. +async fn execute( + &self, + ctx: &ExtensionContext<'_>, + operation_name: Option<&str>, + next: NextExecute<'_>, +) -> Response { + // Befoe starting resolving the whole query + let result = next.run(ctx, operation_name).await; + // After resolving the whole query + result +} +```` + +### resolve + +The `resolve` step is launched for each field. + +```rust +/// Called at resolve field. +async fn resolve( + &self, + ctx: &ExtensionContext<'_>, + info: ResolveInfo<'_>, + next: NextResolve<'_>, +) -> ServerResult> { + // Logic before resolving the field + let result = next.run(ctx, info).await; + // Logic after resolving the field + result +} +``` + +### subscribe + +The `subscribe` lifecycle has the same behavior as the `request` but for a `Subscritpion`. + +```rust +/// Called at subscribe request. +fn subscribe<'s>( + &self, + ctx: &ExtensionContext<'_>, + stream: BoxStream<'s, Response>, + next: NextSubscribe<'_>, +) -> BoxStream<'s, Response> { + next.run(ctx, stream) +} +``` From 2059cb470e5cc8792c17d641d94f7e2ce6f54235 Mon Sep 17 00:00:00 2001 From: Miaxos Date: Tue, 2 Nov 2021 20:26:41 +0000 Subject: [PATCH 34/65] misc: remove old extension documentation --- docs/en/src/custom_extensions.md | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 docs/en/src/custom_extensions.md diff --git a/docs/en/src/custom_extensions.md b/docs/en/src/custom_extensions.md deleted file mode 100644 index c9320298..00000000 --- a/docs/en/src/custom_extensions.md +++ /dev/null @@ -1,7 +0,0 @@ -# Custom extensions - -A GraphQL extension object can receive events in various stages of a query's execution, and you can collect various kinds of data to be returned in the query results. - -You can use `async_graphql::Extension` to define an extension object, and your application must call `Schema::extension` when your `Schema` is created. - -You can refer to [Apollo Tracing](https://github.com/async-graphql/async-graphql/blob/master/src/extensions/tracing.rs) to implement your own extension types. From 9fc4c0b02a1170fb7f025df104dc85b28145fb7a Mon Sep 17 00:00:00 2001 From: Sunli Date: Wed, 3 Nov 2021 15:33:58 +0800 Subject: [PATCH 35/65] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf20798c..9961f3f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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. +- Number value literal lookahead restrictions - [GraphQL - October 2021] (https://github.com/async-graphql/async-graphql/issues/685) ## [2.10.8] 2021-10-26 From 16cf3b8d82ae6ebe9b66bb05b8ccac8a44673436 Mon Sep 17 00:00:00 2001 From: Sunli Date: Wed, 3 Nov 2021 15:36:48 +0800 Subject: [PATCH 36/65] Release 2.11.0 async-graphql@2.11.0 async-graphql-actix-web@2.11.0 async-graphql-axum@2.11.0 async-graphql-derive@2.11.0 async-graphql-parser@2.11.0 async-graphql-poem@2.11.0 async-graphql-rocket@2.11.0 async-graphql-tide@2.11.0 async-graphql-value@2.11.0 async-graphql-warp@2.11.0 Generated by cargo-workspaces --- Cargo.toml | 8 ++++---- derive/Cargo.toml | 4 ++-- integrations/actix-web/Cargo.toml | 4 ++-- integrations/axum/Cargo.toml | 4 ++-- integrations/poem/Cargo.toml | 4 ++-- integrations/rocket/Cargo.toml | 4 ++-- integrations/tide/Cargo.toml | 4 ++-- integrations/warp/Cargo.toml | 4 ++-- parser/Cargo.toml | 4 ++-- value/Cargo.toml | 2 +- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 34adc251..3a0b0c79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql" -version = "2.10.8" +version = "2.11.0" authors = ["sunli ", "Koxiaet"] edition = "2021" description = "A GraphQL server library implemented in Rust" @@ -24,9 +24,9 @@ decimal = ["rust_decimal"] cbor = ["serde_cbor"] [dependencies] -async-graphql-derive = { path = "derive", version = "2.10.8" } -async-graphql-value = { path = "value", version = "2.10.8" } -async-graphql-parser = { path = "parser", version = "2.10.8" } +async-graphql-derive = { path = "derive", version = "=2.11.0" } +async-graphql-value = { path = "value", version = "=2.11.0" } +async-graphql-parser = { path = "parser", version = "=2.11.0" } async-stream = "0.3.0" async-trait = "0.1.48" diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 16629cc8..905c8497 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-derive" -version = "2.10.8" +version = "2.11.0" authors = ["sunli ", "Koxiaet"] edition = "2021" description = "Macros for async-graphql" @@ -15,7 +15,7 @@ categories = ["network-programming", "asynchronous"] proc-macro = true [dependencies] -async-graphql-parser = { path = "../parser", version = "2.10.8" } +async-graphql-parser = { path = "../parser", version = "=2.11.0" } proc-macro2 = "1.0.24" syn = { version = "1.0.64", features = ["full", "extra-traits", "visit-mut", "visit"] } quote = "1.0.9" diff --git a/integrations/actix-web/Cargo.toml b/integrations/actix-web/Cargo.toml index 15f41ca7..75bd71f3 100644 --- a/integrations/actix-web/Cargo.toml +++ b/integrations/actix-web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-actix-web" -version = "2.10.8" +version = "2.11.0" authors = ["sunli ", "Koxiaet"] edition = "2021" description = "async-graphql for actix-web" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "2.10.8" } +async-graphql = { path = "../..", version = "=2.11.0" } actix = "0.10.0" actix-http = "2.2.0" diff --git a/integrations/axum/Cargo.toml b/integrations/axum/Cargo.toml index 34b59f35..4c40f1fb 100644 --- a/integrations/axum/Cargo.toml +++ b/integrations/axum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-axum" -version = "2.10.8" +version = "2.11.0" authors = ["sunli "] edition = "2021" description = "async-graphql for axum" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql", "axum"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "2.10.8" } +async-graphql = { path = "../..", version = "=2.11.0" } async-trait = "0.1.51" axum = { version = "0.2.5", features = ["ws", "headers"] } diff --git a/integrations/poem/Cargo.toml b/integrations/poem/Cargo.toml index 4ebdf780..143c10fc 100644 --- a/integrations/poem/Cargo.toml +++ b/integrations/poem/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-poem" -version = "2.10.8" +version = "2.11.0" authors = ["sunli "] edition = "2021" description = "async-graphql for poem" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql", "poem"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "2.10.8" } +async-graphql = { path = "../..", version = "=2.11.0" } poem = { version = "1.0.13", features = ["websocket"] } futures-util = { version = "0.3.13", default-features = false } diff --git a/integrations/rocket/Cargo.toml b/integrations/rocket/Cargo.toml index 00c6a678..cca3a9db 100644 --- a/integrations/rocket/Cargo.toml +++ b/integrations/rocket/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-rocket" -version = "2.10.8" +version = "2.11.0" authors = ["Daniel Wiesenberg "] edition = "2021" description = "async-graphql for Rocket.rs" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql", "rocket"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "2.10.8" } +async-graphql = { path = "../..", version = "=2.11.0" } rocket = { version = "0.5.0-rc.1", default-features = false } serde = "1.0.126" diff --git a/integrations/tide/Cargo.toml b/integrations/tide/Cargo.toml index 0d1c44d7..757fdf9f 100644 --- a/integrations/tide/Cargo.toml +++ b/integrations/tide/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-tide" -version = "2.10.8" +version = "2.11.0" authors = ["vkill "] edition = "2021" description = "async-graphql for tide" @@ -16,7 +16,7 @@ default = ["websocket"] websocket = ["tide-websockets"] [dependencies] -async-graphql = { path = "../..", version = "2.10.8" } +async-graphql = { path = "../..", version = "=2.11.0" } async-trait = "0.1.48" futures-util = "0.3.13" serde_json = "1.0.64" diff --git a/integrations/warp/Cargo.toml b/integrations/warp/Cargo.toml index b8d9f1b3..c2034d1b 100644 --- a/integrations/warp/Cargo.toml +++ b/integrations/warp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-warp" -version = "2.10.8" +version = "2.11.0" authors = ["sunli ", "Koxiaet"] edition = "2021" description = "async-graphql for warp" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "../..", version = "2.10.8" } +async-graphql = { path = "../..", version = "=2.11.0" } warp = { version = "0.3.0", default-features = false, features = ["websocket"] } futures-util = { version = "0.3.13", default-features = false, features = ["sink"] } diff --git a/parser/Cargo.toml b/parser/Cargo.toml index e85072d1..0dcf2008 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-parser" -version = "2.10.8" +version = "2.11.0" authors = ["sunli ", "Koxiaet"] edition = "2021" description = "GraphQL query parser for async-graphql" @@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql-value = { path = "../value", version = "2.10.8" } +async-graphql-value = { path = "../value", version = "=2.11.0" } pest = "2.1.3" pest_derive = "2.1.0" serde_json = "1.0.64" diff --git a/value/Cargo.toml b/value/Cargo.toml index d44b724b..e1da1d2e 100644 --- a/value/Cargo.toml +++ b/value/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-value" -version = "2.10.8" +version = "2.11.0" authors = ["sunli ", "Koxiaet"] edition = "2021" description = "GraphQL value for async-graphql" From fb283dc3677cba99f80899899b6ca80f2096119a Mon Sep 17 00:00:00 2001 From: Sunli Date: Wed, 3 Nov 2021 16:20:41 +0800 Subject: [PATCH 37/65] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9961f3f1..821e13d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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. -- Number value literal lookahead restrictions - [GraphQL - October 2021] (https://github.com/async-graphql/async-graphql/issues/685) +- Number value literal lookahead restrictions - [GraphQL - October 2021] [#685](https://github.com/async-graphql/async-graphql/issues/685) ## [2.10.8] 2021-10-26 From 3b1dc774bbe5de6164209b5627f1457b5d7d4215 Mon Sep 17 00:00:00 2001 From: Sunli Date: Wed, 3 Nov 2021 16:21:12 +0800 Subject: [PATCH 38/65] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 821e13d9..0fd15c5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## [2.11.0] 2021-11-03 - Use Rust `2021` edition. - Subscription typename - [GraphQL - October 2021] [#681](https://github.com/async-graphql/async-graphql/issues/681) From b48d4c8c1f6e0aad59c203d49295eeb0347e4043 Mon Sep 17 00:00:00 2001 From: Sunli Date: Wed, 3 Nov 2021 18:42:41 +0800 Subject: [PATCH 39/65] Update MSRV --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 4141c381..1aca1636 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,7 +34,7 @@ //! * [Docs](https://docs.rs/async-graphql) //! * [GitHub repository](https://github.com/async-graphql/async-graphql) //! * [Cargo package](https://crates.io/crates/async-graphql) -//! * Minimum supported Rust version: 1.51 or later +//! * Minimum supported Rust version: 1.56.1 or later //! //! ## Features //! From 119c5d0f5d5e4f88a2b6d329cd7c6f0886766bab Mon Sep 17 00:00:00 2001 From: meh Date: Tue, 2 Nov 2021 15:49:27 +0100 Subject: [PATCH 40/65] feat: add chrono::Duration custom scalar --- Cargo.toml | 2 ++ src/types/external/duration.rs | 21 +++++++++++++++++++++ src/types/external/mod.rs | 2 ++ 3 files changed, 25 insertions(+) create mode 100644 src/types/external/duration.rs diff --git a/Cargo.toml b/Cargo.toml index 3a0b0c79..6c9f93ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ dataloader = ["futures-timer", "futures-channel", "lru"] tracing = ["tracinglib", "tracing-futures"] decimal = ["rust_decimal"] cbor = ["serde_cbor"] +chrono-duration = ["chrono", "iso8601-duration"] [dependencies] async-graphql-derive = { path = "derive", version = "=2.11.0" } @@ -50,6 +51,7 @@ mime = "0.3.15" bson = { version = "2.0.0", optional = true, features = ["chrono-0_4"] } chrono = { version = "0.4.19", optional = true } chrono-tz = { version = "0.5.3", optional = true } +iso8601-duration = { version = "0.1.0", optional = true } log = { version = "0.4.14", optional = true } secrecy = { version = "0.7.0", optional = true } tracinglib = { version = "0.1.25", optional = true, package = "tracing" } diff --git a/src/types/external/duration.rs b/src/types/external/duration.rs new file mode 100644 index 00000000..14afbb22 --- /dev/null +++ b/src/types/external/duration.rs @@ -0,0 +1,21 @@ +use chrono::Duration; +use iso8601_duration as iso8601; + +use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; + +/// Implement the Duration scalar +/// +/// The input/output is a string in ISO8601 format. +#[Scalar(internal, name = "Duration")] +impl ScalarType for Duration { + fn parse(value: Value) -> InputValueResult { + match &value { + Value::String(s) => Ok(Duration::from_std(iso8601::Duration::parse(s)?.to_std())?), + _ => Err(InputValueError::expected_type(value)), + } + } + + fn to_value(&self) -> Value { + Value::String(self.to_string()) + } +} diff --git a/src/types/external/mod.rs b/src/types/external/mod.rs index cf109f39..cf3e6b3b 100644 --- a/src/types/external/mod.rs +++ b/src/types/external/mod.rs @@ -18,6 +18,8 @@ mod bson; mod chrono_tz; #[cfg(feature = "chrono")] mod datetime; +#[cfg(feature = "chrono-duration")] +mod duration; #[cfg(feature = "decimal")] mod decimal; #[cfg(feature = "chrono")] From 0ff412080755c32e6cfc3be45e530426c976d1ee Mon Sep 17 00:00:00 2001 From: meh Date: Wed, 3 Nov 2021 14:56:42 +0100 Subject: [PATCH 41/65] docs: add specified_by_url for Duration Co-authored-by: Anthony Griffon --- src/types/external/duration.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/external/duration.rs b/src/types/external/duration.rs index 14afbb22..f9b63c76 100644 --- a/src/types/external/duration.rs +++ b/src/types/external/duration.rs @@ -6,7 +6,7 @@ use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; /// Implement the Duration scalar /// /// The input/output is a string in ISO8601 format. -#[Scalar(internal, name = "Duration")] +#[Scalar(internal, name = "Duration", specified_by_url = "https://en.wikipedia.org/wiki/ISO_8601#Durations")] impl ScalarType for Duration { fn parse(value: Value) -> InputValueResult { match &value { From 1eeb804fd369295fa9d2a0549b1576a252e460f7 Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 4 Nov 2021 09:15:25 +0800 Subject: [PATCH 42/65] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fd15c5b..f0683f89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +- Add `chrono::Duration` custom scalar. [#689](https://github.com/async-graphql/async-graphql/pull/689) + ## [2.11.0] 2021-11-03 - Use Rust `2021` edition. From 4c0a13799377abda857e8707853e1c3c4310b0f6 Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 4 Nov 2021 09:20:06 +0800 Subject: [PATCH 43/65] [async-graphql-axum] Bump axum from `0.2.5` to `0.3` #691 --- integrations/axum/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/axum/Cargo.toml b/integrations/axum/Cargo.toml index 4c40f1fb..9f1210b7 100644 --- a/integrations/axum/Cargo.toml +++ b/integrations/axum/Cargo.toml @@ -15,7 +15,7 @@ categories = ["network-programming", "asynchronous"] async-graphql = { path = "../..", version = "=2.11.0" } async-trait = "0.1.51" -axum = { version = "0.2.5", features = ["ws", "headers"] } +axum = { version = "0.3", features = ["ws", "headers"] } bytes = "1.0.1" headers = "0.3.4" http = "0.2.4" From 5c6fd0cd5a7fc6a244ab2a499a18532fbe88333f Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 4 Nov 2021 09:44:16 +0800 Subject: [PATCH 44/65] Add `MaybeUndefined::as_opt_ref` and `MaybeUndefined::as_opt_deref` methods. #688 --- CHANGELOG.md | 2 + src/types/maybe_undefined.rs | 81 +++++++++++++++++++++++++++++++++--- 2 files changed, 78 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0683f89..5784c6c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - Add `chrono::Duration` custom scalar. [#689](https://github.com/async-graphql/async-graphql/pull/689) +- Implement `From>>` for `MaybeUndefined`. +- Add `MaybeUndefined::as_opt_ref` and `MaybeUndefined::as_opt_deref` methods. ## [2.11.0] 2021-11-03 diff --git a/src/types/maybe_undefined.rs b/src/types/maybe_undefined.rs index b9768cb3..b677c65f 100644 --- a/src/types/maybe_undefined.rs +++ b/src/types/maybe_undefined.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::ops::Deref; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -61,25 +62,25 @@ impl Default for MaybeUndefined { } impl MaybeUndefined { - /// Returns true if the MaybeUndefined is undefined. + /// Returns true if the `MaybeUndefined` is undefined. #[inline] pub fn is_undefined(&self) -> bool { matches!(self, MaybeUndefined::Undefined) } - /// Returns true if the MaybeUndefined is null. + /// Returns true if the `MaybeUndefined` is null. #[inline] pub fn is_null(&self) -> bool { matches!(self, MaybeUndefined::Null) } - /// Returns true if the MaybeUndefined is value. + /// Returns true if the `MaybeUndefined` contains value. #[inline] pub fn is_value(&self) -> bool { matches!(self, MaybeUndefined::Value(_)) } - /// Borrow the value, returns `None` if the value is `undefined` or `null`, otherwise returns `Some(T)`. + /// Borrow the value, returns `None` if the the `MaybeUndefined` is `undefined` or `null`, otherwise returns `Some(T)`. #[inline] pub fn value(&self) -> Option<&T> { match self { @@ -88,7 +89,7 @@ impl MaybeUndefined { } } - /// Convert MaybeUndefined to Option. + /// Converts the `MaybeUndefined` to `Option`. #[inline] pub fn take(self) -> Option { match self { @@ -96,6 +97,30 @@ impl MaybeUndefined { _ => None, } } + + /// Converts the `MaybeUndefined` to `Option>`. + #[inline] + pub fn as_opt_ref(&self) -> Option> { + match self { + MaybeUndefined::Undefined => None, + MaybeUndefined::Null => Some(None), + MaybeUndefined::Value(value) => Some(Some(value)), + } + } + + /// Converts the `MaybeUndefined` to `Option>`. + #[inline] + pub fn as_opt_deref(&self) -> Option> + where + U: ?Sized, + T: Deref, + { + match self { + MaybeUndefined::Undefined => None, + MaybeUndefined::Null => Some(None), + MaybeUndefined::Value(value) => Some(Some(value.deref())), + } + } } impl Type for MaybeUndefined { @@ -166,6 +191,16 @@ impl From> for Option> { } } +impl From>> for MaybeUndefined { + fn from(value: Option>) -> Self { + match value { + Some(Some(value)) => Self::Value(value), + Some(None) => Self::Null, + None => Self::Undefined, + } + } +} + #[cfg(test)] mod tests { use crate::*; @@ -260,4 +295,40 @@ mod tests { Some(Some(42)) ); } + + #[test] + fn test_as_opt_ref() { + let mut value: MaybeUndefined; + let mut r: Option>; + + value = MaybeUndefined::Undefined; + r = value.as_opt_ref(); + assert_eq!(r, None); + + value = MaybeUndefined::Null; + r = value.as_opt_ref(); + assert_eq!(r, Some(None)); + + value = MaybeUndefined::Value("abc".to_string()); + r = value.as_opt_ref(); + assert_eq!(r, Some(Some(&"abc".to_string()))); + } + + #[test] + fn test_as_opt_deref() { + let mut value: MaybeUndefined; + let mut r: Option>; + + value = MaybeUndefined::Undefined; + r = value.as_opt_deref(); + assert_eq!(r, None); + + value = MaybeUndefined::Null; + r = value.as_opt_deref(); + assert_eq!(r, Some(None)); + + value = MaybeUndefined::Value("abc".to_string()); + r = value.as_opt_deref(); + assert_eq!(r, Some(Some("abc"))); + } } From e73c142ce883a044d654f6d9d15f2e4c32bfbc1f Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 4 Nov 2021 14:30:29 +0800 Subject: [PATCH 45/65] Add `Failure` type. #671 --- CHANGELOG.md | 1 + src/error.rs | 78 +++++++++++++++++++++++--- src/lib.rs | 2 +- src/validation/visitor.rs | 1 + tests/error_ext.rs | 112 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 186 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5784c6c3..d7908632 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `chrono::Duration` custom scalar. [#689](https://github.com/async-graphql/async-graphql/pull/689) - Implement `From>>` for `MaybeUndefined`. - Add `MaybeUndefined::as_opt_ref` and `MaybeUndefined::as_opt_deref` methods. +- Add `Failure` type. [#671](https://github.com/async-graphql/async-graphql/issues/671) ## [2.11.0] 2021-11-03 diff --git a/src/error.rs b/src/error.rs index e0c0485e..5bdb3401 100644 --- a/src/error.rs +++ b/src/error.rs @@ -24,6 +24,11 @@ impl ErrorExtensionValues { pub struct ServerError { /// An explanatory message of the error. pub message: String, + /// An explanatory message of the error. (for debug) + /// + /// This message comes from [`Failure`]. + #[serde(skip)] + pub debug_message: Option, /// Where the error occurred. #[serde(skip_serializing_if = "Vec::is_empty", default)] pub locations: Vec, @@ -44,12 +49,18 @@ impl ServerError { pub fn new(message: impl Into, pos: Option) -> Self { Self { message: message.into(), + debug_message: None, locations: pos.map(|pos| vec![pos]).unwrap_or_default(), path: Vec::new(), extensions: None, } } + /// Returns `debug_message` if it is `Some`, otherwise returns `message`. + pub fn debug_message(&self) -> &str { + self.debug_message.as_deref().unwrap_or(&self.message) + } + #[doc(hidden)] pub fn with_path(self, path: Vec) -> Self { Self { path, ..self } @@ -72,6 +83,7 @@ impl From for ServerError { fn from(e: parser::Error) -> Self { Self { message: e.to_string(), + debug_message: None, locations: e.positions().collect(), path: Vec::new(), extensions: None, @@ -164,6 +176,8 @@ pub type InputValueResult = Result>; pub struct Error { /// The error message. pub message: String, + /// The debug error message. + pub debug_message: Option, /// Extensions to the error. #[serde(skip_serializing_if = "error_extensions_is_empty")] pub extensions: Option, @@ -174,6 +188,7 @@ impl Error { pub fn new(message: impl Into) -> Self { Self { message: message.into(), + debug_message: None, extensions: None, } } @@ -183,6 +198,7 @@ impl Error { pub fn into_server_error(self, pos: Pos) -> ServerError { ServerError { message: self.message, + debug_message: self.debug_message, locations: vec![pos], path: Vec::new(), extensions: self.extensions, @@ -194,6 +210,17 @@ impl From for Error { fn from(e: T) -> Self { Self { message: e.to_string(), + debug_message: None, + extensions: None, + } + } +} + +impl From> for Error { + fn from(err: Failure) -> Self { + Self { + message: format!("{}", err.0), + debug_message: Some(format!("{:?}", err.0)), extensions: None, } } @@ -267,40 +294,57 @@ impl From for ParseRequestError { /// An error which can be extended into a `Error`. pub trait ErrorExtensions: Sized { /// Convert the error to a `Error`. - fn extend(&self) -> Error; + fn extend(self) -> Error; /// Add extensions to the error, using a callback to make the extensions. fn extend_with(self, cb: C) -> Error where C: FnOnce(&Self, &mut ErrorExtensionValues), { - let message = self.extend().message; - let mut extensions = self.extend().extensions.unwrap_or_default(); - cb(&self, &mut extensions); + let mut new_extensions = Default::default(); + cb(&self, &mut new_extensions); + + let Error { + message, + debug_message, + extensions, + } = self.extend(); + + let mut extensions = extensions.unwrap_or_default(); + extensions.0.extend(new_extensions.0); + Error { message, + debug_message, extensions: Some(extensions), } } } impl ErrorExtensions for Error { - fn extend(&self) -> Error { - self.clone() + fn extend(self) -> Error { + self } } // implementing for &E instead of E gives the user the possibility to implement for E which does // not conflict with this implementation acting as a fallback. impl ErrorExtensions for &E { - fn extend(&self) -> Error { + fn extend(self) -> Error { Error { message: self.to_string(), + debug_message: None, extensions: None, } } } +impl ErrorExtensions for Failure { + fn extend(self) -> Error { + Error::from(self) + } +} + /// Extend a `Result`'s error value with [`ErrorExtensions`](trait.ErrorExtensions.html). pub trait ResultExt: Sized { /// Extend the error value of the result with the callback. @@ -335,3 +379,23 @@ where } } } + +/// An error type contains `T` that implements `Display` and `Debug`. +/// +/// This type can solve the problem described in [#671](https://github.com/async-graphql/async-graphql/issues/671). +#[derive(Debug)] +pub struct Failure(pub T); + +impl From for Failure { + fn from(err: T) -> Self { + Self(err) + } +} + +impl Failure { + /// Create a new failure. + #[inline] + pub fn new(err: T) -> Self { + Self(err) + } +} diff --git a/src/lib.rs b/src/lib.rs index 1aca1636..a65539fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -209,7 +209,7 @@ pub use base::{ Type, UnionType, }; pub use error::{ - Error, ErrorExtensionValues, ErrorExtensions, InputValueError, InputValueResult, + Error, ErrorExtensionValues, ErrorExtensions, Failure, InputValueError, InputValueResult, ParseRequestError, PathSegment, Result, ResultExt, ServerError, ServerResult, }; pub use look_ahead::Lookahead; diff --git a/src/validation/visitor.rs b/src/validation/visitor.rs index 0a6024c7..e053fa41 100644 --- a/src/validation/visitor.rs +++ b/src/validation/visitor.rs @@ -851,6 +851,7 @@ impl From for ServerError { fn from(e: RuleError) -> Self { Self { message: e.message, + debug_message: None, locations: e.locations, path: Vec::new(), extensions: e.extensions, diff --git a/tests/error_ext.rs b/tests/error_ext.rs index 6214b08d..34a0c991 100644 --- a/tests/error_ext.rs +++ b/tests/error_ext.rs @@ -1,4 +1,5 @@ use async_graphql::*; +use std::fmt::{self, Display, Formatter}; #[tokio::test] pub async fn test_error_extensions() { @@ -75,3 +76,114 @@ pub async fn test_error_extensions() { }) ); } + +#[tokio::test] +pub async fn test_failure() { + #[derive(Debug)] + struct MyError; + + impl Display for MyError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "my error") + } + } + + struct Query; + + #[Object] + impl Query { + async fn failure(&self) -> Result { + Err(Failure(MyError).into()) + } + + async fn failure2(&self) -> Result { + Err(Failure(MyError))?; + Ok(1) + } + + async fn failure3(&self) -> Result { + Err(Failure(MyError) + .extend_with(|_, values| values.set("a", 1)) + .extend_with(|_, values| values.set("b", 2)))?; + Ok(1) + } + + async fn failure4(&self) -> Result { + Err(Failure(MyError)) + .extend_err(|_, values| values.set("a", 1)) + .extend_err(|_, values| values.set("b", 2))?; + Ok(1) + } + } + + let schema = Schema::new(Query, EmptyMutation, EmptySubscription); + assert_eq!( + schema + .execute("{ failure }") + .await + .into_result() + .unwrap_err(), + vec![ServerError { + message: "my error".to_string(), + debug_message: Some("MyError".to_string()), + locations: vec![Pos { line: 1, column: 3 }], + path: vec![PathSegment::Field("failure".to_string())], + extensions: None + }] + ); + + assert_eq!( + schema + .execute("{ failure2 }") + .await + .into_result() + .unwrap_err(), + vec![ServerError { + message: "my error".to_string(), + debug_message: Some("MyError".to_string()), + locations: vec![Pos { line: 1, column: 3 }], + path: vec![PathSegment::Field("failure2".to_string())], + extensions: None + }] + ); + + assert_eq!( + schema + .execute("{ failure3 }") + .await + .into_result() + .unwrap_err(), + vec![ServerError { + message: "my error".to_string(), + debug_message: Some("MyError".to_string()), + locations: vec![Pos { line: 1, column: 3 }], + path: vec![PathSegment::Field("failure3".to_string())], + extensions: Some({ + let mut values = ErrorExtensionValues::default(); + values.set("a", 1); + values.set("b", 2); + values + }) + }] + ); + + assert_eq!( + schema + .execute("{ failure4 }") + .await + .into_result() + .unwrap_err(), + vec![ServerError { + message: "my error".to_string(), + debug_message: Some("MyError".to_string()), + locations: vec![Pos { line: 1, column: 3 }], + path: vec![PathSegment::Field("failure4".to_string())], + extensions: Some({ + let mut values = ErrorExtensionValues::default(); + values.set("a", 1); + values.set("b", 2); + values + }) + }] + ); +} From 727b825e6da55d5f12577997e50111aaa15205a8 Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 4 Nov 2021 14:35:48 +0800 Subject: [PATCH 46/65] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7908632..1ebda6e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Implement `From>>` for `MaybeUndefined`. - Add `MaybeUndefined::as_opt_ref` and `MaybeUndefined::as_opt_deref` methods. - Add `Failure` type. [#671](https://github.com/async-graphql/async-graphql/issues/671) +- [async-graphql-axum] Bump axum from `0.2.5` to `0.3`. ## [2.11.0] 2021-11-03 From 0335d24ccd4db010c9dd321440d134327d741676 Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 4 Nov 2021 14:42:30 +0800 Subject: [PATCH 47/65] rustfmt --- src/types/external/duration.rs | 6 +++++- src/types/external/mod.rs | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/types/external/duration.rs b/src/types/external/duration.rs index f9b63c76..b0624f1e 100644 --- a/src/types/external/duration.rs +++ b/src/types/external/duration.rs @@ -6,7 +6,11 @@ use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; /// Implement the Duration scalar /// /// The input/output is a string in ISO8601 format. -#[Scalar(internal, name = "Duration", specified_by_url = "https://en.wikipedia.org/wiki/ISO_8601#Durations")] +#[Scalar( + internal, + name = "Duration", + specified_by_url = "https://en.wikipedia.org/wiki/ISO_8601#Durations" +)] impl ScalarType for Duration { fn parse(value: Value) -> InputValueResult { match &value { diff --git a/src/types/external/mod.rs b/src/types/external/mod.rs index cf3e6b3b..73b326e5 100644 --- a/src/types/external/mod.rs +++ b/src/types/external/mod.rs @@ -18,10 +18,10 @@ mod bson; mod chrono_tz; #[cfg(feature = "chrono")] mod datetime; -#[cfg(feature = "chrono-duration")] -mod duration; #[cfg(feature = "decimal")] mod decimal; +#[cfg(feature = "chrono-duration")] +mod duration; #[cfg(feature = "chrono")] mod naive_time; #[cfg(feature = "secrecy")] From 1385199107988caf0b98fad8af4d592fae744e5c Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 4 Nov 2021 14:54:26 +0800 Subject: [PATCH 48/65] Fix tests --- src/error.rs | 1 + tests/federation.rs | 1 + tests/field_features.rs | 3 +++ tests/guard.rs | 12 ++++++++++++ tests/input_validators.rs | 39 +++++++++++++++++++++++++++++++++++++++ tests/input_value.rs | 1 + tests/list.rs | 1 + tests/result.rs | 8 ++++++++ tests/subscription.rs | 2 ++ 9 files changed, 68 insertions(+) diff --git a/src/error.rs b/src/error.rs index 5bdb3401..1bcacc9e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -177,6 +177,7 @@ pub struct Error { /// The error message. pub message: String, /// The debug error message. + #[serde(skip)] pub debug_message: Option, /// Extensions to the error. #[serde(skip_serializing_if = "error_extensions_is_empty")] diff --git a/tests/federation.rs b/tests/federation.rs index 36279fa1..acf46d4e 100644 --- a/tests/federation.rs +++ b/tests/federation.rs @@ -242,6 +242,7 @@ pub async fn test_find_entity_with_context() { schema.execute(query).await.into_result().unwrap_err(), vec![ServerError { message: "Not found".to_string(), + debug_message: None, locations: vec![Pos { line: 2, column: 13 diff --git a/tests/field_features.rs b/tests/field_features.rs index 8bb3d13e..0b85ae19 100644 --- a/tests/field_features.rs +++ b/tests/field_features.rs @@ -85,6 +85,7 @@ pub async fn test_field_features() { vec![ServerError { message: r#"Unknown field "valueAbc" on type "QueryRoot". Did you mean "value"?"# .to_owned(), + debug_message: None, locations: vec![Pos { column: 3, line: 1 }], path: Vec::new(), extensions: None, @@ -113,6 +114,7 @@ pub async fn test_field_features() { vec![ServerError { message: r#"Unknown field "valueAbc" on type "MyObj". Did you mean "value"?"# .to_owned(), + debug_message: None, locations: vec![Pos { column: 9, line: 1 }], path: Vec::new(), extensions: None, @@ -148,6 +150,7 @@ pub async fn test_field_features() { .errors, vec![ServerError { message: r#"Unknown field "valuesAbc" on type "SubscriptionRoot". Did you mean "values", "valuesBson"?"#.to_owned(), + debug_message: None, locations: vec![Pos { column: 16, line: 1 diff --git a/tests/guard.rs b/tests/guard.rs index bd2b88b6..89396ac2 100644 --- a/tests/guard.rs +++ b/tests/guard.rs @@ -99,6 +99,7 @@ pub async fn test_guard_simple_rule() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), + debug_message: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -127,6 +128,7 @@ pub async fn test_guard_simple_rule() { .errors, vec![ServerError { message: "Forbidden".to_string(), + debug_message: None, locations: vec![Pos { line: 1, column: 16 @@ -176,6 +178,7 @@ pub async fn test_guard_and_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), + debug_message: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -195,6 +198,7 @@ pub async fn test_guard_and_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), + debug_message: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -214,6 +218,7 @@ pub async fn test_guard_and_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), + debug_message: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -283,6 +288,7 @@ pub async fn test_guard_or_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), + debug_message: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -332,6 +338,7 @@ pub async fn test_guard_chain_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), + debug_message: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -352,6 +359,7 @@ pub async fn test_guard_chain_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), + debug_message: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -372,6 +380,7 @@ pub async fn test_guard_chain_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), + debug_message: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -392,6 +401,7 @@ pub async fn test_guard_chain_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), + debug_message: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -483,6 +493,7 @@ pub async fn test_guard_race_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), + debug_message: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -538,6 +549,7 @@ pub async fn test_guard_use_params() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), + debug_message: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("get".to_owned())], extensions: None, diff --git a/tests/input_validators.rs b/tests/input_validators.rs index 4807903e..139d210c 100644 --- a/tests/input_validators.rs +++ b/tests/input_validators.rs @@ -67,6 +67,7 @@ pub async fn test_input_validator_string_min_length() { .expect_err(&should_fail_msg), vec![ServerError { message: field_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 17 @@ -84,6 +85,7 @@ pub async fn test_input_validator_string_min_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 14 @@ -177,6 +179,7 @@ pub async fn test_input_validator_string_max_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 17 @@ -194,6 +197,7 @@ pub async fn test_input_validator_string_max_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 14 @@ -286,6 +290,7 @@ pub async fn test_input_validator_chars_min_length() { .expect_err(&should_fail_msg), vec![ServerError { message: field_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 17 @@ -303,6 +308,7 @@ pub async fn test_input_validator_chars_min_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 14 @@ -397,6 +403,7 @@ pub async fn test_input_validator_chars_max_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 17 @@ -414,6 +421,7 @@ pub async fn test_input_validator_chars_max_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 14 @@ -534,6 +542,7 @@ pub async fn test_input_validator_string_email() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 17 @@ -552,6 +561,7 @@ pub async fn test_input_validator_string_email() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 14 @@ -682,6 +692,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg.clone(), + debug_message: None, locations: vec!(Pos { line: 1, column: 17 @@ -700,6 +711,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg.clone(), + debug_message: None, locations: vec!(Pos { line: 1, column: 14 @@ -717,6 +729,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 17 @@ -735,6 +748,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 14 @@ -792,6 +806,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 17 @@ -810,6 +825,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 14 @@ -851,6 +867,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 17 @@ -869,6 +886,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 14 @@ -929,6 +947,7 @@ pub async fn test_input_validator_int_range() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 17 @@ -946,6 +965,7 @@ pub async fn test_input_validator_int_range() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 14 @@ -1034,6 +1054,7 @@ pub async fn test_input_validator_int_less_than() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 17 @@ -1051,6 +1072,7 @@ pub async fn test_input_validator_int_less_than() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 14 @@ -1141,6 +1163,7 @@ pub async fn test_input_validator_int_greater_than() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 17 @@ -1158,6 +1181,7 @@ pub async fn test_input_validator_int_greater_than() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 14 @@ -1241,6 +1265,7 @@ pub async fn test_input_validator_int_nonzero() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 17 @@ -1258,6 +1283,7 @@ pub async fn test_input_validator_int_nonzero() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 14 @@ -1345,6 +1371,7 @@ pub async fn test_input_validator_int_equal() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 17 @@ -1362,6 +1389,7 @@ pub async fn test_input_validator_int_equal() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 14 @@ -1461,6 +1489,7 @@ pub async fn test_input_validator_list_max_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 17 @@ -1478,6 +1507,7 @@ pub async fn test_input_validator_list_max_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 14 @@ -1577,6 +1607,7 @@ pub async fn test_input_validator_list_min_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 17 @@ -1594,6 +1625,7 @@ pub async fn test_input_validator_list_min_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 14 @@ -1701,6 +1733,7 @@ pub async fn test_input_validator_operator_or() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 17 @@ -1718,6 +1751,7 @@ pub async fn test_input_validator_operator_or() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 14 @@ -1818,6 +1852,7 @@ pub async fn test_input_validator_operator_and() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 17 @@ -1835,6 +1870,7 @@ pub async fn test_input_validator_operator_and() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 14 @@ -1938,6 +1974,7 @@ pub async fn test_input_validator_variable() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 37 @@ -1955,6 +1992,7 @@ pub async fn test_input_validator_variable() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, + debug_message: None, locations: vec!(Pos { line: 1, column: 34 @@ -2041,6 +2079,7 @@ pub async fn test_custom_input_validator_with_extensions() { .expect_err(should_fail_msg), vec![ServerError { message: field_error_msg.into(), + debug_message: None, locations: vec!(Pos { line: 1, column: 17 diff --git a/tests/input_value.rs b/tests/input_value.rs index aa3e04bb..74560091 100644 --- a/tests/input_value.rs +++ b/tests/input_value.rs @@ -18,6 +18,7 @@ pub async fn test_input_value_custom_error() { vec![ServerError { message: "Failed to parse \"Int\": Only integers from -128 to 127 are accepted." .to_owned(), + debug_message: None, locations: vec![Pos { line: 1, column: 14, diff --git a/tests/list.rs b/tests/list.rs index b48c5c16..da13ee4f 100644 --- a/tests/list.rs +++ b/tests/list.rs @@ -167,6 +167,7 @@ pub async fn test_array_type() { vec![ServerError { message: r#"Failed to parse "[Int!]": Expected input type "[Int; 6]", found [Int; 5]."# .to_owned(), + debug_message: None, locations: vec![Pos { line: 1, column: 22, diff --git a/tests/result.rs b/tests/result.rs index 71616e91..aac660db 100644 --- a/tests/result.rs +++ b/tests/result.rs @@ -34,12 +34,14 @@ pub async fn test_fieldresult() { errors: vec![ ServerError { message: "TestError".to_string(), + debug_message: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("error1".to_owned())], extensions: None, }, ServerError { message: "TestError".to_string(), + debug_message: None, locations: vec![Pos { line: 1, column: 19, @@ -60,6 +62,7 @@ pub async fn test_fieldresult() { .unwrap_err(), vec![ServerError { message: "TestError".to_string(), + debug_message: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("optError".to_owned())], extensions: None, @@ -74,6 +77,7 @@ pub async fn test_fieldresult() { .unwrap_err(), vec![ServerError { message: "TestError".to_string(), + debug_message: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![ PathSegment::Field("vecError".to_owned()), @@ -188,6 +192,7 @@ pub async fn test_error_propagation() { cache_control: Default::default(), errors: vec![ServerError { message: "myerror".to_string(), + debug_message: None, locations: vec![Pos { line: 1, column: 20, @@ -215,6 +220,7 @@ pub async fn test_error_propagation() { cache_control: Default::default(), errors: vec![ServerError { message: "myerror".to_string(), + debug_message: None, locations: vec![Pos { line: 1, column: 23, @@ -238,6 +244,7 @@ pub async fn test_error_propagation() { cache_control: Default::default(), errors: vec![ServerError { message: "myerror".to_string(), + debug_message: None, locations: vec![Pos { line: 1, column: 23, @@ -267,6 +274,7 @@ pub async fn test_error_propagation() { cache_control: Default::default(), errors: vec![ServerError { message: "myerror".to_string(), + debug_message: None, locations: vec![Pos { line: 1, column: 23, diff --git a/tests/subscription.rs b/tests/subscription.rs index 7d43c3f1..9763881e 100644 --- a/tests/subscription.rs +++ b/tests/subscription.rs @@ -345,6 +345,7 @@ pub async fn test_subscription_error() { stream.next().await, Some(Err(vec![ServerError { message: "TestError".to_string(), + debug_message: None, locations: vec![Pos { line: 1, column: 25 @@ -390,6 +391,7 @@ pub async fn test_subscription_fieldresult() { cache_control: Default::default(), errors: vec![ServerError { message: "StreamErr".to_string(), + debug_message: None, locations: vec![Pos { line: 1, column: 16 From 55080bb0d977dfd2226fbfc5549c186a8e529154 Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 4 Nov 2021 15:52:50 +0800 Subject: [PATCH 49/65] Update examples --- examples | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples b/examples index b1faa106..58e92b7f 160000 --- a/examples +++ b/examples @@ -1 +1 @@ -Subproject commit b1faa1061fb895ba0a325f01a09effea5fad50c8 +Subproject commit 58e92b7ffec6aee23b59584331dbe4e101aa049f From fb0ea68c3a0fde9714d5342281e7c17c68b9f7da Mon Sep 17 00:00:00 2001 From: SadiinsoSnowfall Date: Thu, 4 Nov 2021 12:03:49 +0100 Subject: [PATCH 50/65] Added map, contains and transpose function to MaybeUndefined --- src/types/maybe_undefined.rs | 155 +++++++++++++++++++++++++++++++++-- 1 file changed, 150 insertions(+), 5 deletions(-) diff --git a/src/types/maybe_undefined.rs b/src/types/maybe_undefined.rs index b677c65f..b81c2518 100644 --- a/src/types/maybe_undefined.rs +++ b/src/types/maybe_undefined.rs @@ -64,25 +64,25 @@ impl Default for MaybeUndefined { impl MaybeUndefined { /// Returns true if the `MaybeUndefined` is undefined. #[inline] - pub fn is_undefined(&self) -> bool { + pub const fn is_undefined(&self) -> bool { matches!(self, MaybeUndefined::Undefined) } /// Returns true if the `MaybeUndefined` is null. #[inline] - pub fn is_null(&self) -> bool { + pub const fn is_null(&self) -> bool { matches!(self, MaybeUndefined::Null) } /// Returns true if the `MaybeUndefined` contains value. #[inline] - pub fn is_value(&self) -> bool { + pub const fn is_value(&self) -> bool { matches!(self, MaybeUndefined::Value(_)) } /// Borrow the value, returns `None` if the the `MaybeUndefined` is `undefined` or `null`, otherwise returns `Some(T)`. #[inline] - pub fn value(&self) -> Option<&T> { + pub const fn value(&self) -> Option<&T> { match self { MaybeUndefined::Value(value) => Some(value), _ => None, @@ -100,7 +100,7 @@ impl MaybeUndefined { /// Converts the `MaybeUndefined` to `Option>`. #[inline] - pub fn as_opt_ref(&self) -> Option> { + pub const fn as_opt_ref(&self) -> Option> { match self { MaybeUndefined::Undefined => None, MaybeUndefined::Null => Some(None), @@ -121,6 +121,61 @@ impl MaybeUndefined { MaybeUndefined::Value(value) => Some(Some(value.deref())), } } + + /// Returns `true` if the `MaybeUndefined` contains the given value. + #[inline] + pub fn contains_value(&self, x: &U) -> bool + where + U: PartialEq, + { + match self { + MaybeUndefined::Value(y) => x == y, + _ => false, + } + } + + /// Returns `true` if the `MaybeUndefined` contains the given nullable value. + #[inline] + pub fn contains(&self, x: &Option) -> bool + where + U: PartialEq, + { + match self { + MaybeUndefined::Value(y) => matches!(x, Some(v) if v == y), + MaybeUndefined::Null => matches!(x, None), + _ => false, + } + } + + /// Maps a `MaybeUndefined` to `MaybeUndefined` by applying a function to the contained nullable value + #[inline] + pub fn map) -> Option>(self, f: F) -> MaybeUndefined { + match self { + MaybeUndefined::Value(v) => { + match f(Some(v)) { + Some(v) => MaybeUndefined::Value(v), + None => MaybeUndefined::Null + } + }, + MaybeUndefined::Null => { + match f(None) { + Some(v) => MaybeUndefined::Value(v), + None => MaybeUndefined::Null + } + }, + MaybeUndefined::Undefined => MaybeUndefined::Undefined, + } + } + + /// Maps a `MaybeUndefined` to `MaybeUndefined` by applying a function to the contained value + #[inline] + pub fn map_value U>(self, f: F) -> MaybeUndefined { + match self { + MaybeUndefined::Value(v) => MaybeUndefined::Value(f(v)), + MaybeUndefined::Null => MaybeUndefined::Null, + MaybeUndefined::Undefined => MaybeUndefined::Undefined, + } + } } impl Type for MaybeUndefined { @@ -157,6 +212,24 @@ impl InputType for MaybeUndefined { } } +impl MaybeUndefined> { + /// Transposes a `MaybeUndefined` of a [`Result`] into a [`Result`] of a `MaybeUndefined`. + /// + /// [`MaybeUndefined::Undefined`] will be mapped to [`Ok`]`(`[`MaybeUndefined::Undefined`]`)`. + /// [`MaybeUndefined::Null`] will be mapped to [`Ok`]`(`[`MaybeUndefined::Null`]`)`. + /// [`MaybeUndefined::Value`]`(`[`Ok`]`(_))` and [`MaybeUndefined::Value`]`(`[`Err`]`(_))` will be mapped to + /// [`Ok`]`(`[`MaybeUndefined::Value`]`(_))` and [`Err`]`(_)`. + #[inline] + pub fn transpose(self) -> Result, E> { + match self { + MaybeUndefined::Undefined => Ok(MaybeUndefined::Undefined), + MaybeUndefined::Null => Ok(MaybeUndefined::Null), + MaybeUndefined::Value(Ok(v)) => Ok(MaybeUndefined::Value(v)), + MaybeUndefined::Value(Err(e)) => Err(e) + } + } +} + impl Serialize for MaybeUndefined { fn serialize(&self, serializer: S) -> Result { match self { @@ -331,4 +404,76 @@ mod tests { r = value.as_opt_deref(); assert_eq!(r, Some(Some("abc"))); } + + #[test] + fn test_contains_value() { + let test = "abc"; + + let mut value: MaybeUndefined = MaybeUndefined::Undefined; + assert!(!value.contains_value(&test)); + + value = MaybeUndefined::Null; + assert!(!value.contains_value(&test)); + + value = MaybeUndefined::Value("abc".to_string()); + assert!(value.contains_value(&test)); + } + + + #[test] + fn test_contains() { + let test = Some("abc"); + let none: Option<&str> = None; + + let mut value: MaybeUndefined = MaybeUndefined::Undefined; + assert!(!value.contains(&test)); + assert!(!value.contains(&none)); + + value = MaybeUndefined::Null; + assert!(!value.contains(&test)); + assert!(value.contains(&none)); + + value = MaybeUndefined::Value("abc".to_string()); + assert!(value.contains(&test)); + assert!(!value.contains(&none)); + } + + #[test] + fn test_map_value() { + let mut value: MaybeUndefined = MaybeUndefined::Undefined; + assert_eq!(value.map_value(|v| v > 2), MaybeUndefined::Undefined); + + value = MaybeUndefined::Null; + assert_eq!(value.map_value(|v| v > 2), MaybeUndefined::Null); + + value = MaybeUndefined::Value(5); + assert_eq!(value.map_value(|v| v > 2), MaybeUndefined::Value(true)); + } + + #[test] + fn test_map() { + let mut value: MaybeUndefined = MaybeUndefined::Undefined; + assert_eq!(value.map(|v| Some(v.is_some())), MaybeUndefined::Undefined); + + value = MaybeUndefined::Null; + assert_eq!(value.map(|v| Some(v.is_some())), MaybeUndefined::Value(false)); + + value = MaybeUndefined::Value(5); + assert_eq!(value.map(|v| Some(v.is_some())), MaybeUndefined::Value(true)); + } + + #[test] + fn test_transpose() { + let mut value: MaybeUndefined> = MaybeUndefined::Undefined; + assert_eq!(value.transpose(), Ok(MaybeUndefined::Undefined)); + + value = MaybeUndefined::Null; + assert_eq!(value.transpose(), Ok(MaybeUndefined::Null)); + + value = MaybeUndefined::Value(Ok(5)); + assert_eq!(value.transpose(), Ok(MaybeUndefined::Value(5))); + + value = MaybeUndefined::Value(Err("eror")); + assert_eq!(value.transpose(), Err("eror")); + } } From 369f1459b7f84e407534fc67c69eaf2e8125bce7 Mon Sep 17 00:00:00 2001 From: Sunli Date: Thu, 4 Nov 2021 19:37:22 +0800 Subject: [PATCH 51/65] Rework Failure #671 --- src/error.rs | 108 ++++++++++++++++++++++--------- src/validation/visitor.rs | 2 +- tests/error_ext.rs | 129 ++++++++++++++++---------------------- tests/federation.rs | 2 +- tests/field_features.rs | 6 +- tests/guard.rs | 24 +++---- tests/input_validators.rs | 78 +++++++++++------------ tests/input_value.rs | 2 +- tests/list.rs | 2 +- tests/result.rs | 16 ++--- tests/subscription.rs | 4 +- 11 files changed, 199 insertions(+), 174 deletions(-) diff --git a/src/error.rs b/src/error.rs index 1bcacc9e..f6609eaf 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,9 @@ +use std::any::Any; use std::collections::BTreeMap; +use std::error::Error as StdError; use std::fmt::{self, Debug, Display, Formatter}; use std::marker::PhantomData; +use std::sync::Arc; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -20,15 +23,13 @@ impl ErrorExtensionValues { } /// An error in a GraphQL server. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize)] pub struct ServerError { /// An explanatory message of the error. pub message: String, - /// An explanatory message of the error. (for debug) - /// - /// This message comes from [`Failure`]. + /// The concrete error type, comes from [`Failure`]. #[serde(skip)] - pub debug_message: Option, + pub error: Option>, /// Where the error occurred. #[serde(skip_serializing_if = "Vec::is_empty", default)] pub locations: Vec, @@ -44,21 +45,53 @@ fn error_extensions_is_empty(values: &Option) -> bool { values.as_ref().map_or(true, |values| values.0.is_empty()) } +impl Debug for ServerError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("ServerError") + .field("message", &self.message) + .field("locations", &self.locations) + .field("path", &self.path) + .field("extensions", &self.extensions) + .finish() + } +} + +impl PartialEq for ServerError { + fn eq(&self, other: &Self) -> bool { + self.message.eq(&other.message) + && self.locations.eq(&other.locations) + && self.path.eq(&other.path) + && self.extensions.eq(&other.extensions) + } +} + impl ServerError { /// Create a new server error with the message. pub fn new(message: impl Into, pos: Option) -> Self { Self { message: message.into(), - debug_message: None, + error: None, locations: pos.map(|pos| vec![pos]).unwrap_or_default(), path: Vec::new(), extensions: None, } } - /// Returns `debug_message` if it is `Some`, otherwise returns `message`. - pub fn debug_message(&self) -> &str { - self.debug_message.as_deref().unwrap_or(&self.message) + /// Downcast the error object by reference. + /// + /// # Examples + /// + /// ```rust + /// use std::string::FromUtf8Error; + /// use async_graphql::{Failure, Error, ServerError, Pos}; + /// + /// let bytes = vec![0, 159]; + /// let err: Error = String::from_utf8(bytes).map_err(Failure).unwrap_err().into(); + /// let server_err: ServerError = err.into_server_error(Pos { line: 1, column: 1 }); + /// assert!(server_err.concrete_error::().is_some()); + /// ``` + pub fn concrete_error(&self) -> Option<&T> { + self.error.as_ref().map(|err| err.downcast_ref()).flatten() } #[doc(hidden)] @@ -83,7 +116,7 @@ impl From for ServerError { fn from(e: parser::Error) -> Self { Self { message: e.to_string(), - debug_message: None, + error: None, locations: e.positions().collect(), path: Vec::new(), extensions: None, @@ -172,24 +205,39 @@ impl From for InputValueError { pub type InputValueResult = Result>; /// An error with a message and optional extensions. -#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[derive(Clone, Serialize)] pub struct Error { /// The error message. pub message: String, - /// The debug error message. + /// The concrete error type, comes from [`Failure`]. #[serde(skip)] - pub debug_message: Option, + pub error: Option>, /// Extensions to the error. #[serde(skip_serializing_if = "error_extensions_is_empty")] pub extensions: Option, } +impl Debug for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("Error") + .field("message", &self.message) + .field("extensions", &self.extensions) + .finish() + } +} + +impl PartialEq for Error { + fn eq(&self, other: &Self) -> bool { + self.message.eq(&other.message) && self.extensions.eq(&other.extensions) + } +} + impl Error { /// Create an error from the given error message. pub fn new(message: impl Into) -> Self { Self { message: message.into(), - debug_message: None, + error: None, extensions: None, } } @@ -199,7 +247,7 @@ impl Error { pub fn into_server_error(self, pos: Pos) -> ServerError { ServerError { message: self.message, - debug_message: self.debug_message, + error: self.error, locations: vec![pos], path: Vec::new(), extensions: self.extensions, @@ -207,21 +255,21 @@ impl Error { } } -impl From for Error { +impl From for Error { fn from(e: T) -> Self { Self { message: e.to_string(), - debug_message: None, + error: None, extensions: None, } } } -impl From> for Error { - fn from(err: Failure) -> Self { +impl From> for Error { + fn from(e: Failure) -> Self { Self { - message: format!("{}", err.0), - debug_message: Some(format!("{:?}", err.0)), + message: e.0.to_string(), + error: Some(Arc::new(e.0)), extensions: None, } } @@ -307,7 +355,7 @@ pub trait ErrorExtensions: Sized { let Error { message, - debug_message, + error, extensions, } = self.extend(); @@ -316,7 +364,7 @@ pub trait ErrorExtensions: Sized { Error { message, - debug_message, + error, extensions: Some(extensions), } } @@ -330,17 +378,17 @@ impl ErrorExtensions for Error { // implementing for &E instead of E gives the user the possibility to implement for E which does // not conflict with this implementation acting as a fallback. -impl ErrorExtensions for &E { +impl ErrorExtensions for &E { fn extend(self) -> Error { Error { message: self.to_string(), - debug_message: None, + error: None, extensions: None, } } } -impl ErrorExtensions for Failure { +impl ErrorExtensions for Failure { fn extend(self) -> Error { Error::from(self) } @@ -381,19 +429,17 @@ where } } -/// An error type contains `T` that implements `Display` and `Debug`. -/// -/// This type can solve the problem described in [#671](https://github.com/async-graphql/async-graphql/issues/671). +/// A wrapper around a dynamic error type. #[derive(Debug)] pub struct Failure(pub T); -impl From for Failure { +impl From for Failure { fn from(err: T) -> Self { Self(err) } } -impl Failure { +impl Failure { /// Create a new failure. #[inline] pub fn new(err: T) -> Self { diff --git a/src/validation/visitor.rs b/src/validation/visitor.rs index e053fa41..a44469f7 100644 --- a/src/validation/visitor.rs +++ b/src/validation/visitor.rs @@ -851,7 +851,7 @@ impl From for ServerError { fn from(e: RuleError) -> Self { Self { message: e.message, - debug_message: None, + error: None, locations: e.locations, path: Vec::new(), extensions: e.extensions, diff --git a/tests/error_ext.rs b/tests/error_ext.rs index 34a0c991..10ad7df7 100644 --- a/tests/error_ext.rs +++ b/tests/error_ext.rs @@ -1,5 +1,4 @@ use async_graphql::*; -use std::fmt::{self, Display, Formatter}; #[tokio::test] pub async fn test_error_extensions() { @@ -79,13 +78,13 @@ pub async fn test_error_extensions() { #[tokio::test] pub async fn test_failure() { - #[derive(Debug)] - struct MyError; + #[derive(thiserror::Error, Debug, PartialEq)] + enum MyError { + #[error("error1")] + Error1, - impl Display for MyError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "my error") - } + #[error("error2")] + Error2, } struct Query; @@ -93,23 +92,23 @@ pub async fn test_failure() { #[Object] impl Query { async fn failure(&self) -> Result { - Err(Failure(MyError).into()) + Err(Failure(MyError::Error1).into()) } async fn failure2(&self) -> Result { - Err(Failure(MyError))?; + Err(Failure(MyError::Error2))?; Ok(1) } async fn failure3(&self) -> Result { - Err(Failure(MyError) + Err(Failure(MyError::Error1) .extend_with(|_, values| values.set("a", 1)) .extend_with(|_, values| values.set("b", 2)))?; Ok(1) } async fn failure4(&self) -> Result { - Err(Failure(MyError)) + Err(Failure(MyError::Error2)) .extend_err(|_, values| values.set("a", 1)) .extend_err(|_, values| values.set("b", 2))?; Ok(1) @@ -117,73 +116,53 @@ pub async fn test_failure() { } let schema = Schema::new(Query, EmptyMutation, EmptySubscription); + let err = schema + .execute("{ failure }") + .await + .into_result() + .unwrap_err() + .remove(0); + assert_eq!(err.concrete_error::().unwrap(), &MyError::Error1); + + let err = schema + .execute("{ failure2 }") + .await + .into_result() + .unwrap_err() + .remove(0); + assert_eq!(err.concrete_error::().unwrap(), &MyError::Error2); + + let err = schema + .execute("{ failure3 }") + .await + .into_result() + .unwrap_err() + .remove(0); + assert_eq!(err.concrete_error::().unwrap(), &MyError::Error1); assert_eq!( - schema - .execute("{ failure }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: "my error".to_string(), - debug_message: Some("MyError".to_string()), - locations: vec![Pos { line: 1, column: 3 }], - path: vec![PathSegment::Field("failure".to_string())], - extensions: None - }] + err.extensions, + Some({ + let mut values = ErrorExtensionValues::default(); + values.set("a", 1); + values.set("b", 2); + values + }) ); + let err = schema + .execute("{ failure4 }") + .await + .into_result() + .unwrap_err() + .remove(0); + assert_eq!(err.concrete_error::().unwrap(), &MyError::Error2); assert_eq!( - schema - .execute("{ failure2 }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: "my error".to_string(), - debug_message: Some("MyError".to_string()), - locations: vec![Pos { line: 1, column: 3 }], - path: vec![PathSegment::Field("failure2".to_string())], - extensions: None - }] - ); - - assert_eq!( - schema - .execute("{ failure3 }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: "my error".to_string(), - debug_message: Some("MyError".to_string()), - locations: vec![Pos { line: 1, column: 3 }], - path: vec![PathSegment::Field("failure3".to_string())], - extensions: Some({ - let mut values = ErrorExtensionValues::default(); - values.set("a", 1); - values.set("b", 2); - values - }) - }] - ); - - assert_eq!( - schema - .execute("{ failure4 }") - .await - .into_result() - .unwrap_err(), - vec![ServerError { - message: "my error".to_string(), - debug_message: Some("MyError".to_string()), - locations: vec![Pos { line: 1, column: 3 }], - path: vec![PathSegment::Field("failure4".to_string())], - extensions: Some({ - let mut values = ErrorExtensionValues::default(); - values.set("a", 1); - values.set("b", 2); - values - }) - }] + err.extensions, + Some({ + let mut values = ErrorExtensionValues::default(); + values.set("a", 1); + values.set("b", 2); + values + }) ); } diff --git a/tests/federation.rs b/tests/federation.rs index acf46d4e..33b6fdc9 100644 --- a/tests/federation.rs +++ b/tests/federation.rs @@ -242,7 +242,7 @@ pub async fn test_find_entity_with_context() { schema.execute(query).await.into_result().unwrap_err(), vec![ServerError { message: "Not found".to_string(), - debug_message: None, + error: None, locations: vec![Pos { line: 2, column: 13 diff --git a/tests/field_features.rs b/tests/field_features.rs index 0b85ae19..ce10c995 100644 --- a/tests/field_features.rs +++ b/tests/field_features.rs @@ -85,7 +85,7 @@ pub async fn test_field_features() { vec![ServerError { message: r#"Unknown field "valueAbc" on type "QueryRoot". Did you mean "value"?"# .to_owned(), - debug_message: None, + error: None, locations: vec![Pos { column: 3, line: 1 }], path: Vec::new(), extensions: None, @@ -114,7 +114,7 @@ pub async fn test_field_features() { vec![ServerError { message: r#"Unknown field "valueAbc" on type "MyObj". Did you mean "value"?"# .to_owned(), - debug_message: None, + error: None, locations: vec![Pos { column: 9, line: 1 }], path: Vec::new(), extensions: None, @@ -150,7 +150,7 @@ pub async fn test_field_features() { .errors, vec![ServerError { message: r#"Unknown field "valuesAbc" on type "SubscriptionRoot". Did you mean "values", "valuesBson"?"#.to_owned(), - debug_message: None, + error: None, locations: vec![Pos { column: 16, line: 1 diff --git a/tests/guard.rs b/tests/guard.rs index 89396ac2..7ef868c8 100644 --- a/tests/guard.rs +++ b/tests/guard.rs @@ -99,7 +99,7 @@ pub async fn test_guard_simple_rule() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -128,7 +128,7 @@ pub async fn test_guard_simple_rule() { .errors, vec![ServerError { message: "Forbidden".to_string(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 16 @@ -178,7 +178,7 @@ pub async fn test_guard_and_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -198,7 +198,7 @@ pub async fn test_guard_and_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -218,7 +218,7 @@ pub async fn test_guard_and_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -288,7 +288,7 @@ pub async fn test_guard_or_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -338,7 +338,7 @@ pub async fn test_guard_chain_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -359,7 +359,7 @@ pub async fn test_guard_chain_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -380,7 +380,7 @@ pub async fn test_guard_chain_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -401,7 +401,7 @@ pub async fn test_guard_chain_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -493,7 +493,7 @@ pub async fn test_guard_race_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -549,7 +549,7 @@ pub async fn test_guard_use_params() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("get".to_owned())], extensions: None, diff --git a/tests/input_validators.rs b/tests/input_validators.rs index 139d210c..eb0cd1b6 100644 --- a/tests/input_validators.rs +++ b/tests/input_validators.rs @@ -67,7 +67,7 @@ pub async fn test_input_validator_string_min_length() { .expect_err(&should_fail_msg), vec![ServerError { message: field_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 17 @@ -85,7 +85,7 @@ pub async fn test_input_validator_string_min_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 14 @@ -179,7 +179,7 @@ pub async fn test_input_validator_string_max_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 17 @@ -197,7 +197,7 @@ pub async fn test_input_validator_string_max_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 14 @@ -290,7 +290,7 @@ pub async fn test_input_validator_chars_min_length() { .expect_err(&should_fail_msg), vec![ServerError { message: field_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 17 @@ -308,7 +308,7 @@ pub async fn test_input_validator_chars_min_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 14 @@ -403,7 +403,7 @@ pub async fn test_input_validator_chars_max_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 17 @@ -421,7 +421,7 @@ pub async fn test_input_validator_chars_max_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 14 @@ -542,7 +542,7 @@ pub async fn test_input_validator_string_email() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 17 @@ -561,7 +561,7 @@ pub async fn test_input_validator_string_email() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 14 @@ -692,7 +692,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg.clone(), - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 17 @@ -711,7 +711,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg.clone(), - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 14 @@ -729,7 +729,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 17 @@ -748,7 +748,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 14 @@ -806,7 +806,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 17 @@ -825,7 +825,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 14 @@ -867,7 +867,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 17 @@ -886,7 +886,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 14 @@ -947,7 +947,7 @@ pub async fn test_input_validator_int_range() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 17 @@ -965,7 +965,7 @@ pub async fn test_input_validator_int_range() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 14 @@ -1054,7 +1054,7 @@ pub async fn test_input_validator_int_less_than() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 17 @@ -1072,7 +1072,7 @@ pub async fn test_input_validator_int_less_than() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 14 @@ -1163,7 +1163,7 @@ pub async fn test_input_validator_int_greater_than() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 17 @@ -1181,7 +1181,7 @@ pub async fn test_input_validator_int_greater_than() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 14 @@ -1265,7 +1265,7 @@ pub async fn test_input_validator_int_nonzero() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 17 @@ -1283,7 +1283,7 @@ pub async fn test_input_validator_int_nonzero() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 14 @@ -1371,7 +1371,7 @@ pub async fn test_input_validator_int_equal() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 17 @@ -1389,7 +1389,7 @@ pub async fn test_input_validator_int_equal() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 14 @@ -1489,7 +1489,7 @@ pub async fn test_input_validator_list_max_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 17 @@ -1507,7 +1507,7 @@ pub async fn test_input_validator_list_max_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 14 @@ -1607,7 +1607,7 @@ pub async fn test_input_validator_list_min_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 17 @@ -1625,7 +1625,7 @@ pub async fn test_input_validator_list_min_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 14 @@ -1733,7 +1733,7 @@ pub async fn test_input_validator_operator_or() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 17 @@ -1751,7 +1751,7 @@ pub async fn test_input_validator_operator_or() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 14 @@ -1852,7 +1852,7 @@ pub async fn test_input_validator_operator_and() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 17 @@ -1870,7 +1870,7 @@ pub async fn test_input_validator_operator_and() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 14 @@ -1974,7 +1974,7 @@ pub async fn test_input_validator_variable() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 37 @@ -1992,7 +1992,7 @@ pub async fn test_input_validator_variable() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 34 @@ -2079,7 +2079,7 @@ pub async fn test_custom_input_validator_with_extensions() { .expect_err(should_fail_msg), vec![ServerError { message: field_error_msg.into(), - debug_message: None, + error: None, locations: vec!(Pos { line: 1, column: 17 diff --git a/tests/input_value.rs b/tests/input_value.rs index 74560091..1387f042 100644 --- a/tests/input_value.rs +++ b/tests/input_value.rs @@ -18,7 +18,7 @@ pub async fn test_input_value_custom_error() { vec![ServerError { message: "Failed to parse \"Int\": Only integers from -128 to 127 are accepted." .to_owned(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 14, diff --git a/tests/list.rs b/tests/list.rs index da13ee4f..de2afe98 100644 --- a/tests/list.rs +++ b/tests/list.rs @@ -167,7 +167,7 @@ pub async fn test_array_type() { vec![ServerError { message: r#"Failed to parse "[Int!]": Expected input type "[Int; 6]", found [Int; 5]."# .to_owned(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 22, diff --git a/tests/result.rs b/tests/result.rs index aac660db..2487cc22 100644 --- a/tests/result.rs +++ b/tests/result.rs @@ -34,14 +34,14 @@ pub async fn test_fieldresult() { errors: vec![ ServerError { message: "TestError".to_string(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("error1".to_owned())], extensions: None, }, ServerError { message: "TestError".to_string(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 19, @@ -62,7 +62,7 @@ pub async fn test_fieldresult() { .unwrap_err(), vec![ServerError { message: "TestError".to_string(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("optError".to_owned())], extensions: None, @@ -77,7 +77,7 @@ pub async fn test_fieldresult() { .unwrap_err(), vec![ServerError { message: "TestError".to_string(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![ PathSegment::Field("vecError".to_owned()), @@ -192,7 +192,7 @@ pub async fn test_error_propagation() { cache_control: Default::default(), errors: vec![ServerError { message: "myerror".to_string(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 20, @@ -220,7 +220,7 @@ pub async fn test_error_propagation() { cache_control: Default::default(), errors: vec![ServerError { message: "myerror".to_string(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 23, @@ -244,7 +244,7 @@ pub async fn test_error_propagation() { cache_control: Default::default(), errors: vec![ServerError { message: "myerror".to_string(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 23, @@ -274,7 +274,7 @@ pub async fn test_error_propagation() { cache_control: Default::default(), errors: vec![ServerError { message: "myerror".to_string(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 23, diff --git a/tests/subscription.rs b/tests/subscription.rs index 9763881e..e948a5a6 100644 --- a/tests/subscription.rs +++ b/tests/subscription.rs @@ -345,7 +345,7 @@ pub async fn test_subscription_error() { stream.next().await, Some(Err(vec![ServerError { message: "TestError".to_string(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 25 @@ -391,7 +391,7 @@ pub async fn test_subscription_fieldresult() { cache_control: Default::default(), errors: vec![ServerError { message: "StreamErr".to_string(), - debug_message: None, + error: None, locations: vec![Pos { line: 1, column: 16 From 0e234f52e7d8f7e7ecc79e9305a67b51ab019abb Mon Sep 17 00:00:00 2001 From: SadiinsoSnowfall Date: Thu, 4 Nov 2021 13:47:54 +0100 Subject: [PATCH 52/65] Updated changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ebda6e2..32e9e137 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `chrono::Duration` custom scalar. [#689](https://github.com/async-graphql/async-graphql/pull/689) - Implement `From>>` for `MaybeUndefined`. -- Add `MaybeUndefined::as_opt_ref` and `MaybeUndefined::as_opt_deref` methods. +- Add `MaybeUndefined::as_opt_ref`, `MaybeUndefined::as_opt_deref`, `MaybeUndefined::map`, `MaybeUndefined::map_value`, `MaybeUndefined::contains`, `MaybeUndefined::contains_value`, and `MaybeUndefined::transpose` methods. +- Made `MaybeUndefined::is_undefined`, `MaybeUndefined::is_null`, `MaybeUndefined::is_value`, `MaybeUndefined::value` and `MaybeUndefined::as_opt_ref` const. - Add `Failure` type. [#671](https://github.com/async-graphql/async-graphql/issues/671) - [async-graphql-axum] Bump axum from `0.2.5` to `0.3`. From 5b44b98c257f0faa63d6e1f59e92fd6df58ae704 Mon Sep 17 00:00:00 2001 From: SadiinsoSnowfall Date: Thu, 4 Nov 2021 14:05:36 +0100 Subject: [PATCH 53/65] Fixed clippy warning --- src/types/maybe_undefined.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/maybe_undefined.rs b/src/types/maybe_undefined.rs index b81c2518..788f41b2 100644 --- a/src/types/maybe_undefined.rs +++ b/src/types/maybe_undefined.rs @@ -143,7 +143,7 @@ impl MaybeUndefined { match self { MaybeUndefined::Value(y) => matches!(x, Some(v) if v == y), MaybeUndefined::Null => matches!(x, None), - _ => false, + MaybeUndefined::Undefined => false, } } From 78251eff9e52f22c6c7ca388a4f6cde2f2340ec5 Mon Sep 17 00:00:00 2001 From: Sunli Date: Fri, 5 Nov 2021 09:25:46 +0800 Subject: [PATCH 54/65] [async-graphql-poem] Export the HTTP headers in the `Context`. --- examples | 2 +- integrations/poem/Cargo.toml | 2 +- integrations/poem/src/lib.rs | 2 ++ integrations/poem/src/query.rs | 9 +++--- integrations/poem/src/response.rs | 50 +++++++++++++++++++++++++++++++ 5 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 integrations/poem/src/response.rs diff --git a/examples b/examples index 58e92b7f..b3915aa7 160000 --- a/examples +++ b/examples @@ -1 +1 @@ -Subproject commit 58e92b7ffec6aee23b59584331dbe4e101aa049f +Subproject commit b3915aa777e434e9049c4d4a2448ebbd2524f6b1 diff --git a/integrations/poem/Cargo.toml b/integrations/poem/Cargo.toml index 143c10fc..9c5a6fbd 100644 --- a/integrations/poem/Cargo.toml +++ b/integrations/poem/Cargo.toml @@ -14,7 +14,7 @@ categories = ["network-programming", "asynchronous"] [dependencies] async-graphql = { path = "../..", version = "=2.11.0" } -poem = { version = "1.0.13", features = ["websocket"] } +poem = { version = "1.0.19", features = ["websocket"] } futures-util = { version = "0.3.13", default-features = false } serde_json = "1.0.66" tokio-util = { version = "0.6.7", features = ["compat"] } diff --git a/integrations/poem/src/lib.rs b/integrations/poem/src/lib.rs index 0a140beb..7fcc5ebd 100644 --- a/integrations/poem/src/lib.rs +++ b/integrations/poem/src/lib.rs @@ -4,8 +4,10 @@ mod extractor; mod query; +mod response; mod subscription; pub use extractor::{GraphQLBatchRequest, GraphQLRequest}; pub use query::GraphQL; +pub use response::{GraphQLBatchResponse, GraphQLResponse}; pub use subscription::GraphQLSubscription; diff --git a/integrations/poem/src/query.rs b/integrations/poem/src/query.rs index 6dbc0478..73d20fbd 100644 --- a/integrations/poem/src/query.rs +++ b/integrations/poem/src/query.rs @@ -1,8 +1,7 @@ -use async_graphql::{BatchResponse as GraphQLBatchResponse, ObjectType, Schema, SubscriptionType}; -use poem::web::Json; +use async_graphql::{ObjectType, Schema, SubscriptionType}; use poem::{async_trait, Endpoint, FromRequest, Request, Result}; -use crate::GraphQLBatchRequest; +use crate::{GraphQLBatchRequest, GraphQLBatchResponse}; /// A GraphQL query endpoint. /// @@ -45,11 +44,11 @@ where Mutation: ObjectType + 'static, Subscription: SubscriptionType + 'static, { - type Output = Result>; + type Output = Result; async fn call(&self, req: Request) -> Self::Output { let (req, mut body) = req.split(); let req = GraphQLBatchRequest::from_request(&req, &mut body).await?; - Ok(Json(self.schema.execute_batch(req.0).await)) + Ok(GraphQLBatchResponse(self.schema.execute_batch(req.0).await)) } } diff --git a/integrations/poem/src/response.rs b/integrations/poem/src/response.rs new file mode 100644 index 00000000..7e0dfd68 --- /dev/null +++ b/integrations/poem/src/response.rs @@ -0,0 +1,50 @@ +use poem::http::header::HeaderName; +use poem::web::Json; +use poem::{IntoResponse, Response}; + +/// Response for `async_graphql::Request`. +pub struct GraphQLResponse(pub async_graphql::Response); + +impl From for GraphQLResponse { + fn from(resp: async_graphql::Response) -> Self { + Self(resp) + } +} + +impl IntoResponse for GraphQLResponse { + fn into_response(self) -> Response { + GraphQLBatchResponse(self.0.into()).into_response() + } +} + +/// Response for `async_graphql::BatchRequest`. +pub struct GraphQLBatchResponse(pub async_graphql::BatchResponse); + +impl From for GraphQLBatchResponse { + fn from(resp: async_graphql::BatchResponse) -> Self { + Self(resp) + } +} + +impl IntoResponse for GraphQLBatchResponse { + fn into_response(self) -> Response { + let mut resp = Json(&self.0).into_response(); + + if self.0.is_ok() { + if let Some(cache_control) = self.0.cache_control().value() { + if let Ok(value) = cache_control.try_into() { + resp.headers_mut().insert("cache-control", value); + } + } + } + + for (name, value) in self.0.http_headers() { + if let (Ok(name), Ok(value)) = (TryInto::::try_into(name), value.try_into()) + { + resp.headers_mut().append(name, value); + } + } + + resp + } +} From 0bcf4f55b86a00ded83f7b36e91d5d0b751e3153 Mon Sep 17 00:00:00 2001 From: Sunli Date: Fri, 5 Nov 2021 09:27:20 +0800 Subject: [PATCH 55/65] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ebda6e2..42f8face 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `MaybeUndefined::as_opt_ref` and `MaybeUndefined::as_opt_deref` methods. - Add `Failure` type. [#671](https://github.com/async-graphql/async-graphql/issues/671) - [async-graphql-axum] Bump axum from `0.2.5` to `0.3`. +- [async-graphql-poem] Export the HTTP headers in the `Context`. ## [2.11.0] 2021-11-03 From bb9bd08b11061abc7393ce882981494eb77b30d7 Mon Sep 17 00:00:00 2001 From: Sunli Date: Fri, 5 Nov 2021 09:29:50 +0800 Subject: [PATCH 56/65] rustfmt --- src/types/maybe_undefined.rs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/types/maybe_undefined.rs b/src/types/maybe_undefined.rs index 788f41b2..fe722062 100644 --- a/src/types/maybe_undefined.rs +++ b/src/types/maybe_undefined.rs @@ -151,17 +151,13 @@ impl MaybeUndefined { #[inline] pub fn map) -> Option>(self, f: F) -> MaybeUndefined { match self { - MaybeUndefined::Value(v) => { - match f(Some(v)) { - Some(v) => MaybeUndefined::Value(v), - None => MaybeUndefined::Null - } + MaybeUndefined::Value(v) => match f(Some(v)) { + Some(v) => MaybeUndefined::Value(v), + None => MaybeUndefined::Null, }, - MaybeUndefined::Null => { - match f(None) { - Some(v) => MaybeUndefined::Value(v), - None => MaybeUndefined::Null - } + MaybeUndefined::Null => match f(None) { + Some(v) => MaybeUndefined::Value(v), + None => MaybeUndefined::Null, }, MaybeUndefined::Undefined => MaybeUndefined::Undefined, } @@ -225,7 +221,7 @@ impl MaybeUndefined> { MaybeUndefined::Undefined => Ok(MaybeUndefined::Undefined), MaybeUndefined::Null => Ok(MaybeUndefined::Null), MaybeUndefined::Value(Ok(v)) => Ok(MaybeUndefined::Value(v)), - MaybeUndefined::Value(Err(e)) => Err(e) + MaybeUndefined::Value(Err(e)) => Err(e), } } } @@ -419,7 +415,6 @@ mod tests { assert!(value.contains_value(&test)); } - #[test] fn test_contains() { let test = Some("abc"); @@ -456,10 +451,16 @@ mod tests { assert_eq!(value.map(|v| Some(v.is_some())), MaybeUndefined::Undefined); value = MaybeUndefined::Null; - assert_eq!(value.map(|v| Some(v.is_some())), MaybeUndefined::Value(false)); + assert_eq!( + value.map(|v| Some(v.is_some())), + MaybeUndefined::Value(false) + ); value = MaybeUndefined::Value(5); - assert_eq!(value.map(|v| Some(v.is_some())), MaybeUndefined::Value(true)); + assert_eq!( + value.map(|v| Some(v.is_some())), + MaybeUndefined::Value(true) + ); } #[test] From d64e075164eb0b86a00f7336d5c73a8a163067fc Mon Sep 17 00:00:00 2001 From: Sunli Date: Fri, 5 Nov 2021 10:09:46 +0800 Subject: [PATCH 57/65] Update examples --- examples | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples b/examples index b3915aa7..8641a72c 160000 --- a/examples +++ b/examples @@ -1 +1 @@ -Subproject commit b3915aa777e434e9049c4d4a2448ebbd2524f6b1 +Subproject commit 8641a72c8ccb78e924e2550cd1fbfa2e62319174 From 4d65f9c7393fbe7db001a8b27b3e949b3d8be080 Mon Sep 17 00:00:00 2001 From: Sunli Date: Fri, 5 Nov 2021 19:05:49 +0800 Subject: [PATCH 58/65] Rework Failure2 #671 --- src/error.rs | 31 +++++++++++++++++++------------ tests/error_ext.rs | 26 ++++++++++++++++++++++---- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/error.rs b/src/error.rs index f6609eaf..6c79bf40 100644 --- a/src/error.rs +++ b/src/error.rs @@ -86,7 +86,7 @@ impl ServerError { /// use async_graphql::{Failure, Error, ServerError, Pos}; /// /// let bytes = vec![0, 159]; - /// let err: Error = String::from_utf8(bytes).map_err(Failure).unwrap_err().into(); + /// let err: Error = String::from_utf8(bytes).map_err(Failure::new).unwrap_err().into(); /// let server_err: ServerError = err.into_server_error(Pos { line: 1, column: 1 }); /// assert!(server_err.concrete_error::().is_some()); /// ``` @@ -265,11 +265,11 @@ impl From for Error { } } -impl From> for Error { - fn from(e: Failure) -> Self { +impl From for Error { + fn from(e: Failure) -> Self { Self { - message: e.0.to_string(), - error: Some(Arc::new(e.0)), + message: e.message, + error: Some(e.error), extensions: None, } } @@ -388,7 +388,8 @@ impl ErrorExtensions for &E { } } -impl ErrorExtensions for Failure { +impl ErrorExtensions for Failure { + #[inline] fn extend(self) -> Error { Error::from(self) } @@ -431,18 +432,24 @@ where /// A wrapper around a dynamic error type. #[derive(Debug)] -pub struct Failure(pub T); +pub struct Failure { + message: String, + error: Arc, +} -impl From for Failure { +impl From for Failure { fn from(err: T) -> Self { - Self(err) + Self { + message: err.to_string(), + error: Arc::new(err), + } } } -impl Failure { +impl Failure { /// Create a new failure. #[inline] - pub fn new(err: T) -> Self { - Self(err) + pub fn new(err: T) -> Self { + From::from(err) } } diff --git a/tests/error_ext.rs b/tests/error_ext.rs index 10ad7df7..b723f823 100644 --- a/tests/error_ext.rs +++ b/tests/error_ext.rs @@ -92,23 +92,23 @@ pub async fn test_failure() { #[Object] impl Query { async fn failure(&self) -> Result { - Err(Failure(MyError::Error1).into()) + Err(Failure::new(MyError::Error1).into()) } async fn failure2(&self) -> Result { - Err(Failure(MyError::Error2))?; + Err(Failure::new(MyError::Error2))?; Ok(1) } async fn failure3(&self) -> Result { - Err(Failure(MyError::Error1) + Err(Failure::new(MyError::Error1) .extend_with(|_, values| values.set("a", 1)) .extend_with(|_, values| values.set("b", 2)))?; Ok(1) } async fn failure4(&self) -> Result { - Err(Failure(MyError::Error2)) + Err(Failure::new(MyError::Error2)) .extend_err(|_, values| values.set("a", 1)) .extend_err(|_, values| values.set("b", 2))?; Ok(1) @@ -166,3 +166,21 @@ pub async fn test_failure() { }) ); } + +#[tokio::test] +pub async fn test_failure2() { + #[derive(thiserror::Error, Debug, PartialEq)] + enum MyError { + #[error("error1")] + Error1, + } + + struct Query; + + #[Object] + impl Query { + async fn failure(&self) -> Result { + Err(MyError::Error1)? + } + } +} From dbc08628946fc34d2386b57e0f7719d033469eec Mon Sep 17 00:00:00 2001 From: Sunli Date: Sun, 7 Nov 2021 19:11:17 +0800 Subject: [PATCH 59/65] Rework Failure 3 #671 --- CHANGELOG.md | 2 +- src/error.rs | 52 +++++++++++++------------- src/lib.rs | 4 +- src/validation/visitor.rs | 2 +- tests/error_ext.rs | 18 ++++----- tests/federation.rs | 2 +- tests/field_features.rs | 6 +-- tests/guard.rs | 24 ++++++------ tests/input_validators.rs | 78 +++++++++++++++++++-------------------- tests/input_value.rs | 2 +- tests/list.rs | 2 +- tests/result.rs | 16 ++++---- tests/subscription.rs | 4 +- 13 files changed, 106 insertions(+), 106 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4543ba02..1d6e5614 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Implement `From>>` for `MaybeUndefined`. - Add `MaybeUndefined::as_opt_ref`, `MaybeUndefined::as_opt_deref`, `MaybeUndefined::map`, `MaybeUndefined::map_value`, `MaybeUndefined::contains`, `MaybeUndefined::contains_value`, and `MaybeUndefined::transpose` methods. - Made `MaybeUndefined::is_undefined`, `MaybeUndefined::is_null`, `MaybeUndefined::is_value`, `MaybeUndefined::value` and `MaybeUndefined::as_opt_ref` const. -- Add `Failure` type. [#671](https://github.com/async-graphql/async-graphql/issues/671) +- Add `ResolverError` type. [#671](https://github.com/async-graphql/async-graphql/issues/671) - [async-graphql-axum] Bump axum from `0.2.5` to `0.3`. - [async-graphql-poem] Export the HTTP headers in the `Context`. diff --git a/src/error.rs b/src/error.rs index 6c79bf40..871c7c61 100644 --- a/src/error.rs +++ b/src/error.rs @@ -27,9 +27,9 @@ impl ErrorExtensionValues { pub struct ServerError { /// An explanatory message of the error. pub message: String, - /// The concrete error type, comes from [`Failure`]. + /// The source of the error, comes from [`ResolverError`]. #[serde(skip)] - pub error: Option>, + pub source: Option>, /// Where the error occurred. #[serde(skip_serializing_if = "Vec::is_empty", default)] pub locations: Vec, @@ -70,28 +70,28 @@ impl ServerError { pub fn new(message: impl Into, pos: Option) -> Self { Self { message: message.into(), - error: None, + source: None, locations: pos.map(|pos| vec![pos]).unwrap_or_default(), path: Vec::new(), extensions: None, } } - /// Downcast the error object by reference. + /// Get the source of the error. /// /// # Examples /// /// ```rust /// use std::string::FromUtf8Error; - /// use async_graphql::{Failure, Error, ServerError, Pos}; + /// use async_graphql::{ResolverError, Error, ServerError, Pos}; /// /// let bytes = vec![0, 159]; - /// let err: Error = String::from_utf8(bytes).map_err(Failure::new).unwrap_err().into(); + /// let err: Error = String::from_utf8(bytes).map_err(ResolverError::new).unwrap_err().into(); /// let server_err: ServerError = err.into_server_error(Pos { line: 1, column: 1 }); - /// assert!(server_err.concrete_error::().is_some()); + /// assert!(server_err.source::().is_some()); /// ``` - pub fn concrete_error(&self) -> Option<&T> { - self.error.as_ref().map(|err| err.downcast_ref()).flatten() + pub fn source(&self) -> Option<&T> { + self.source.as_ref().map(|err| err.downcast_ref()).flatten() } #[doc(hidden)] @@ -116,7 +116,7 @@ impl From for ServerError { fn from(e: parser::Error) -> Self { Self { message: e.to_string(), - error: None, + source: None, locations: e.positions().collect(), path: Vec::new(), extensions: None, @@ -209,9 +209,9 @@ pub type InputValueResult = Result>; pub struct Error { /// The error message. pub message: String, - /// The concrete error type, comes from [`Failure`]. + /// The source of the error, comes from [`ResolverError`]. #[serde(skip)] - pub error: Option>, + pub source: Option>, /// Extensions to the error. #[serde(skip_serializing_if = "error_extensions_is_empty")] pub extensions: Option, @@ -237,7 +237,7 @@ impl Error { pub fn new(message: impl Into) -> Self { Self { message: message.into(), - error: None, + source: None, extensions: None, } } @@ -247,7 +247,7 @@ impl Error { pub fn into_server_error(self, pos: Pos) -> ServerError { ServerError { message: self.message, - error: self.error, + source: self.source, locations: vec![pos], path: Vec::new(), extensions: self.extensions, @@ -259,17 +259,17 @@ impl From for Error { fn from(e: T) -> Self { Self { message: e.to_string(), - error: None, + source: None, extensions: None, } } } -impl From for Error { - fn from(e: Failure) -> Self { +impl From for Error { + fn from(e: ResolverError) -> Self { Self { message: e.message, - error: Some(e.error), + source: Some(e.error), extensions: None, } } @@ -355,7 +355,7 @@ pub trait ErrorExtensions: Sized { let Error { message, - error, + source, extensions, } = self.extend(); @@ -364,7 +364,7 @@ pub trait ErrorExtensions: Sized { Error { message, - error, + source, extensions: Some(extensions), } } @@ -382,13 +382,13 @@ impl ErrorExtensions for &E { fn extend(self) -> Error { Error { message: self.to_string(), - error: None, + source: None, extensions: None, } } } -impl ErrorExtensions for Failure { +impl ErrorExtensions for ResolverError { #[inline] fn extend(self) -> Error { Error::from(self) @@ -430,14 +430,14 @@ where } } -/// A wrapper around a dynamic error type. +/// A wrapper around a dynamic error type for resolver. #[derive(Debug)] -pub struct Failure { +pub struct ResolverError { message: String, error: Arc, } -impl From for Failure { +impl From for ResolverError { fn from(err: T) -> Self { Self { message: err.to_string(), @@ -446,7 +446,7 @@ impl From for Failure { } } -impl Failure { +impl ResolverError { /// Create a new failure. #[inline] pub fn new(err: T) -> Self { diff --git a/src/lib.rs b/src/lib.rs index a65539fa..37da9c4d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -209,8 +209,8 @@ pub use base::{ Type, UnionType, }; pub use error::{ - Error, ErrorExtensionValues, ErrorExtensions, Failure, InputValueError, InputValueResult, - ParseRequestError, PathSegment, Result, ResultExt, ServerError, ServerResult, + Error, ErrorExtensionValues, ErrorExtensions, InputValueError, InputValueResult, + ParseRequestError, PathSegment, ResolverError, Result, ResultExt, ServerError, ServerResult, }; pub use look_ahead::Lookahead; pub use registry::CacheControl; diff --git a/src/validation/visitor.rs b/src/validation/visitor.rs index a44469f7..96c37717 100644 --- a/src/validation/visitor.rs +++ b/src/validation/visitor.rs @@ -851,7 +851,7 @@ impl From for ServerError { fn from(e: RuleError) -> Self { Self { message: e.message, - error: None, + source: None, locations: e.locations, path: Vec::new(), extensions: e.extensions, diff --git a/tests/error_ext.rs b/tests/error_ext.rs index b723f823..4ca56a19 100644 --- a/tests/error_ext.rs +++ b/tests/error_ext.rs @@ -92,23 +92,23 @@ pub async fn test_failure() { #[Object] impl Query { async fn failure(&self) -> Result { - Err(Failure::new(MyError::Error1).into()) + Err(ResolverError::new(MyError::Error1).into()) } async fn failure2(&self) -> Result { - Err(Failure::new(MyError::Error2))?; + Err(ResolverError::new(MyError::Error2))?; Ok(1) } async fn failure3(&self) -> Result { - Err(Failure::new(MyError::Error1) + Err(ResolverError::new(MyError::Error1) .extend_with(|_, values| values.set("a", 1)) .extend_with(|_, values| values.set("b", 2)))?; Ok(1) } async fn failure4(&self) -> Result { - Err(Failure::new(MyError::Error2)) + Err(ResolverError::new(MyError::Error2)) .extend_err(|_, values| values.set("a", 1)) .extend_err(|_, values| values.set("b", 2))?; Ok(1) @@ -122,7 +122,7 @@ pub async fn test_failure() { .into_result() .unwrap_err() .remove(0); - assert_eq!(err.concrete_error::().unwrap(), &MyError::Error1); + assert_eq!(err.source::().unwrap(), &MyError::Error1); let err = schema .execute("{ failure2 }") @@ -130,7 +130,7 @@ pub async fn test_failure() { .into_result() .unwrap_err() .remove(0); - assert_eq!(err.concrete_error::().unwrap(), &MyError::Error2); + assert_eq!(err.source::().unwrap(), &MyError::Error2); let err = schema .execute("{ failure3 }") @@ -138,7 +138,7 @@ pub async fn test_failure() { .into_result() .unwrap_err() .remove(0); - assert_eq!(err.concrete_error::().unwrap(), &MyError::Error1); + assert_eq!(err.source::().unwrap(), &MyError::Error1); assert_eq!( err.extensions, Some({ @@ -155,7 +155,7 @@ pub async fn test_failure() { .into_result() .unwrap_err() .remove(0); - assert_eq!(err.concrete_error::().unwrap(), &MyError::Error2); + assert_eq!(err.source::().unwrap(), &MyError::Error2); assert_eq!( err.extensions, Some({ @@ -179,7 +179,7 @@ pub async fn test_failure2() { #[Object] impl Query { - async fn failure(&self) -> Result { + async fn failure(&self) -> Result { Err(MyError::Error1)? } } diff --git a/tests/federation.rs b/tests/federation.rs index 33b6fdc9..d335ad91 100644 --- a/tests/federation.rs +++ b/tests/federation.rs @@ -242,7 +242,7 @@ pub async fn test_find_entity_with_context() { schema.execute(query).await.into_result().unwrap_err(), vec![ServerError { message: "Not found".to_string(), - error: None, + source: None, locations: vec![Pos { line: 2, column: 13 diff --git a/tests/field_features.rs b/tests/field_features.rs index ce10c995..0329cb47 100644 --- a/tests/field_features.rs +++ b/tests/field_features.rs @@ -85,7 +85,7 @@ pub async fn test_field_features() { vec![ServerError { message: r#"Unknown field "valueAbc" on type "QueryRoot". Did you mean "value"?"# .to_owned(), - error: None, + source: None, locations: vec![Pos { column: 3, line: 1 }], path: Vec::new(), extensions: None, @@ -114,7 +114,7 @@ pub async fn test_field_features() { vec![ServerError { message: r#"Unknown field "valueAbc" on type "MyObj". Did you mean "value"?"# .to_owned(), - error: None, + source: None, locations: vec![Pos { column: 9, line: 1 }], path: Vec::new(), extensions: None, @@ -150,7 +150,7 @@ pub async fn test_field_features() { .errors, vec![ServerError { message: r#"Unknown field "valuesAbc" on type "SubscriptionRoot". Did you mean "values", "valuesBson"?"#.to_owned(), - error: None, + source: None, locations: vec![Pos { column: 16, line: 1 diff --git a/tests/guard.rs b/tests/guard.rs index 7ef868c8..829eb586 100644 --- a/tests/guard.rs +++ b/tests/guard.rs @@ -99,7 +99,7 @@ pub async fn test_guard_simple_rule() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -128,7 +128,7 @@ pub async fn test_guard_simple_rule() { .errors, vec![ServerError { message: "Forbidden".to_string(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 16 @@ -178,7 +178,7 @@ pub async fn test_guard_and_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -198,7 +198,7 @@ pub async fn test_guard_and_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -218,7 +218,7 @@ pub async fn test_guard_and_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -288,7 +288,7 @@ pub async fn test_guard_or_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -338,7 +338,7 @@ pub async fn test_guard_chain_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -359,7 +359,7 @@ pub async fn test_guard_chain_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -380,7 +380,7 @@ pub async fn test_guard_chain_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -401,7 +401,7 @@ pub async fn test_guard_chain_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -493,7 +493,7 @@ pub async fn test_guard_race_operator() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("value".to_owned())], extensions: None, @@ -549,7 +549,7 @@ pub async fn test_guard_use_params() { .unwrap_err(), vec![ServerError { message: "Forbidden".to_string(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("get".to_owned())], extensions: None, diff --git a/tests/input_validators.rs b/tests/input_validators.rs index eb0cd1b6..6cbd9743 100644 --- a/tests/input_validators.rs +++ b/tests/input_validators.rs @@ -67,7 +67,7 @@ pub async fn test_input_validator_string_min_length() { .expect_err(&should_fail_msg), vec![ServerError { message: field_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 17 @@ -85,7 +85,7 @@ pub async fn test_input_validator_string_min_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 14 @@ -179,7 +179,7 @@ pub async fn test_input_validator_string_max_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 17 @@ -197,7 +197,7 @@ pub async fn test_input_validator_string_max_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 14 @@ -290,7 +290,7 @@ pub async fn test_input_validator_chars_min_length() { .expect_err(&should_fail_msg), vec![ServerError { message: field_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 17 @@ -308,7 +308,7 @@ pub async fn test_input_validator_chars_min_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 14 @@ -403,7 +403,7 @@ pub async fn test_input_validator_chars_max_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 17 @@ -421,7 +421,7 @@ pub async fn test_input_validator_chars_max_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 14 @@ -542,7 +542,7 @@ pub async fn test_input_validator_string_email() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 17 @@ -561,7 +561,7 @@ pub async fn test_input_validator_string_email() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 14 @@ -692,7 +692,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg.clone(), - error: None, + source: None, locations: vec!(Pos { line: 1, column: 17 @@ -711,7 +711,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg.clone(), - error: None, + source: None, locations: vec!(Pos { line: 1, column: 14 @@ -729,7 +729,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 17 @@ -748,7 +748,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 14 @@ -806,7 +806,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 17 @@ -825,7 +825,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 14 @@ -867,7 +867,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 17 @@ -886,7 +886,7 @@ pub async fn test_input_validator_string_mac() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 14 @@ -947,7 +947,7 @@ pub async fn test_input_validator_int_range() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 17 @@ -965,7 +965,7 @@ pub async fn test_input_validator_int_range() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 14 @@ -1054,7 +1054,7 @@ pub async fn test_input_validator_int_less_than() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 17 @@ -1072,7 +1072,7 @@ pub async fn test_input_validator_int_less_than() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 14 @@ -1163,7 +1163,7 @@ pub async fn test_input_validator_int_greater_than() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 17 @@ -1181,7 +1181,7 @@ pub async fn test_input_validator_int_greater_than() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 14 @@ -1265,7 +1265,7 @@ pub async fn test_input_validator_int_nonzero() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 17 @@ -1283,7 +1283,7 @@ pub async fn test_input_validator_int_nonzero() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 14 @@ -1371,7 +1371,7 @@ pub async fn test_input_validator_int_equal() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 17 @@ -1389,7 +1389,7 @@ pub async fn test_input_validator_int_equal() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 14 @@ -1489,7 +1489,7 @@ pub async fn test_input_validator_list_max_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 17 @@ -1507,7 +1507,7 @@ pub async fn test_input_validator_list_max_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 14 @@ -1607,7 +1607,7 @@ pub async fn test_input_validator_list_min_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 17 @@ -1625,7 +1625,7 @@ pub async fn test_input_validator_list_min_length() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 14 @@ -1733,7 +1733,7 @@ pub async fn test_input_validator_operator_or() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 17 @@ -1751,7 +1751,7 @@ pub async fn test_input_validator_operator_or() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 14 @@ -1852,7 +1852,7 @@ pub async fn test_input_validator_operator_and() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 17 @@ -1870,7 +1870,7 @@ pub async fn test_input_validator_operator_and() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 14 @@ -1974,7 +1974,7 @@ pub async fn test_input_validator_variable() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: field_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 37 @@ -1992,7 +1992,7 @@ pub async fn test_input_validator_variable() { .expect_err(&should_fail_msg[..]), vec![ServerError { message: object_error_msg, - error: None, + source: None, locations: vec!(Pos { line: 1, column: 34 @@ -2079,7 +2079,7 @@ pub async fn test_custom_input_validator_with_extensions() { .expect_err(should_fail_msg), vec![ServerError { message: field_error_msg.into(), - error: None, + source: None, locations: vec!(Pos { line: 1, column: 17 diff --git a/tests/input_value.rs b/tests/input_value.rs index 1387f042..6ea3f871 100644 --- a/tests/input_value.rs +++ b/tests/input_value.rs @@ -18,7 +18,7 @@ pub async fn test_input_value_custom_error() { vec![ServerError { message: "Failed to parse \"Int\": Only integers from -128 to 127 are accepted." .to_owned(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 14, diff --git a/tests/list.rs b/tests/list.rs index de2afe98..90a62a09 100644 --- a/tests/list.rs +++ b/tests/list.rs @@ -167,7 +167,7 @@ pub async fn test_array_type() { vec![ServerError { message: r#"Failed to parse "[Int!]": Expected input type "[Int; 6]", found [Int; 5]."# .to_owned(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 22, diff --git a/tests/result.rs b/tests/result.rs index 2487cc22..c4608ae9 100644 --- a/tests/result.rs +++ b/tests/result.rs @@ -34,14 +34,14 @@ pub async fn test_fieldresult() { errors: vec![ ServerError { message: "TestError".to_string(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("error1".to_owned())], extensions: None, }, ServerError { message: "TestError".to_string(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 19, @@ -62,7 +62,7 @@ pub async fn test_fieldresult() { .unwrap_err(), vec![ServerError { message: "TestError".to_string(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![PathSegment::Field("optError".to_owned())], extensions: None, @@ -77,7 +77,7 @@ pub async fn test_fieldresult() { .unwrap_err(), vec![ServerError { message: "TestError".to_string(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 3 }], path: vec![ PathSegment::Field("vecError".to_owned()), @@ -192,7 +192,7 @@ pub async fn test_error_propagation() { cache_control: Default::default(), errors: vec![ServerError { message: "myerror".to_string(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 20, @@ -220,7 +220,7 @@ pub async fn test_error_propagation() { cache_control: Default::default(), errors: vec![ServerError { message: "myerror".to_string(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 23, @@ -244,7 +244,7 @@ pub async fn test_error_propagation() { cache_control: Default::default(), errors: vec![ServerError { message: "myerror".to_string(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 23, @@ -274,7 +274,7 @@ pub async fn test_error_propagation() { cache_control: Default::default(), errors: vec![ServerError { message: "myerror".to_string(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 23, diff --git a/tests/subscription.rs b/tests/subscription.rs index e948a5a6..750c9405 100644 --- a/tests/subscription.rs +++ b/tests/subscription.rs @@ -345,7 +345,7 @@ pub async fn test_subscription_error() { stream.next().await, Some(Err(vec![ServerError { message: "TestError".to_string(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 25 @@ -391,7 +391,7 @@ pub async fn test_subscription_fieldresult() { cache_control: Default::default(), errors: vec![ServerError { message: "StreamErr".to_string(), - error: None, + source: None, locations: vec![Pos { line: 1, column: 16 From c97b634303bfa0223626a4df745547318453715a Mon Sep 17 00:00:00 2001 From: Sunli Date: Sat, 3 Apr 2021 11:52:54 +0800 Subject: [PATCH 60/65] Update for actix-web v4.0.0-beta.5 --- integrations/actix-web/Cargo.toml | 12 ++++----- integrations/actix-web/src/lib.rs | 21 +++++++-------- integrations/actix-web/src/subscription.rs | 30 ++++++++++++---------- 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/integrations/actix-web/Cargo.toml b/integrations/actix-web/Cargo.toml index 75bd71f3..1b4ca5b6 100644 --- a/integrations/actix-web/Cargo.toml +++ b/integrations/actix-web/Cargo.toml @@ -14,15 +14,15 @@ categories = ["network-programming", "asynchronous"] [dependencies] async-graphql = { path = "../..", version = "=2.11.0" } -actix = "0.10.0" -actix-http = "2.2.0" -actix-web = { version = "3.3.2", default-features = false } -actix-web-actors = "3.0.0" -async-channel = "1.6.1" +actix = "0.11.1" +actix-http = "3.0.0-beta.5" +actix-web = { version = "4.0.0-beta.5", default-features = false } +actix-web-actors = "4.0.0-beta.4" futures-util = { version = "0.3.13", default-features = false } serde_json = "1.0.64" serde_urlencoded = "0.7.0" +futures-channel = "0.3.13" [dev-dependencies] -actix-rt = "1.1.0" +actix-rt = "2.0.0" async-mutex = "1.4.0" diff --git a/integrations/actix-web/src/lib.rs b/integrations/actix-web/src/lib.rs index fc73c967..878d26a0 100644 --- a/integrations/actix-web/src/lib.rs +++ b/integrations/actix-web/src/lib.rs @@ -11,15 +11,15 @@ use std::future::Future; use std::io::{self, ErrorKind}; use std::pin::Pin; -use actix_web::client::PayloadError; use actix_web::dev::{Payload, PayloadStream}; +use actix_web::error::PayloadError; use actix_web::http::{Method, StatusCode}; use actix_web::{http, Error, FromRequest, HttpRequest, HttpResponse, Responder, Result}; -use futures_util::future::{self, FutureExt, Ready}; -use futures_util::{StreamExt, TryStreamExt}; - use async_graphql::http::MultipartOptions; use async_graphql::ParseRequestError; +use futures_channel::mpsc; +use futures_util::future::{self, FutureExt}; +use futures_util::{SinkExt, StreamExt, TryStreamExt}; /// Extractor for GraphQL request. /// @@ -84,7 +84,7 @@ impl FromRequest for BatchRequest { .and_then(|value| value.to_str().ok()) .map(|value| value.to_string()); - let (tx, rx) = async_channel::bounded(16); + let (mut tx, rx) = mpsc::channel(16); // Payload is !Send so we create indirection with a channel let mut payload = payload.take(); @@ -160,20 +160,17 @@ impl From for Response { } impl Responder for Response { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _req: &HttpRequest) -> Self::Future { + fn respond_to(self, _req: &HttpRequest) -> HttpResponse { let mut res = HttpResponse::build(StatusCode::OK); res.content_type("application/json"); if self.0.is_ok() { if let Some(cache_control) = self.0.cache_control().value() { - res.header("cache-control", cache_control); + res.append_header(("cache-control", cache_control)); } } for (name, value) in self.0.http_headers() { - res.header(name, value); + res.append_header((name, value)); } - futures_util::future::ok(res.body(serde_json::to_string(&self.0).unwrap())) + res.body(serde_json::to_string(&self.0).unwrap()) } } diff --git a/integrations/actix-web/src/subscription.rs b/integrations/actix-web/src/subscription.rs index ad2713e6..e4dd2b9a 100644 --- a/integrations/actix-web/src/subscription.rs +++ b/integrations/actix-web/src/subscription.rs @@ -2,19 +2,22 @@ use std::future::Future; use std::str::FromStr; use std::time::{Duration, Instant}; +use actix::ActorFutureExt; use actix::{ - Actor, ActorContext, ActorFuture, ActorStream, AsyncContext, ContextFutureSpawner, - StreamHandler, WrapFuture, WrapStream, + Actor, ActorContext, ActorStreamExt, AsyncContext, ContextFutureSpawner, StreamHandler, + WrapFuture, WrapStream, }; use actix_http::error::PayloadError; use actix_http::{ws, Error}; -use actix_web::web::Bytes; +use actix_web::web::{BufMut, Bytes, BytesMut}; use actix_web::{HttpRequest, HttpResponse}; use actix_web_actors::ws::{CloseReason, Message, ProtocolError, WebsocketContext}; use async_graphql::http::{WebSocket, WebSocketProtocols, WsMessage, ALL_WEBSOCKET_PROTOCOLS}; use async_graphql::{Data, ObjectType, Result, Schema, SubscriptionType}; +use futures_channel::mpsc; use futures_util::future::Ready; use futures_util::stream::Stream; +use futures_util::SinkExt; const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); const CLIENT_TIMEOUT: Duration = Duration::from_secs(10); @@ -24,9 +27,9 @@ pub struct WSSubscription { schema: Schema, protocol: WebSocketProtocols, last_heartbeat: Instant, - messages: Option>>, + messages: Option>, initializer: Option, - continuation: Vec, + continuation: BytesMut, } impl @@ -94,7 +97,7 @@ where last_heartbeat: Instant::now(), messages: None, initializer: Some(initializer), - continuation: Vec::new(), + continuation: BytesMut::new(), }, &ALL_WEBSOCKET_PROTOCOLS, request, @@ -125,7 +128,7 @@ where fn started(&mut self, ctx: &mut Self::Context) { self.send_heartbeats(ctx); - let (tx, rx) = async_channel::unbounded(); + let (tx, rx) = mpsc::unbounded(); WebSocket::with_data( self.schema.clone(), @@ -178,20 +181,21 @@ where } Message::Continuation(item) => match item { ws::Item::FirstText(bytes) | ws::Item::FirstBinary(bytes) => { - self.continuation = bytes.to_vec(); + self.continuation.clear(); + self.continuation.put(bytes); None } ws::Item::Continue(bytes) => { - self.continuation.extend_from_slice(&bytes); + self.continuation.put(bytes); None } ws::Item::Last(bytes) => { - self.continuation.extend_from_slice(&bytes); - Some(std::mem::take(&mut self.continuation)) + self.continuation.put(bytes); + Some(std::mem::take(&mut self.continuation).freeze()) } }, Message::Text(s) => Some(s.into_bytes()), - Message::Binary(bytes) => Some(bytes.to_vec()), + Message::Binary(bytes) => Some(bytes), Message::Close(_) => { ctx.stop(); None @@ -200,7 +204,7 @@ where }; if let Some(message) = message { - let sender = self.messages.as_ref().unwrap().clone(); + let mut sender = self.messages.as_ref().unwrap().clone(); async move { sender.send(message).await } .into_actor(self) From 80406ddab29f5771c462ac4aef85eca3c0f074a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Libor=20Va=C5=A1=C3=AD=C4=8Dek?= Date: Sat, 3 Apr 2021 11:47:22 +0200 Subject: [PATCH 61/65] Update tests for actix-web v4.0.0-beta.5 --- integrations/actix-web/Cargo.toml | 2 +- integrations/actix-web/src/subscription.rs | 5 +- integrations/actix-web/tests/graphql.rs | 121 +++++++++++++-------- 3 files changed, 76 insertions(+), 52 deletions(-) diff --git a/integrations/actix-web/Cargo.toml b/integrations/actix-web/Cargo.toml index 1b4ca5b6..4b605128 100644 --- a/integrations/actix-web/Cargo.toml +++ b/integrations/actix-web/Cargo.toml @@ -24,5 +24,5 @@ serde_urlencoded = "0.7.0" futures-channel = "0.3.13" [dev-dependencies] -actix-rt = "2.0.0" +actix-rt = "2.2.0" async-mutex = "1.4.0" diff --git a/integrations/actix-web/src/subscription.rs b/integrations/actix-web/src/subscription.rs index e4dd2b9a..152a49ec 100644 --- a/integrations/actix-web/src/subscription.rs +++ b/integrations/actix-web/src/subscription.rs @@ -2,10 +2,9 @@ use std::future::Future; use std::str::FromStr; use std::time::{Duration, Instant}; -use actix::ActorFutureExt; use actix::{ - Actor, ActorContext, ActorStreamExt, AsyncContext, ContextFutureSpawner, StreamHandler, - WrapFuture, WrapStream, + Actor, ActorContext, ActorFutureExt, ActorStreamExt, AsyncContext, ContextFutureSpawner, + StreamHandler, WrapFuture, WrapStream, }; use actix_http::error::PayloadError; use actix_http::{ws, Error}; diff --git a/integrations/actix-web/tests/graphql.rs b/integrations/actix-web/tests/graphql.rs index dc75676a..f53111ea 100644 --- a/integrations/actix-web/tests/graphql.rs +++ b/integrations/actix-web/tests/graphql.rs @@ -1,4 +1,6 @@ mod test_utils; +use actix_http::Request; +use actix_web::dev::{MessageBody, Service, ServiceResponse}; use actix_web::{guard, test, web, App}; use async_graphql::*; use serde_json::json; @@ -6,43 +8,50 @@ use test_utils::*; #[actix_rt::test] async fn test_playground() { - let srv = test::start(|| { + let app = test::init_service( App::new().service( web::resource("/") .guard(guard::Get()) .to(test_utils::gql_playgound), - ) - }); - let mut response = srv.get("/").send().await.unwrap(); - assert!(response.status().is_success()); - let body = response.body().await.unwrap(); + ), + ) + .await; + + let req = test::TestRequest::with_uri("/").to_request(); + + let resp = app.call(req).await.unwrap(); + assert!(resp.status().is_success()); + let body = test::read_body(resp).await; assert!(std::str::from_utf8(&body).unwrap().contains("graphql")); } #[actix_rt::test] async fn test_add() { - let srv = test::start(|| { + let app = test::init_service( App::new() .data(Schema::new(AddQueryRoot, EmptyMutation, EmptySubscription)) .service( web::resource("/") .guard(guard::Post()) .to(gql_handle_schema::), - ) - }); - let mut response = srv - .post("/") - .send_body(r#"{"query":"{ add(a: 10, b: 20) }"}"#) - .await - .unwrap(); - assert!(response.status().is_success()); - let body = response.body().await.unwrap(); + ), + ) + .await; + + let resp = test::TestRequest::post() + .uri("/") + .set_payload(r#"{"query":"{ add(a: 10, b: 20) }"}"#) + .send_request(&app) + .await; + + assert!(resp.status().is_success()); + let body = test::read_body(resp).await; assert_eq!(body, json!({"data": {"add": 30}}).to_string()); } #[actix_rt::test] async fn test_hello() { - let srv = test::start(|| { + let app = test::init_service( App::new() .data(Schema::new( HelloQueryRoot, @@ -53,16 +62,18 @@ async fn test_hello() { web::resource("/") .guard(guard::Post()) .to(gql_handle_schema::), - ) - }); + ), + ) + .await; - let mut response = srv - .post("/") - .send_body(r#"{"query":"{ hello }"}"#) - .await - .unwrap(); - assert!(response.status().is_success()); - let body = response.body().await.unwrap(); + let resp = test::TestRequest::post() + .uri("/") + .set_payload(r#"{"query":"{ hello }"}"#) + .send_request(&app) + .await; + + assert!(resp.status().is_success()); + let body = test::read_body(resp).await; assert_eq!( body, json!({"data": {"hello": "Hello, world!"}}).to_string() @@ -71,7 +82,7 @@ async fn test_hello() { #[actix_rt::test] async fn test_hello_header() { - let srv = test::start(|| { + let app = test::init_service( App::new() .data(Schema::new( HelloQueryRoot, @@ -82,23 +93,25 @@ async fn test_hello_header() { web::resource("/") .guard(guard::Post()) .to(gql_handle_schema_with_header::), - ) - }); + ), + ) + .await; - let mut response = srv - .post("/") - .header("Name", "Foo") - .send_body(r#"{"query":"{ hello }"}"#) - .await - .unwrap(); - assert!(response.status().is_success()); - let body = response.body().await.unwrap(); + let resp = test::TestRequest::post() + .uri("/") + .append_header(("Name", "Foo")) + .set_payload(r#"{"query":"{ hello }"}"#) + .send_request(&app) + .await; + + assert!(resp.status().is_success()); + let body = test::read_body(resp).await; assert_eq!(body, json!({"data": {"hello": "Hello, Foo!"}}).to_string()); } #[actix_rt::test] async fn test_count() { - let srv = test::start(|| { + let app = test::init_service( App::new() .data( Schema::build(CountQueryRoot, CountMutation, EmptySubscription) @@ -109,28 +122,40 @@ async fn test_count() { web::resource("/") .guard(guard::Post()) .to(gql_handle_schema::), - ) - }); - count_action_helper(0, r#"{"query":"{ count }"}"#, &srv).await; - count_action_helper(10, r#"{"query":"mutation{ addCount(count: 10) }"}"#, &srv).await; + ), + ) + .await; + + count_action_helper(0, r#"{"query":"{ count }"}"#, &app).await; + count_action_helper(10, r#"{"query":"mutation{ addCount(count: 10) }"}"#, &app).await; count_action_helper( 8, r#"{"query":"mutation{ subtractCount(count: 2) }"}"#, - &srv, + &app, ) .await; count_action_helper( 6, r#"{"query":"mutation{ subtractCount(count: 2) }"}"#, - &srv, + &app, ) .await; } -async fn count_action_helper(expected: i32, body: &'static str, srv: &test::TestServer) { - let mut response = srv.post("/").send_body(body).await.unwrap(); - assert!(response.status().is_success()); - let body = response.body().await.unwrap(); +async fn count_action_helper(expected: i32, payload: &'static str, app: &S) +where + S: Service, Error = E>, + B: MessageBody + Unpin, + E: std::fmt::Debug, +{ + let resp = test::TestRequest::post() + .uri("/") + .set_payload(payload) + .send_request(app) + .await; + + assert!(resp.status().is_success()); + let body = test::read_body(resp).await; assert!(std::str::from_utf8(&body) .unwrap() .contains(&expected.to_string())); From 737cce27e0e7d6e494de328ec5a6fd5589df7a26 Mon Sep 17 00:00:00 2001 From: Sunli Date: Sat, 5 Jun 2021 10:51:11 +0800 Subject: [PATCH 62/65] Update for actix-web-4.0.0-beta.6 --- integrations/actix-web/Cargo.toml | 6 +++--- integrations/actix-web/src/lib.rs | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/integrations/actix-web/Cargo.toml b/integrations/actix-web/Cargo.toml index 4b605128..1c4eaf64 100644 --- a/integrations/actix-web/Cargo.toml +++ b/integrations/actix-web/Cargo.toml @@ -15,9 +15,9 @@ categories = ["network-programming", "asynchronous"] async-graphql = { path = "../..", version = "=2.11.0" } actix = "0.11.1" -actix-http = "3.0.0-beta.5" -actix-web = { version = "4.0.0-beta.5", default-features = false } -actix-web-actors = "4.0.0-beta.4" +actix-http = "3.0.0-beta.6" +actix-web = { version = "4.0.0-beta.6", default-features = false } +actix-web-actors = { git = "https://github.com/actix/actix-web.git", rev = "0bb035c" } futures-util = { version = "0.3.13", default-features = false } serde_json = "1.0.64" serde_urlencoded = "0.7.0" diff --git a/integrations/actix-web/src/lib.rs b/integrations/actix-web/src/lib.rs index 878d26a0..2ac15564 100644 --- a/integrations/actix-web/src/lib.rs +++ b/integrations/actix-web/src/lib.rs @@ -118,6 +118,7 @@ impl FromRequest for BatchRequest { } PayloadError::Http2Payload(e) if e.is_io() => e.into_io().unwrap(), PayloadError::Http2Payload(e) => io::Error::new(ErrorKind::Other, e), + _ => io::Error::new(ErrorKind::Other, "unknown error"), }) .into_async_read(), config, From cac24dc5f3055f234876d659e0fb2e46de8c9d78 Mon Sep 17 00:00:00 2001 From: Sunli Date: Tue, 29 Jun 2021 09:44:29 +0800 Subject: [PATCH 63/65] Bump actix-web to `3.0.0-beta.8` --- integrations/actix-web/Cargo.toml | 8 ++++---- integrations/actix-web/src/subscription.rs | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/integrations/actix-web/Cargo.toml b/integrations/actix-web/Cargo.toml index 1c4eaf64..4da6e776 100644 --- a/integrations/actix-web/Cargo.toml +++ b/integrations/actix-web/Cargo.toml @@ -14,10 +14,10 @@ categories = ["network-programming", "asynchronous"] [dependencies] async-graphql = { path = "../..", version = "=2.11.0" } -actix = "0.11.1" -actix-http = "3.0.0-beta.6" -actix-web = { version = "4.0.0-beta.6", default-features = false } -actix-web-actors = { git = "https://github.com/actix/actix-web.git", rev = "0bb035c" } +actix = "0.12" +actix-http = "3.0.0-beta.8" +actix-web = { version = "4.0.0-beta.8", default-features = false } +actix-web-actors = "4.0.0-beta.6" futures-util = { version = "0.3.13", default-features = false } serde_json = "1.0.64" serde_urlencoded = "0.7.0" diff --git a/integrations/actix-web/src/subscription.rs b/integrations/actix-web/src/subscription.rs index 152a49ec..c366d843 100644 --- a/integrations/actix-web/src/subscription.rs +++ b/integrations/actix-web/src/subscription.rs @@ -6,8 +6,8 @@ use actix::{ Actor, ActorContext, ActorFutureExt, ActorStreamExt, AsyncContext, ContextFutureSpawner, StreamHandler, WrapFuture, WrapStream, }; -use actix_http::error::PayloadError; -use actix_http::{ws, Error}; +use actix_http::ws::Item; +use actix_web::error::{Error, PayloadError}; use actix_web::web::{BufMut, Bytes, BytesMut}; use actix_web::{HttpRequest, HttpResponse}; use actix_web_actors::ws::{CloseReason, Message, ProtocolError, WebsocketContext}; @@ -179,16 +179,16 @@ where None } Message::Continuation(item) => match item { - ws::Item::FirstText(bytes) | ws::Item::FirstBinary(bytes) => { + Item::FirstText(bytes) | ws::Item::FirstBinary(bytes) => { self.continuation.clear(); self.continuation.put(bytes); None } - ws::Item::Continue(bytes) => { + Item::Continue(bytes) => { self.continuation.put(bytes); None } - ws::Item::Last(bytes) => { + Item::Last(bytes) => { self.continuation.put(bytes); Some(std::mem::take(&mut self.continuation).freeze()) } From e613084bd191e2d91356af8ced3f9866121af106 Mon Sep 17 00:00:00 2001 From: Ricky Lam Date: Wed, 30 Jun 2021 10:38:18 +0800 Subject: [PATCH 64/65] Fix compile error --- integrations/actix-web/src/subscription.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/actix-web/src/subscription.rs b/integrations/actix-web/src/subscription.rs index c366d843..ac388bf7 100644 --- a/integrations/actix-web/src/subscription.rs +++ b/integrations/actix-web/src/subscription.rs @@ -179,7 +179,7 @@ where None } Message::Continuation(item) => match item { - Item::FirstText(bytes) | ws::Item::FirstBinary(bytes) => { + Item::FirstText(bytes) | Item::FirstBinary(bytes) => { self.continuation.clear(); self.continuation.put(bytes); None From 99714336494e470692849f32cbfba3fc81c39014 Mon Sep 17 00:00:00 2001 From: Sunli Date: Tue, 19 Oct 2021 12:51:27 +0800 Subject: [PATCH 65/65] Bump actix-web from `4.0.0-beta.8` to `4.0.0-beta.9` --- integrations/actix-web/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integrations/actix-web/Cargo.toml b/integrations/actix-web/Cargo.toml index 4da6e776..256a5aa5 100644 --- a/integrations/actix-web/Cargo.toml +++ b/integrations/actix-web/Cargo.toml @@ -15,9 +15,9 @@ categories = ["network-programming", "asynchronous"] async-graphql = { path = "../..", version = "=2.11.0" } actix = "0.12" -actix-http = "3.0.0-beta.8" -actix-web = { version = "4.0.0-beta.8", default-features = false } -actix-web-actors = "4.0.0-beta.6" +actix-http = "3.0.0-beta.10" +actix-web = { version = "4.0.0-beta.9", default-features = false } +actix-web-actors = "4.0.0-beta.7" futures-util = { version = "0.3.13", default-features = false } serde_json = "1.0.64" serde_urlencoded = "0.7.0"