diff --git a/Cargo.toml b/Cargo.toml index 207c4433..087a8463 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql" -version = "1.5.0" +version = "1.5.1" authors = ["sunli "] edition = "2018" description = "The GraphQL server library implemented by rust" @@ -18,21 +18,20 @@ default = ["bson", "chrono", "uuid", "url", "validators"] validators = ["regex", "once_cell"] [dependencies] -async-graphql-derive = { path = "async-graphql-derive", version = "1.5.0" } +async-graphql-derive = { path = "async-graphql-derive", version = "1.5.1" } graphql-parser = "0.2.3" anyhow = "1.0.26" thiserror = "1.0.11" async-trait = "0.1.24" serde = "1.0.104" serde_derive = "1.0.104" -serde_json = { version = "1.0.48", features = ["raw_value"] } +serde_json = "1.0.48" fnv = "1.0.6" bytes = "0.5.4" Inflector = "0.11.4" base64 = "0.12.0" byteorder = "1.3.4" -itoa = "0.4.5" -ryu = "1.0.3" +futures = "0.3.0" once_cell = { version = "1.3.1", optional = true } regex = { version = "1.3.5", optional = true } bson = { version = "0.14.1", optional = true } diff --git a/async-graphql-actix-web/Cargo.toml b/async-graphql-actix-web/Cargo.toml index 1833b054..ec37824b 100644 --- a/async-graphql-actix-web/Cargo.toml +++ b/async-graphql-actix-web/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-actix-web" -version = "0.6.0" +version = "0.6.1" authors = ["sunli "] edition = "2018" description = "async-graphql for actix-web" @@ -13,7 +13,7 @@ keywords = ["futures", "async", "graphql"] categories = ["network-programming", "asynchronous"] [dependencies] -async-graphql = { path = "..", version = "1.5.0" } +async-graphql = { path = "..", version = "1.5.1" } actix-web = "2.0.0" actix-multipart = "0.2.0" actix-web-actors = "2.0.0" diff --git a/async-graphql-actix-web/src/lib.rs b/async-graphql-actix-web/src/lib.rs index 800aa09b..0b55acbf 100644 --- a/async-graphql-actix-web/src/lib.rs +++ b/async-graphql-actix-web/src/lib.rs @@ -250,10 +250,9 @@ where let mut gql_req = web::Json::::from_request(&req, &mut payload.0) .await? .into_inner(); - let prepared = match gql_req.prepare(&schema) { - Ok(prepared) => prepared, - Err(err) => return Ok(web::Json(GQLResponse(Err(err))).respond_to(&req).await?), - }; + let prepared = gql_req + .prepare(&schema) + .map_err(actix_web::error::ErrorBadRequest)?; let mut cache_control = prepared.cache_control().value(); let gql_resp = prepared.execute().await; if gql_resp.is_err() { diff --git a/async-graphql-derive/Cargo.toml b/async-graphql-derive/Cargo.toml index ae30abf0..97c18c3f 100644 --- a/async-graphql-derive/Cargo.toml +++ b/async-graphql-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-graphql-derive" -version = "1.5.0" +version = "1.5.1" authors = ["sunli "] edition = "2018" description = "Macros for async-graphql" diff --git a/async-graphql-derive/src/enum.rs b/async-graphql-derive/src/enum.rs index a9277903..1afd2292 100644 --- a/async-graphql-derive/src/enum.rs +++ b/async-graphql-derive/src/enum.rs @@ -112,8 +112,8 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result, w: &mut #crate_name::JsonWriter) -> #crate_name::Result<()> { - #crate_name::EnumType::resolve_enum(value, w) + async fn resolve(value: &Self, _: &#crate_name::ContextSelectionSet<'_>) -> #crate_name::Result<#crate_name::serde_json::Value> { + #crate_name::EnumType::resolve_enum(value) } } }; diff --git a/async-graphql-derive/src/interface.rs b/async-graphql-derive/src/interface.rs index ecaee8ad..e27e76dd 100644 --- a/async-graphql-derive/src/interface.rs +++ b/async-graphql-derive/src/interface.rs @@ -37,7 +37,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result .unwrap_or_else(|| quote! {None}); let mut registry_types = Vec::new(); let mut possible_types = Vec::new(); - let mut inline_fragment_resolvers = Vec::new(); + let mut collect_inline_fields = Vec::new(); for field in &fields.unnamed { if let Type::Path(p) = &field.ty { @@ -58,13 +58,13 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result possible_types.push(quote! { possible_types.insert(<#p as #crate_name::Type>::type_name().to_string()); }); - inline_fragment_resolvers.push(quote! { - if name == <#p as #crate_name::Type>::type_name() { - if let #ident::#enum_name(obj) = self { - return #crate_name::do_resolve(ctx, obj, w).await; - } - return Ok(()); - } + collect_inline_fields.push(quote! { + // if name == <#p as #crate_name::Type>::type_name() { + // if let #ident::#enum_name(obj) = self { + // return &obj; + // } + // unreachable!() + // } }); } else { return Err(Error::new_spanned(field, "Invalid type")); @@ -205,7 +205,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result if field.name.as_str() == #name { #(#get_params)* let ctx_obj = ctx.with_item(&field.selection_set); - return #crate_name::OutputValueType::resolve(&#resolve_obj, &ctx_obj, w).await. + return #crate_name::OutputValueType::resolve(&#resolve_obj, &ctx_obj).await. map_err(|err| err.with_position(field.position).into()); } }); @@ -250,7 +250,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result #[#crate_name::async_trait::async_trait] impl #generics #crate_name::ObjectType for #ident #generics { - async fn resolve_field(&self, ctx: &#crate_name::Context<'_>, field: &#crate_name::graphql_parser::query::Field, w: &mut #crate_name::JsonWriter) -> #crate_name::Result<()> { + async fn resolve_field(&self, ctx: &#crate_name::Context<'_>, field: &#crate_name::graphql_parser::query::Field) -> #crate_name::Result<#crate_name::serde_json::Value> { use #crate_name::ErrorWithPosition; #(#resolvers)* @@ -262,8 +262,13 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result .with_position(field.position)); } - async fn resolve_inline_fragment(&self, name: &str, ctx: &#crate_name::ContextSelectionSet<'_>, w: &mut #crate_name::JsonWriter) -> #crate_name::Result<()> { - #(#inline_fragment_resolvers)* + fn collect_inline_fields<'a>( + &'a self, + name: &str, + ctx: #crate_name::ContextSelectionSet<'a>, + futures: &mut Vec<#crate_name::BoxFieldFuture<'a>>, + ) -> #crate_name::Result<()> { + #(#collect_inline_fields)* #crate_name::anyhow::bail!(#crate_name::QueryError::UnrecognizedInlineFragment { object: #gql_typename.to_string(), name: name.to_string(), @@ -273,11 +278,8 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result #[#crate_name::async_trait::async_trait] impl #generics #crate_name::OutputValueType for #ident #generics { - async fn resolve(value: &Self, ctx: &#crate_name::ContextSelectionSet<'_>, w: &mut #crate_name::JsonWriter) -> #crate_name::Result<()> { - w.begin_object(); - #crate_name::do_resolve(ctx, value, w).await?; - w.end_object(); - Ok(()) + async fn resolve(value: &Self, ctx: &#crate_name::ContextSelectionSet<'_>) -> #crate_name::Result<#crate_name::serde_json::Value> { + #crate_name::do_resolve(ctx, value).await } } }; diff --git a/async-graphql-derive/src/object.rs b/async-graphql-derive/src/object.rs index 22474b70..b12a042c 100644 --- a/async-graphql-derive/src/object.rs +++ b/async-graphql-derive/src/object.rs @@ -211,7 +211,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result< if field.name.as_str() == #field_name { #(#get_params)* let ctx_obj = ctx.with_item(&field.selection_set); - return #crate_name::OutputValueType::resolve(&#resolve_obj, &ctx_obj, w).await. + return #crate_name::OutputValueType::resolve(&#resolve_obj, &ctx_obj).await. map_err(|err| err.with_position(field.position).into()); } }); @@ -256,7 +256,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result< #[#crate_name::async_trait::async_trait] impl#generics #crate_name::ObjectType for #self_ty { - async fn resolve_field(&self, ctx: &#crate_name::Context<'_>, field: &#crate_name::graphql_parser::query::Field, w: &mut #crate_name::JsonWriter) -> #crate_name::Result<()> { + async fn resolve_field(&self, ctx: &#crate_name::Context<'_>, field: &#crate_name::graphql_parser::query::Field) -> #crate_name::Result<#crate_name::serde_json::Value> { use #crate_name::ErrorWithPosition; #(#resolvers)* @@ -267,22 +267,12 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result< } .with_position(field.position)); } - - async fn resolve_inline_fragment(&self, name: &str, ctx: &#crate_name::ContextSelectionSet<'_>, _w: &mut #crate_name::JsonWriter) -> #crate_name::Result<()> { - #crate_name::anyhow::bail!(#crate_name::QueryError::UnrecognizedInlineFragment { - object: #gql_typename.to_string(), - name: name.to_string(), - }); - } } #[#crate_name::async_trait::async_trait] impl #generics #crate_name::OutputValueType for #self_ty { - async fn resolve(value: &Self, ctx: &#crate_name::ContextSelectionSet<'_>, w: &mut #crate_name::JsonWriter) -> #crate_name::Result<()> { - w.begin_object(); - #crate_name::do_resolve(ctx, value, w).await?; - w.end_object(); - Ok(()) + async fn resolve(value: &Self, ctx: &#crate_name::ContextSelectionSet<'_>) -> #crate_name::Result<#crate_name::serde_json::Value> { + #crate_name::do_resolve(ctx, value).await } } }; diff --git a/async-graphql-derive/src/subscription.rs b/async-graphql-derive/src/subscription.rs index 426afeee..01d1dd94 100644 --- a/async-graphql-derive/src/subscription.rs +++ b/async-graphql-derive/src/subscription.rs @@ -211,22 +211,12 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result< if let Some(msg) = msg.downcast_ref::<#ty>() { #(#get_params)* if self.#ident(msg, #(#use_params)*) { - let mut w = #crate_name::JsonWriter::default(); let ctx_selection_set = ctx_field.with_item(&field.selection_set); - - w.begin_object(); - - w.begin_object_key(); - w.string(ctx_field.result_name()); - w.end_object_key(); - - w.begin_object_value(); - #crate_name::OutputValueType::resolve(msg, &ctx_selection_set, &mut w).await?; - w.end_object_value(); - - w.end_object(); - - return Ok(Some(w.into_string())); + let value = + #crate_name::OutputValueType::resolve(msg, &ctx_selection_set).await?; + let mut res = #crate_name::serde_json::Map::new(); + res.insert(ctx_field.result_name(), value); + return Ok(Some(res.into())); } } }); @@ -275,7 +265,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result< ctx: &#crate_name::ContextBase<'_, ()>, types: &std::collections::HashMap, msg: &(dyn std::any::Any + Send + Sync), - ) -> #crate_name::Result> { + ) -> #crate_name::Result> { let tid = msg.type_id(); if let Some(field) = types.get(&tid) { let ctx_field = ctx.with_item(field); diff --git a/async-graphql-derive/src/union.rs b/async-graphql-derive/src/union.rs index c7299513..c5cc2deb 100644 --- a/async-graphql-derive/src/union.rs +++ b/async-graphql-derive/src/union.rs @@ -34,7 +34,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result .unwrap_or_else(|| quote! {None}); let mut registry_types = Vec::new(); let mut possible_types = Vec::new(); - let mut inline_fragment_resolvers = Vec::new(); + let mut collect_inline_fields = Vec::new(); for field in &fields.unnamed { if let Type::Path(p) = &field.ty { @@ -54,13 +54,13 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result possible_types.push(quote! { possible_types.insert(<#p as #crate_name::Type>::type_name().to_string()); }); - inline_fragment_resolvers.push(quote! { - if name == <#p as #crate_name::Type>::type_name() { - if let #ident::#enum_name(obj) = self { - #crate_name::do_resolve(ctx, obj, result).await?; - } - return Ok(()); - } + collect_inline_fields.push(quote! { + // if name == <#p as #crate_name::Type>::type_name() { + // if let #ident::#enum_name(obj) = self { + // return &obj; + // } + // unreachable!() + // } }); } else { return Err(Error::new_spanned(field, "Invalid type")); @@ -106,9 +106,14 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result .with_position(field.position)); } - async fn resolve_inline_fragment(&self, name: &str, ctx: &#crate_name::ContextSelectionSet<'_>, result: &mut #crate_name::serde_json::Map) -> #crate_name::Result<()> { - #(#inline_fragment_resolvers)* - anyhow::bail!(#crate_name::QueryError::UnrecognizedInlineFragment { + fn collect_inline_fields<'a>( + &'a self, + name: &str, + ctx: #crate_name::ContextSelectionSet<'a>, + futures: &mut Vec<#crate_name::BoxFieldFuture<'a>>, + ) -> #crate_name::Result<()> { + #(#collect_inline_fields)* + #crate_name::anyhow::bail!(#crate_name::QueryError::UnrecognizedInlineFragment { object: #gql_typename.to_string(), name: name.to_string(), }); @@ -118,10 +123,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result #[#crate_name::async_trait::async_trait] impl #generics #crate_name::OutputValueType for #ident #generics { async fn resolve(value: &Self, ctx: &#crate_name::ContextSelectionSet<'_>) -> #crate_name::Result<#crate_name::serde_json::Value> { - w.begin_object(); - #crate_name::do_resolve(ctx, value).await?; - w.end_object(); - Ok(()) + #crate_name::do_resolve(ctx, value).await } } }; diff --git a/async-graphql-derive/src/utils.rs b/async-graphql-derive/src/utils.rs index 057a5957..3280d8d3 100644 --- a/async-graphql-derive/src/utils.rs +++ b/async-graphql-derive/src/utils.rs @@ -161,7 +161,7 @@ pub fn parse_validator(crate_name: &TokenStream, args: &MetaList) -> Result, w: &mut JsonWriter) - -> Result<()>; + async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result; } +#[allow(missing_docs)] +pub type BoxFieldFuture<'a> = + Pin> + 'a + Send>>; + /// Represents a GraphQL object #[async_trait::async_trait] pub trait ObjectType: OutputValueType { @@ -64,20 +69,20 @@ pub trait ObjectType: OutputValueType { } /// Resolves a field value and outputs it as a json value `serde_json::Value`. - async fn resolve_field( - &self, - ctx: &Context<'_>, - field: &Field, - w: &mut JsonWriter, - ) -> Result<()>; + async fn resolve_field(&self, ctx: &Context<'_>, field: &Field) -> Result; - /// Resolve an inline fragment with the `name`. - async fn resolve_inline_fragment( - &self, + /// Collect the fields with the `name` inline object + fn collect_inline_fields<'a>( + &'a self, name: &str, - ctx: &ContextSelectionSet<'_>, - w: &mut JsonWriter, - ) -> Result<()>; + _ctx: ContextSelectionSet<'a>, + _futures: &mut Vec>, + ) -> Result<()> { + anyhow::bail!(QueryError::UnrecognizedInlineFragment { + object: Self::type_name().to_string(), + name: name.to_string(), + }); + } } /// Represents a GraphQL input object @@ -107,9 +112,8 @@ pub trait InputObjectType: InputValueType {} /// } /// } /// -/// fn to_json(&self, w: &mut JsonWriter) -> Result<()> { -/// w.int(self.0 as i64); -/// Ok(()) +/// fn to_json(&self) -> Result { +/// Ok(self.0.into()) /// } /// } /// @@ -135,7 +139,7 @@ pub trait Scalar: Sized + Send { } /// Convert the scalar value to json value. - fn to_json(&self, w: &mut JsonWriter) -> Result<()>; + fn to_json(&self) -> Result; } #[macro_export] @@ -168,9 +172,8 @@ macro_rules! impl_scalar_internal { async fn resolve( value: &Self, _: &crate::ContextSelectionSet<'_>, - w: &mut crate::JsonWriter, - ) -> crate::Result<()> { - value.to_json(w) + ) -> crate::Result { + value.to_json() } } }; @@ -206,9 +209,8 @@ macro_rules! impl_scalar { async fn resolve( value: &Self, _: &async_graphql::ContextSelectionSet<'_>, - w: &mut async_graphql::JsonWriter, - ) -> async_graphql::Result<()> { - value.to_json(w) + ) -> async_graphql::Result { + value.to_json() } } }; @@ -227,11 +229,7 @@ impl Type for &T { #[async_trait::async_trait] impl OutputValueType for &T { #[allow(clippy::trivially_copy_pass_by_ref)] - async fn resolve( - value: &Self, - ctx: &ContextSelectionSet<'_>, - w: &mut JsonWriter, - ) -> Result<()> { - T::resolve(*value, ctx, w).await + async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result { + T::resolve(*value, ctx).await } } diff --git a/src/context.rs b/src/context.rs index 9290c053..3b7e8c0c 100644 --- a/src/context.rs +++ b/src/context.rs @@ -144,6 +144,7 @@ pub type ContextSelectionSet<'a> = ContextBase<'a, &'a SelectionSet>; pub type Context<'a> = ContextBase<'a, &'a Field>; /// Query context +#[derive(Clone)] pub struct ContextBase<'a, T> { pub(crate) item: T, pub(crate) variables: &'a Variables, @@ -333,10 +334,7 @@ impl<'a> ContextBase<'a, &'a Field> { } #[doc(hidden)] - pub fn result_name(&self) -> &str { - self.item - .alias - .as_deref() - .unwrap_or_else(|| self.name.as_str()) + pub fn result_name(&self) -> String { + self.item.alias.clone().unwrap_or_else(|| self.name.clone()) } } diff --git a/src/http/mod.rs b/src/http/mod.rs index 2eabd46a..aeeb3d30 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -74,7 +74,7 @@ impl GQLRequest { } /// Serializable query result type -pub struct GQLResponse(pub Result); +pub struct GQLResponse(pub Result); impl Serialize for GQLResponse { fn serialize(&self, serializer: S) -> std::result::Result { @@ -82,13 +82,10 @@ impl Serialize for GQLResponse { Ok(res) => { let mut map = serializer.serialize_map(None)?; map.serialize_key("data")?; - map.serialize_value( - &serde_json::value::RawValue::from_string(res.clone()).unwrap(), - )?; + map.serialize_value(&res)?; map.end() } Err(err) => { - println!("err: {}", err); let mut map = serializer.serialize_map(None)?; map.serialize_key("errors")?; map.serialize_value(&GQLError(err))?; @@ -242,7 +239,7 @@ mod tests { #[test] fn test_response_data() { - let resp = GQLResponse(Ok(serde_json::to_string(&json!({"ok": true})).unwrap())); + let resp = GQLResponse(Ok(json!({"ok": true}))); assert_eq!( serde_json::to_value(resp).unwrap(), json! ({ diff --git a/src/json_writer.rs b/src/json_writer.rs deleted file mode 100644 index a75977ed..00000000 --- a/src/json_writer.rs +++ /dev/null @@ -1,203 +0,0 @@ -#![allow(missing_docs)] - -use serde_json::ser::CharEscape; - -const BB: u8 = b'b'; // \x08 -const TT: u8 = b't'; // \x09 -const NN: u8 = b'n'; // \x0A -const FF: u8 = b'f'; // \x0C -const RR: u8 = b'r'; // \x0D -const QU: u8 = b'"'; // \x22 -const BS: u8 = b'\\'; // \x5C -const UU: u8 = b'u'; // \x00...\x1F except the ones above -const __: u8 = 0; - -#[inline] -fn from_escape_table(escape: u8, byte: u8) -> CharEscape { - match escape { - self::BB => CharEscape::Backspace, - self::TT => CharEscape::Tab, - self::NN => CharEscape::LineFeed, - self::FF => CharEscape::FormFeed, - self::RR => CharEscape::CarriageReturn, - self::QU => CharEscape::Quote, - self::BS => CharEscape::ReverseSolidus, - self::UU => CharEscape::AsciiControl(byte), - _ => unreachable!(), - } -} - -static ESCAPE: [u8; 256] = [ - // 1 2 3 4 5 6 7 8 9 A B C D E F - UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, // 0 - UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, // 1 - __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2 - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3 - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4 - __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5 - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6 - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7 - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8 - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9 - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E - __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F -]; - -#[inline] -fn write_char_escape(data: &mut Vec, char_escape: CharEscape) { - use self::CharEscape::*; - - let s = match char_escape { - Quote => b"\\\"", - ReverseSolidus => b"\\\\", - Solidus => b"\\/", - Backspace => b"\\b", - FormFeed => b"\\f", - LineFeed => b"\\n", - CarriageReturn => b"\\r", - Tab => b"\\t", - AsciiControl(byte) => { - static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef"; - let bytes = &[ - b'\\', - b'u', - b'0', - b'0', - HEX_DIGITS[(byte >> 4) as usize], - HEX_DIGITS[(byte & 0xF) as usize], - ]; - data.extend_from_slice(bytes); - return; - } - }; - data.extend_from_slice(s); -} - -/// JSON Writer -#[derive(Default)] -pub struct JsonWriter { - data: Vec, -} - -impl JsonWriter { - #[inline] - pub fn into_string(self) -> String { - unsafe { String::from_utf8_unchecked(self.data) } - } - - #[inline] - pub fn null(&mut self) { - self.data.extend_from_slice(b"null"); - } - - #[inline] - pub fn bool(&mut self, value: bool) { - if value { - self.data.extend_from_slice(b"true"); - } else { - self.data.extend_from_slice(b"false"); - } - } - - #[inline] - pub fn int(&mut self, value: i64) { - let mut buffer = itoa::Buffer::new(); - self.data.extend_from_slice(buffer.format(value).as_bytes()); - } - - #[inline] - pub fn float(&mut self, value: f64) { - let mut buffer = ryu::Buffer::new(); - let s = buffer.format_finite(value); - self.data.extend_from_slice(s.as_bytes()); - } - - #[inline] - pub fn string(&mut self, value: &str) { - self.data.push(b'"'); - let bytes = value.as_bytes(); - - let mut start = 0; - - for (i, &byte) in bytes.iter().enumerate() { - let escape = ESCAPE[byte as usize]; - if escape == 0 { - continue; - } - - if start < i { - self.data.extend_from_slice(&bytes[start..i]); - } - - let char_escape = from_escape_table(escape, byte); - write_char_escape(&mut self.data, char_escape); - - start = i + 1; - } - - if start != bytes.len() { - self.data.extend_from_slice(&bytes[start..]); - } - self.data.push(b'"'); - } - - #[inline] - pub fn begin_array(&mut self) { - self.data.push(b'['); - } - - #[inline] - pub fn end_array(&mut self) { - if let Some(lc) = self.data.last_mut() { - if *lc == b',' { - *lc = b']'; - return; - } - } - self.data.push(b']'); - } - - #[inline] - pub fn begin_array_value(&mut self) {} - - #[inline] - pub fn end_array_value(&mut self) { - self.data.push(b','); - } - - #[inline] - pub fn begin_object(&mut self) { - self.data.push(b'{'); - } - - #[inline] - pub fn end_object(&mut self) { - if let Some(lc) = self.data.last_mut() { - if *lc == b',' { - *lc = b'}'; - return; - } - } - self.data.push(b'}'); - } - - #[inline] - pub fn begin_object_key(&mut self) {} - - #[inline] - pub fn end_object_key(&mut self) {} - - #[inline] - pub fn begin_object_value(&mut self) { - self.data.push(b':'); - } - - #[inline] - pub fn end_object_value(&mut self) { - self.data.push(b','); - } -} diff --git a/src/lib.rs b/src/lib.rs index 24c79bb7..d313e15c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,7 +69,6 @@ extern crate serde_derive; mod base; mod context; mod error; -mod json_writer; mod model; mod query; mod resolver; @@ -98,7 +97,6 @@ pub use base::{Scalar, Type}; pub use context::{Context, Variables}; pub use error::{ErrorWithPosition, PositionError, QueryError, QueryParseError}; pub use graphql_parser::query::Value; -pub use json_writer::JsonWriter; pub use query::{PreparedQuery, QueryBuilder}; pub use registry::CacheControl; pub use scalars::ID; @@ -121,11 +119,11 @@ pub use context::ContextSelectionSet; #[doc(hidden)] pub mod registry; #[doc(hidden)] -pub use base::{InputObjectType, InputValueType, ObjectType, OutputValueType}; +pub use base::{BoxFieldFuture, InputObjectType, InputValueType, ObjectType, OutputValueType}; #[doc(hidden)] pub use context::ContextBase; #[doc(hidden)] -pub use resolver::do_resolve; +pub use resolver::{collect_fields, do_resolve}; #[doc(hidden)] pub use subscription::{Subscribe, SubscriptionType}; #[doc(hidden)] @@ -216,13 +214,13 @@ pub use types::{EnumItem, EnumType}; /// #[async_std::main] /// async fn main() { /// let schema = Schema::new(MyObject{ value: 10 }, EmptyMutation, EmptySubscription); -/// let res = serde_json::from_str::(&schema.query(r#"{ +/// let res = schema.query(r#"{ /// value /// valueRef /// valueWithError /// valueWithArg1: valueWithArg /// valueWithArg2: valueWithArg(a: 99) -/// }"#).execute().await.unwrap()).unwrap(); +/// }"#).execute().await.unwrap(); /// assert_eq!(res, serde_json::json!({ /// "value": 10, /// "valueRef": 10, @@ -283,7 +281,7 @@ pub use async_graphql_derive::Object; /// #[async_std::main] /// async fn main() { /// let schema = Schema::new(MyObject{ value1: MyEnum::A, value2: MyEnum::B }, EmptyMutation, EmptySubscription); -/// let res = serde_json::from_str::(&schema.query("{ value1 value2 }").execute().await.unwrap()).unwrap(); +/// let res = schema.query("{ value1 value2 }").execute().await.unwrap(); /// assert_eq!(res, serde_json::json!({ "value1": "A", "value2": "b" })); /// } /// ``` @@ -332,11 +330,11 @@ pub use async_graphql_derive::Enum; /// #[async_std::main] /// async fn main() { /// let schema = Schema::new(MyObject, EmptyMutation, EmptySubscription); -/// let res = serde_json::from_str::(&schema.query(r#" +/// let res = schema.query(r#" /// { /// value1: value(input:{a:9, b:3}) /// value2: value(input:{a:9}) -/// }"#).execute().await.unwrap()).unwrap(); +/// }"#).execute().await.unwrap(); /// assert_eq!(res, serde_json::json!({ "value1": 27, "value2": 90 })); /// } /// ``` @@ -437,14 +435,14 @@ pub use async_graphql_derive::InputObject; /// #[async_std::main] /// async fn main() { /// let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription).data("hello".to_string()); -/// let res = serde_json::from_str::(&schema.query(r#" +/// let res = schema.query(r#" /// { /// typeA { /// valueA /// valueB /// valueC(a: 3, b: 2) /// } -/// }"#).execute().await.unwrap()).unwrap(); +/// }"#).execute().await.unwrap(); /// assert_eq!(res, serde_json::json!({ /// "typeA": { /// "valueA": "hello", diff --git a/src/query.rs b/src/query.rs index cfbc2e47..0c94dd2c 100644 --- a/src/query.rs +++ b/src/query.rs @@ -2,7 +2,7 @@ use crate::context::Data; use crate::registry::{CacheControl, Registry}; use crate::types::QueryRoot; use crate::validation::check_rules; -use crate::{ContextBase, JsonWriter, OutputValueType, Result}; +use crate::{ContextBase, OutputValueType, Result}; use crate::{ObjectType, QueryError, QueryParseError, Variables}; use bytes::Bytes; use graphql_parser::parse_query; @@ -111,7 +111,7 @@ impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> { } /// Execute the query. - pub async fn execute(self) -> Result + pub async fn execute(self) -> Result where Query: ObjectType + Send + Sync, Mutation: ObjectType + Send + Sync, @@ -160,7 +160,7 @@ impl<'a, Query, Mutation> PreparedQuery<'a, Query, Mutation> { } /// Execute the query. - pub async fn execute(self) -> Result + pub async fn execute(self) -> Result where Query: ObjectType + Send + Sync, Mutation: ObjectType + Send + Sync, @@ -173,13 +173,11 @@ impl<'a, Query, Mutation> PreparedQuery<'a, Query, Mutation> { data: self.data, fragments: &self.fragments, }; - let mut w = JsonWriter::default(); match self.root { - Root::Query(query) => OutputValueType::resolve(query, &ctx, &mut w).await?, - Root::Mutation(mutation) => OutputValueType::resolve(mutation, &ctx, &mut w).await?, + Root::Query(query) => OutputValueType::resolve(query, &ctx).await, + Root::Mutation(mutation) => OutputValueType::resolve(mutation, &ctx).await, } - Ok(w.into_string()) } /// Get cache control value diff --git a/src/resolver.rs b/src/resolver.rs index 4aad9efc..a29ec05c 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -1,103 +1,91 @@ -use crate::{ContextSelectionSet, ErrorWithPosition, JsonWriter, ObjectType, QueryError, Result}; +use crate::base::BoxFieldFuture; +use crate::{ContextSelectionSet, Error, ErrorWithPosition, ObjectType, QueryError, Result}; +use futures::{future, TryFutureExt}; use graphql_parser::query::{Selection, TypeCondition}; -use std::future::Future; -use std::pin::Pin; - -struct Resolver<'a, T> { - ctx: &'a ContextSelectionSet<'a>, - obj: &'a T, - w: &'a mut JsonWriter, -} - -impl<'a, T: ObjectType + Send + Sync> Resolver<'a, T> { - pub fn resolve(&'a mut self) -> Pin> + 'a + Send>> { - Box::pin(async move { - if self.ctx.items.is_empty() { - anyhow::bail!(QueryError::MustHaveSubFields { - object: T::type_name().to_string(), - } - .with_position(self.ctx.span.0)); - } - - for selection in &self.ctx.item.items { - match selection { - Selection::Field(field) => { - if self.ctx.is_skip(&field.directives)? { - continue; - } - - let ctx_field = self.ctx.with_item(field); - if field.name.as_str() == "__typename" { - self.w.begin_object_key(); - self.w.string(ctx_field.result_name()); - self.w.end_object_key(); - - self.w.begin_object_value(); - self.w.string(&T::type_name()); - self.w.end_object_value(); - - continue; - } - - self.w.begin_object_key(); - self.w.string(ctx_field.result_name()); - self.w.end_object_key(); - - self.w.begin_object_value(); - self.obj.resolve_field(&ctx_field, field, self.w).await?; - self.w.end_object_value(); - } - Selection::FragmentSpread(fragment_spread) => { - if self.ctx.is_skip(&fragment_spread.directives)? { - continue; - } - - if let Some(fragment) = - self.ctx.fragments.get(&fragment_spread.fragment_name) - { - let mut r = Resolver { - ctx: &self.ctx.with_item(&fragment.selection_set), - obj: self.obj, - w: self.w, - }; - r.resolve().await?; - } else { - return Err(QueryError::UnknownFragment { - name: fragment_spread.fragment_name.clone(), - } - .into()); - } - } - Selection::InlineFragment(inline_fragment) => { - if self.ctx.is_skip(&inline_fragment.directives)? { - continue; - } - - if let Some(TypeCondition::On(name)) = &inline_fragment.type_condition { - self.obj - .resolve_inline_fragment( - &name, - &self.ctx.with_item(&inline_fragment.selection_set), - self.w, - ) - .await?; - } - } - } - } - - Ok(()) - }) - } -} +use std::iter::FromIterator; #[allow(missing_docs)] -#[inline] pub async fn do_resolve<'a, T: ObjectType + Send + Sync>( ctx: &'a ContextSelectionSet<'a>, root: &'a T, - w: &mut JsonWriter, +) -> Result { + let mut futures = Vec::new(); + collect_fields(ctx.clone(), root, &mut futures)?; + let res = futures::future::try_join_all(futures).await?; + let map = serde_json::Map::from_iter(res); + Ok(map.into()) +} + +#[allow(missing_docs)] +pub fn collect_fields<'a, T: ObjectType + Send + Sync>( + ctx: ContextSelectionSet<'a>, + root: &'a T, + futures: &mut Vec>, ) -> Result<()> { - Resolver { ctx, obj: root, w }.resolve().await?; + if ctx.items.is_empty() { + anyhow::bail!(QueryError::MustHaveSubFields { + object: T::type_name().to_string(), + } + .with_position(ctx.span.0)); + } + + for selection in &ctx.item.items { + match selection { + Selection::Field(field) => { + if ctx.is_skip(&field.directives)? { + continue; + } + + let ctx_field = ctx.with_item(field); + let field_name = ctx_field.result_name(); + + if field.name.as_str() == "__typename" { + // Get the typename + futures.push(Box::pin( + future::ok::(T::type_name().to_string().into()) + .map_ok(move |value| (field_name, value)), + )); + continue; + } + + futures.push(Box::pin({ + let ctx_field = ctx_field.clone(); + async move { + root.resolve_field(&ctx_field, field) + .map_ok(move |value| (field_name, value)) + .await + } + })) + } + Selection::FragmentSpread(fragment_spread) => { + if ctx.is_skip(&fragment_spread.directives)? { + continue; + } + + if let Some(fragment) = ctx.fragments.get(&fragment_spread.fragment_name) { + collect_fields(ctx.with_item(&fragment.selection_set), root, futures)?; + } else { + return Err(QueryError::UnknownFragment { + name: fragment_spread.fragment_name.clone(), + } + .into()); + } + } + Selection::InlineFragment(inline_fragment) => { + if ctx.is_skip(&inline_fragment.directives)? { + continue; + } + + if let Some(TypeCondition::On(name)) = &inline_fragment.type_condition { + root.collect_inline_fields( + name, + ctx.with_item(&inline_fragment.selection_set), + futures, + )?; + } + } + } + } + Ok(()) } diff --git a/src/scalars/bool.rs b/src/scalars/bool.rs index 480d7f25..e8c27cf0 100644 --- a/src/scalars/bool.rs +++ b/src/scalars/bool.rs @@ -1,4 +1,4 @@ -use crate::{impl_scalar_internal, JsonWriter, Result, Scalar, Value}; +use crate::{impl_scalar_internal, Result, Scalar, Value}; impl Scalar for bool { fn type_name() -> &'static str { @@ -16,9 +16,8 @@ impl Scalar for bool { } } - fn to_json(&self, w: &mut JsonWriter) -> Result<()> { - w.bool(*self); - Ok(()) + fn to_json(&self) -> Result { + Ok((*self).into()) } } diff --git a/src/scalars/bson.rs b/src/scalars/bson.rs index f06d70b0..96f8020c 100644 --- a/src/scalars/bson.rs +++ b/src/scalars/bson.rs @@ -1,4 +1,4 @@ -use crate::{impl_scalar_internal, JsonWriter, Result, Scalar, Value}; +use crate::{impl_scalar_internal, Result, Scalar, Value}; use bson::oid::ObjectId; impl Scalar for ObjectId { @@ -13,9 +13,8 @@ impl Scalar for ObjectId { } } - fn to_json(&self, w: &mut JsonWriter) -> Result<()> { - w.string(&self.to_string()); - Ok(()) + fn to_json(&self) -> Result { + Ok(self.to_string().into()) } } diff --git a/src/scalars/datetime.rs b/src/scalars/datetime.rs index 6858a889..066fbd15 100644 --- a/src/scalars/datetime.rs +++ b/src/scalars/datetime.rs @@ -1,4 +1,4 @@ -use crate::{impl_scalar_internal, JsonWriter, Result, Scalar, Value}; +use crate::{impl_scalar_internal, Result, Scalar, Value}; use chrono::{DateTime, TimeZone, Utc}; /// Implement the DateTime scalar @@ -16,9 +16,8 @@ impl Scalar for DateTime { } } - fn to_json(&self, w: &mut JsonWriter) -> Result<()> { - w.string(&self.to_rfc3339()); - Ok(()) + fn to_json(&self) -> Result { + Ok(self.to_rfc3339().into()) } } diff --git a/src/scalars/floats.rs b/src/scalars/floats.rs index f9cae3ab..3c622c38 100644 --- a/src/scalars/floats.rs +++ b/src/scalars/floats.rs @@ -1,4 +1,4 @@ -use crate::{impl_scalar_internal, JsonWriter, Result, Scalar, Value}; +use crate::{impl_scalar_internal, Result, Scalar, Value}; macro_rules! impl_float_scalars { ($($ty:ty),*) => { @@ -20,9 +20,8 @@ macro_rules! impl_float_scalars { } } - fn to_json(&self, w: &mut JsonWriter) -> Result<()> { - w.float(*self as f64); - Ok(()) + fn to_json(&self) -> Result { + Ok((*self).into()) } } diff --git a/src/scalars/id.rs b/src/scalars/id.rs index 70aec4ac..c81613d4 100644 --- a/src/scalars/id.rs +++ b/src/scalars/id.rs @@ -1,4 +1,4 @@ -use crate::{impl_scalar_internal, JsonWriter, Result, Scalar, Value}; +use crate::{impl_scalar_internal, Result, Scalar, Value}; use std::ops::{Deref, DerefMut}; /// ID scalar @@ -58,9 +58,8 @@ impl Scalar for ID { } } - fn to_json(&self, w: &mut JsonWriter) -> Result<()> { - w.string(&self.0); - Ok(()) + fn to_json(&self) -> Result { + Ok(self.0.clone().into()) } } diff --git a/src/scalars/integers.rs b/src/scalars/integers.rs index 46034c6f..48fdd1b5 100644 --- a/src/scalars/integers.rs +++ b/src/scalars/integers.rs @@ -1,4 +1,4 @@ -use crate::{impl_scalar_internal, JsonWriter, Result, Scalar, Value}; +use crate::{impl_scalar_internal, Result, Scalar, Value}; macro_rules! impl_integer_scalars { ($($ty:ty),*) => { @@ -19,9 +19,8 @@ macro_rules! impl_integer_scalars { } } - fn to_json(&self, w: &mut JsonWriter) -> Result<()> { - w.int(*self as i64); - Ok(()) + fn to_json(&self) -> Result { + Ok((*self).into()) } } diff --git a/src/scalars/string.rs b/src/scalars/string.rs index 6d16904a..2039746e 100644 --- a/src/scalars/string.rs +++ b/src/scalars/string.rs @@ -1,6 +1,6 @@ use crate::{ - impl_scalar_internal, registry, ContextSelectionSet, JsonWriter, OutputValueType, Result, - Scalar, Type, Value, + impl_scalar_internal, registry, ContextSelectionSet, OutputValueType, Result, Scalar, Type, + Value, }; use std::borrow::Cow; @@ -29,9 +29,8 @@ impl Scalar for String { } } - fn to_json(&self, w: &mut JsonWriter) -> Result<()> { - w.string(self.as_str()); - Ok(()) + fn to_json(&self) -> Result { + Ok(self.clone().into()) } } @@ -56,8 +55,7 @@ impl<'a> Type for &'a str { #[async_trait::async_trait] impl<'a> OutputValueType for &'a str { - async fn resolve(value: &Self, _: &ContextSelectionSet<'_>, w: &mut JsonWriter) -> Result<()> { - w.string(*value); - Ok(()) + async fn resolve(value: &Self, _: &ContextSelectionSet<'_>) -> Result { + Ok((*value).into()) } } diff --git a/src/scalars/url.rs b/src/scalars/url.rs index b06cb297..07cd5924 100644 --- a/src/scalars/url.rs +++ b/src/scalars/url.rs @@ -1,4 +1,4 @@ -use crate::{impl_scalar_internal, JsonWriter, Result, Scalar, Value}; +use crate::{impl_scalar_internal, Result, Scalar, Value}; use url::Url; impl Scalar for Url { @@ -13,9 +13,8 @@ impl Scalar for Url { } } - fn to_json(&self, w: &mut JsonWriter) -> Result<()> { - w.string(&self.to_string()); - Ok(()) + fn to_json(&self) -> Result { + Ok(self.to_string().into()) } } diff --git a/src/scalars/uuid.rs b/src/scalars/uuid.rs index 8d76c3e1..98ee4b0a 100644 --- a/src/scalars/uuid.rs +++ b/src/scalars/uuid.rs @@ -1,4 +1,4 @@ -use crate::{impl_scalar_internal, JsonWriter, Result, Scalar, Value}; +use crate::{impl_scalar_internal, Result, Scalar, Value}; use uuid::Uuid; impl Scalar for Uuid { @@ -13,9 +13,8 @@ impl Scalar for Uuid { } } - fn to_json(&self, w: &mut JsonWriter) -> Result<()> { - w.string(&self.to_string()); - Ok(()) + fn to_json(&self) -> Result { + Ok(self.to_string().into()) } } diff --git a/src/subscription.rs b/src/subscription.rs index ed36f91b..86cff5c7 100644 --- a/src/subscription.rs +++ b/src/subscription.rs @@ -28,7 +28,7 @@ impl Subscribe { &self, schema: &Schema, msg: &(dyn Any + Send + Sync), - ) -> Result> + ) -> Result> where Subscription: SubscriptionType + Sync + Send + 'static, { @@ -91,7 +91,7 @@ pub trait SubscriptionType: Type { ctx: &ContextBase<'_, ()>, types: &HashMap, msg: &(dyn Any + Send + Sync), - ) -> Result>; + ) -> Result>; } fn create_types( diff --git a/src/types/connection/connection_type.rs b/src/types/connection/connection_type.rs index 9633efd9..14129c3f 100644 --- a/src/types/connection/connection_type.rs +++ b/src/types/connection/connection_type.rs @@ -1,7 +1,7 @@ use crate::types::connection::edge::Edge; use crate::types::connection::page_info::PageInfo; use crate::{ - do_resolve, registry, Context, ContextSelectionSet, ErrorWithPosition, JsonWriter, ObjectType, + do_resolve, registry, Context, ContextSelectionSet, ErrorWithPosition, ObjectType, OutputValueType, QueryError, Result, Type, }; use graphql_parser::query::Field; @@ -127,16 +127,11 @@ impl Type for Con impl ObjectType for Connection { - async fn resolve_field( - &self, - ctx: &Context<'_>, - field: &Field, - w: &mut JsonWriter, - ) -> Result<()> { + async fn resolve_field(&self, ctx: &Context<'_>, field: &Field) -> Result { if field.name.as_str() == "pageInfo" { let ctx_obj = ctx.with_item(&field.selection_set); let page_info = &self.page_info; - return OutputValueType::resolve(page_info, &ctx_obj, w) + return OutputValueType::resolve(page_info, &ctx_obj) .await .map_err(|err| err.with_position(field.position).into()); } else if field.name.as_str() == "edges" { @@ -150,16 +145,14 @@ impl ObjectType node, }) .collect::>(); - return OutputValueType::resolve(&edges, &ctx_obj, w) + return OutputValueType::resolve(&edges, &ctx_obj) .await .map_err(|err| err.with_position(field.position).into()); } else if field.name.as_str() == "totalCount" { - if let Some(total_count) = self.total_count { - w.int(total_count as i64); - } else { - w.null(); - } - return Ok(()); + return Ok(self + .total_count + .map(|n| (n as i32).into()) + .unwrap_or_else(|| serde_json::Value::Null)); } else if field.name.as_str() == T::type_name().to_plural().to_camel_case() { let ctx_obj = ctx.with_item(&field.selection_set); let items = self @@ -167,7 +160,7 @@ impl ObjectType .iter() .map(|(_, _, item)| item) .collect::>(); - return OutputValueType::resolve(&items, &ctx_obj, w) + return OutputValueType::resolve(&items, &ctx_obj) .await .map_err(|err| err.with_position(field.position).into()); } @@ -178,29 +171,13 @@ impl ObjectType } .with_position(field.position)) } - - async fn resolve_inline_fragment( - &self, - name: &str, - _ctx: &ContextSelectionSet<'_>, - _w: &mut JsonWriter, - ) -> Result<()> { - anyhow::bail!(QueryError::UnrecognizedInlineFragment { - object: Connection::::type_name().to_string(), - name: name.to_string(), - }); - } } #[async_trait::async_trait] impl OutputValueType for Connection { - async fn resolve( - value: &Self, - ctx: &ContextSelectionSet<'_>, - w: &mut JsonWriter, - ) -> Result<()> { - do_resolve(ctx, value, w).await + async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result { + do_resolve(ctx, value).await } } diff --git a/src/types/connection/edge.rs b/src/types/connection/edge.rs index 318c51ba..f201d0cd 100644 --- a/src/types/connection/edge.rs +++ b/src/types/connection/edge.rs @@ -1,6 +1,6 @@ use crate::{ - do_resolve, registry, Context, ContextSelectionSet, ErrorWithPosition, JsonWriter, ObjectType, - OutputValueType, QueryError, Result, Type, + do_resolve, registry, Context, ContextSelectionSet, ErrorWithPosition, ObjectType, + OutputValueType, Result, Type, }; use graphql_parser::query::Field; use std::borrow::Cow; @@ -78,35 +78,17 @@ where T: OutputValueType + Send + Sync + 'a, E: ObjectType + Sync + Send + 'a, { - async fn resolve_field( - &self, - ctx: &Context<'_>, - field: &Field, - w: &mut JsonWriter, - ) -> Result<()> { + async fn resolve_field(&self, ctx: &Context<'_>, field: &Field) -> Result { if field.name.as_str() == "node" { let ctx_obj = ctx.with_item(&field.selection_set); - return OutputValueType::resolve(self.node, &ctx_obj, w) + return OutputValueType::resolve(self.node, &ctx_obj) .await .map_err(|err| err.with_position(field.position).into()); } else if field.name.as_str() == "cursor" { - w.string(self.cursor); - return Ok(()); + return Ok(self.cursor.into()); } - self.extra_type.resolve_field(ctx, field, w).await - } - - async fn resolve_inline_fragment( - &self, - name: &str, - _ctx: &ContextSelectionSet<'_>, - _w: &mut JsonWriter, - ) -> Result<()> { - anyhow::bail!(QueryError::UnrecognizedInlineFragment { - object: as Type>::type_name().to_string(), - name: name.to_string(), - }); + self.extra_type.resolve_field(ctx, field).await } } @@ -116,11 +98,7 @@ where T: OutputValueType + Send + Sync + 'a, E: ObjectType + Sync + Send + 'a, { - async fn resolve( - value: &Self, - ctx: &ContextSelectionSet<'_>, - w: &mut JsonWriter, - ) -> Result<()> { - do_resolve(ctx, value, w).await + async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result { + do_resolve(ctx, value).await } } diff --git a/src/types/connection/mod.rs b/src/types/connection/mod.rs index c5de32a6..dabd5c4f 100644 --- a/src/types/connection/mod.rs +++ b/src/types/connection/mod.rs @@ -106,7 +106,7 @@ impl EmptyEdgeFields {} /// async fn main() { /// let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription); /// -/// assert_eq!(serde_json::from_str::(&schema.query("{ numbers(first: 2) { edges { node } } }").execute().await.unwrap()).unwrap(), serde_json::json!({ +/// assert_eq!(schema.query("{ numbers(first: 2) { edges { node } } }").execute().await.unwrap(), serde_json::json!({ /// "numbers": { /// "edges": [ /// {"node": 0}, @@ -115,7 +115,7 @@ impl EmptyEdgeFields {} /// }, /// })); /// -/// assert_eq!(serde_json::from_str::(&schema.query("{ numbers(last: 2) { edges { node diff } } }").execute().await.unwrap()).unwrap(), serde_json::json!({ +/// assert_eq!(schema.query("{ numbers(last: 2) { edges { node diff } } }").execute().await.unwrap(), serde_json::json!({ /// "numbers": { /// "edges": [ /// {"node": -2, "diff": -1002}, diff --git a/src/types/empty_mutation.rs b/src/types/empty_mutation.rs index 0bf6d542..04e64508 100644 --- a/src/types/empty_mutation.rs +++ b/src/types/empty_mutation.rs @@ -1,6 +1,5 @@ use crate::{ - registry, Context, ContextSelectionSet, JsonWriter, ObjectType, OutputValueType, QueryError, - Result, Type, + registry, Context, ContextSelectionSet, ObjectType, OutputValueType, QueryError, Result, Type, }; use graphql_parser::query::Field; use std::borrow::Cow; @@ -46,32 +45,14 @@ impl ObjectType for EmptyMutation { true } - async fn resolve_field( - &self, - _ctx: &Context<'_>, - _name: &Field, - _w: &mut JsonWriter, - ) -> Result<()> { - unreachable!() - } - - async fn resolve_inline_fragment( - &self, - _name: &str, - _ctx: &ContextSelectionSet<'_>, - _w: &mut JsonWriter, - ) -> Result<()> { + async fn resolve_field(&self, _ctx: &Context<'_>, _name: &Field) -> Result { unreachable!() } } #[async_trait::async_trait] impl OutputValueType for EmptyMutation { - async fn resolve( - _value: &Self, - _ctx: &ContextSelectionSet<'_>, - _w: &mut JsonWriter, - ) -> Result<()> { + async fn resolve(_value: &Self, _ctx: &ContextSelectionSet<'_>) -> Result { Err(QueryError::NotConfiguredMutations.into()) } } diff --git a/src/types/empty_subscription.rs b/src/types/empty_subscription.rs index b7867403..2bd35dad 100644 --- a/src/types/empty_subscription.rs +++ b/src/types/empty_subscription.rs @@ -1,8 +1,9 @@ use crate::{ - registry, ContextBase, ContextSelectionSet, JsonWriter, OutputValueType, QueryError, Result, + registry, ContextBase, ContextSelectionSet, OutputValueType, QueryError, Result, SubscriptionType, Type, }; use graphql_parser::query::Field; +use serde_json::Value; use std::any::{Any, TypeId}; use std::borrow::Cow; use std::collections::hash_map::RandomState; @@ -43,18 +44,14 @@ impl SubscriptionType for EmptySubscription { _ctx: &ContextBase<'_, ()>, _types: &HashMap, _msg: &(dyn Any + Send + Sync), - ) -> Result> { + ) -> Result> { unreachable!() } } #[async_trait::async_trait] impl OutputValueType for EmptySubscription { - async fn resolve( - _value: &Self, - _ctx: &ContextSelectionSet<'_>, - _w: &mut JsonWriter, - ) -> Result<()> { + async fn resolve(_value: &Self, _ctx: &ContextSelectionSet<'_>) -> Result { Err(QueryError::NotConfiguredSubscriptions.into()) } } diff --git a/src/types/enum.rs b/src/types/enum.rs index 1bf59119..c3c5146d 100644 --- a/src/types/enum.rs +++ b/src/types/enum.rs @@ -1,4 +1,4 @@ -use crate::{JsonWriter, Result, Type}; +use crate::{Result, Type}; use graphql_parser::query::Value; #[allow(missing_docs)] @@ -30,12 +30,11 @@ pub trait EnumType: Type + Sized + Eq + Send + Copy + Sized + 'static { }) } - fn resolve_enum(&self, w: &mut JsonWriter) -> Result<()> { + fn resolve_enum(&self) -> Result { let items = Self::items(); for item in items { if item.value == *self { - w.string(item.name); - return Ok(()); + return Ok(item.name.into()); } } unreachable!() diff --git a/src/types/list.rs b/src/types/list.rs index 7cc06d88..4f529632 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -1,6 +1,4 @@ -use crate::{ - registry, ContextSelectionSet, InputValueType, JsonWriter, OutputValueType, Result, Type, Value, -}; +use crate::{registry, ContextSelectionSet, InputValueType, OutputValueType, Result, Type, Value}; use std::borrow::Cow; impl Type for Vec { @@ -36,19 +34,12 @@ impl InputValueType for Vec { #[allow(clippy::ptr_arg)] #[async_trait::async_trait] impl OutputValueType for Vec { - async fn resolve( - value: &Self, - ctx: &ContextSelectionSet<'_>, - w: &mut JsonWriter, - ) -> Result<()> { - w.begin_array(); + async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result { + let mut futures = Vec::with_capacity(value.len()); for item in value { - w.begin_array_value(); - OutputValueType::resolve(item, &ctx, w).await?; - w.end_array_value(); + futures.push(OutputValueType::resolve(item, &ctx)); } - w.end_array(); - Ok(()) + Ok(futures::future::try_join_all(futures).await?.into()) } } @@ -64,19 +55,12 @@ impl Type for &[T] { #[async_trait::async_trait] impl OutputValueType for &[T] { - async fn resolve( - value: &Self, - ctx: &ContextSelectionSet<'_>, - w: &mut JsonWriter, - ) -> Result<()> { - w.begin_array(); - for item in value.iter() { - w.begin_array_value(); - OutputValueType::resolve(item, &ctx, w).await?; - w.end_array_value(); + async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result { + let mut futures = Vec::with_capacity(value.len()); + for item in *value { + futures.push(OutputValueType::resolve(item, &ctx)); } - w.end_array(); - Ok(()) + Ok(futures::future::try_join_all(futures).await?.into()) } } diff --git a/src/types/optional.rs b/src/types/optional.rs index 5992f76f..f5a73644 100644 --- a/src/types/optional.rs +++ b/src/types/optional.rs @@ -1,6 +1,4 @@ -use crate::{ - registry, ContextSelectionSet, InputValueType, JsonWriter, OutputValueType, Result, Type, Value, -}; +use crate::{registry, ContextSelectionSet, InputValueType, OutputValueType, Result, Type, Value}; use std::borrow::Cow; impl Type for Option { @@ -29,16 +27,12 @@ impl InputValueType for Option { #[async_trait::async_trait] impl OutputValueType for Option { - async fn resolve( - value: &Self, - ctx: &ContextSelectionSet<'_>, - w: &mut JsonWriter, - ) -> Result<()> where { + async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result where + { if let Some(inner) = value { - OutputValueType::resolve(inner, ctx, w).await + OutputValueType::resolve(inner, ctx).await } else { - w.null(); - Ok(()) + Ok(serde_json::Value::Null) } } } diff --git a/src/types/query_root.rs b/src/types/query_root.rs index 8f1482e7..beb30f90 100644 --- a/src/types/query_root.rs +++ b/src/types/query_root.rs @@ -1,7 +1,7 @@ use crate::model::{__Schema, __Type}; use crate::{ - do_resolve, registry, Context, ContextSelectionSet, ErrorWithPosition, JsonWriter, ObjectType, - OutputValueType, QueryError, Result, Type, Value, + do_resolve, registry, Context, ContextSelectionSet, ErrorWithPosition, ObjectType, + OutputValueType, Result, Type, Value, }; use graphql_parser::query::Field; use std::borrow::Cow; @@ -65,12 +65,7 @@ impl Type for QueryRoot { #[async_trait::async_trait] impl ObjectType for QueryRoot { - async fn resolve_field( - &self, - ctx: &Context<'_>, - field: &Field, - w: &mut JsonWriter, - ) -> Result<()> { + async fn resolve_field(&self, ctx: &Context<'_>, field: &Field) -> Result { if field.name.as_str() == "__schema" { let ctx_obj = ctx.with_item(&field.selection_set); return OutputValueType::resolve( @@ -78,7 +73,6 @@ impl ObjectType for QueryRoot { registry: &ctx.registry, }, &ctx_obj, - w, ) .await .map_err(|err| err.with_position(field.position).into()); @@ -91,38 +85,18 @@ impl ObjectType for QueryRoot { .get(&type_name) .map(|ty| __Type::new_simple(ctx.registry, ty)), &ctx_obj, - w, ) .await .map_err(|err| err.with_position(field.position).into()); } - self.inner.resolve_field(ctx, field, w).await - } - - async fn resolve_inline_fragment( - &self, - name: &str, - _ctx: &ContextSelectionSet<'_>, - _w: &mut JsonWriter, - ) -> Result<()> { - anyhow::bail!(QueryError::UnrecognizedInlineFragment { - object: T::type_name().to_string(), - name: name.to_string(), - }); + self.inner.resolve_field(ctx, field).await } } #[async_trait::async_trait] impl OutputValueType for QueryRoot { - async fn resolve( - value: &Self, - ctx: &ContextSelectionSet<'_>, - w: &mut JsonWriter, - ) -> Result<()> { - w.begin_object(); - do_resolve(ctx, value, w).await?; - w.end_object(); - Ok(()) + async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result { + do_resolve(ctx, value).await } } diff --git a/src/validation/mod.rs b/src/validation/mod.rs index 86ba7be2..115bb9e5 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -1,13 +1,13 @@ -use crate::error::RuleErrors; -use crate::registry::Registry; -use crate::validation::visitor::{visit, VisitorContext, VisitorNil}; -use crate::{CacheControl, Result}; -use graphql_parser::query::Document; - mod rules; mod utils; mod visitor; +use crate::error::RuleErrors; +use crate::registry::Registry; +use crate::{CacheControl, Result}; +use graphql_parser::query::Document; +use visitor::{visit, VisitorContext, VisitorNil}; + pub fn check_rules(registry: &Registry, doc: &Document) -> Result { let mut ctx = VisitorContext::new(registry, doc); let mut cache_control = CacheControl::default(); diff --git a/tests/enum.rs b/tests/enum.rs index e89652f9..fe4a76a6 100644 --- a/tests/enum.rs +++ b/tests/enum.rs @@ -44,8 +44,7 @@ pub async fn test_enum_type() { "# ); assert_eq!( - serde_json::from_str::(&schema.query(&query).execute().await.unwrap()) - .unwrap(), + schema.query(&query).execute().await.unwrap(), serde_json::json!({ "value": "A", "testArg": "A", diff --git a/tests/input_object.rs b/tests/input_object.rs index c2b84690..6191e9b9 100644 --- a/tests/input_object.rs +++ b/tests/input_object.rs @@ -81,8 +81,7 @@ pub async fn test_input_object_default_value() { }}"# ); assert_eq!( - serde_json::from_str::(&schema.query(&query).execute().await.unwrap()) - .unwrap(), + schema.query(&query).execute().await.unwrap(), serde_json::json!({ "a": { "a": 999, diff --git a/tests/list.rs b/tests/list.rs index f9a4fd14..5fa885f8 100644 --- a/tests/list.rs +++ b/tests/list.rs @@ -52,8 +52,7 @@ pub async fn test_list_type() { json_value ); assert_eq!( - serde_json::from_str::(&schema.query(&query).execute().await.unwrap()) - .unwrap(), + schema.query(&query).execute().await.unwrap(), serde_json::json!({ "valueVec": vec![1, 2, 3, 4, 5], "valueSlice": vec![1, 2, 3, 4, 5], diff --git a/tests/optional.rs b/tests/optional.rs index 2fa7fec9..8674a23e 100644 --- a/tests/optional.rs +++ b/tests/optional.rs @@ -66,8 +66,7 @@ pub async fn test_optional_type() { }}"# ); assert_eq!( - serde_json::from_str::(&schema.query(&query).execute().await.unwrap()) - .unwrap(), + schema.query(&query).execute().await.unwrap(), serde_json::json!({ "value1": 10, "value1Ref": 10, diff --git a/tests/scalars.rs b/tests/scalars.rs index 27dc2652..2331d47b 100644 --- a/tests/scalars.rs +++ b/tests/scalars.rs @@ -35,7 +35,7 @@ macro_rules! test_scalars { let json_value: serde_json::Value = $value.into(); let query = format!("{{ value testArg(input: {0}) testInput(input: {{value: {0}}}) }}", json_value); assert_eq!( - serde_json::from_str::(&schema.query(&query).execute().await.unwrap()).unwrap(), + schema.query(&query).execute().await.unwrap(), serde_json::json!({ "value": $value, "testArg": $value, "testInput": $value }) ); }