Some improvements.

This commit is contained in:
Sunli 2020-10-10 10:32:43 +08:00
parent 7fe59d9f90
commit 87ba51fdd0
57 changed files with 532 additions and 523 deletions

View File

@ -150,8 +150,8 @@ pub fn generate(enum_args: &args::Enum) -> GeneratorResult<TokenStream> {
#[#crate_name::async_trait::async_trait] #[#crate_name::async_trait::async_trait]
impl #crate_name::OutputValueType for #ident { impl #crate_name::OutputValueType for #ident {
async fn resolve(&self, _: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::serde_json::Value> { async fn resolve(&self, _: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> {
Ok(#crate_name::resolver_utils::enum_value(*self).into_json().unwrap()) Ok(#crate_name::resolver_utils::enum_value(*self))
} }
} }

View File

@ -125,7 +125,7 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
put_fields.push(quote! { put_fields.push(quote! {
map.insert( map.insert(
#crate_name::parser::types::Name::new_unchecked(#name.to_owned()), #crate_name::parser::types::Name::new(#name),
#crate_name::InputValueType::to_value(&self.#ident) #crate_name::InputValueType::to_value(&self.#ident)
); );
}); });

View File

@ -312,7 +312,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
#[allow(clippy::all, clippy::pedantic)] #[allow(clippy::all, clippy::pedantic)]
#[#crate_name::async_trait::async_trait] #[#crate_name::async_trait::async_trait]
impl #generics #crate_name::resolver_utils::ContainerType for #ident #generics { impl #generics #crate_name::resolver_utils::ContainerType for #ident #generics {
async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<Option<#crate_name::serde_json::Value>> { async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<Option<#crate_name::Value>> {
#(#resolvers)* #(#resolvers)*
Ok(None) Ok(None)
} }
@ -327,7 +327,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
#[allow(clippy::all, clippy::pedantic)] #[allow(clippy::all, clippy::pedantic)]
#[#crate_name::async_trait::async_trait] #[#crate_name::async_trait::async_trait]
impl #generics #crate_name::OutputValueType for #ident #generics { impl #generics #crate_name::OutputValueType for #ident #generics {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::serde_json::Value> { async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> {
#crate_name::resolver_utils::resolve_container(ctx, self).await #crate_name::resolver_utils::resolve_container(ctx, self).await
} }
} }

View File

@ -90,7 +90,7 @@ pub fn generate(object_args: &args::MergedObject) -> GeneratorResult<TokenStream
#[allow(clippy::all, clippy::pedantic)] #[allow(clippy::all, clippy::pedantic)]
#[#crate_name::async_trait::async_trait] #[#crate_name::async_trait::async_trait]
impl #crate_name::resolver_utils::ContainerType for #ident { impl #crate_name::resolver_utils::ContainerType for #ident {
async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::serde_json::Value>> { async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> {
#create_merged_obj.resolve_field(ctx).await #create_merged_obj.resolve_field(ctx).await
} }
} }
@ -98,7 +98,7 @@ pub fn generate(object_args: &args::MergedObject) -> GeneratorResult<TokenStream
#[allow(clippy::all, clippy::pedantic)] #[allow(clippy::all, clippy::pedantic)]
#[#crate_name::async_trait::async_trait] #[#crate_name::async_trait::async_trait]
impl #crate_name::OutputValueType for #ident { impl #crate_name::OutputValueType for #ident {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::serde_json::Value> { async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> {
#crate_name::resolver_utils::resolve_container(ctx, self).await #crate_name::resolver_utils::resolve_container(ctx, self).await
} }
} }

View File

@ -80,7 +80,7 @@ pub fn generate(object_args: &args::MergedSubscription) -> GeneratorResult<Token
fn create_field_stream<'a>( fn create_field_stream<'a>(
&'a self, &'a self,
ctx: &'a #crate_name::Context<'a> ctx: &'a #crate_name::Context<'a>
) -> Option<::std::pin::Pin<::std::boxed::Box<dyn #crate_name::futures::Stream<Item = #crate_name::ServerResult<#crate_name::serde_json::Value>> + ::std::marker::Send + 'a>>> { ) -> Option<::std::pin::Pin<::std::boxed::Box<dyn #crate_name::futures::Stream<Item = #crate_name::ServerResult<#crate_name::Value>> + ::std::marker::Send + 'a>>> {
None #create_field_stream None #create_field_stream
} }
} }

View File

@ -490,12 +490,12 @@ pub fn generate(
#[allow(unused_braces, unused_variables, unused_parens, unused_mut)] #[allow(unused_braces, unused_variables, unused_parens, unused_mut)]
#[#crate_name::async_trait::async_trait] #[#crate_name::async_trait::async_trait]
impl#generics #crate_name::resolver_utils::ContainerType for #self_ty #where_clause { impl#generics #crate_name::resolver_utils::ContainerType for #self_ty #where_clause {
async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::serde_json::Value>> { async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> {
#(#resolvers)* #(#resolvers)*
Ok(None) Ok(None)
} }
async fn find_entity(&self, ctx: &#crate_name::Context<'_>, params: &#crate_name::Value) -> #crate_name::ServerResult<::std::option::Option<#crate_name::serde_json::Value>> { async fn find_entity(&self, ctx: &#crate_name::Context<'_>, params: &#crate_name::Value) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> {
let params = match params { let params = match params {
#crate_name::Value::Object(params) => params, #crate_name::Value::Object(params) => params,
_ => return Ok(None), _ => return Ok(None),
@ -516,7 +516,7 @@ pub fn generate(
#[allow(clippy::all, clippy::pedantic)] #[allow(clippy::all, clippy::pedantic)]
#[#crate_name::async_trait::async_trait] #[#crate_name::async_trait::async_trait]
impl #generics #crate_name::OutputValueType for #self_ty #where_clause { impl #generics #crate_name::OutputValueType for #self_ty #where_clause {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::serde_json::Value> { async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> {
#crate_name::resolver_utils::resolve_container(ctx, self).await #crate_name::resolver_utils::resolve_container(ctx, self).await
} }
} }

View File

@ -64,8 +64,8 @@ pub fn generate(
&self, &self,
_: &#crate_name::ContextSelectionSet<'_>, _: &#crate_name::ContextSelectionSet<'_>,
_field: &#crate_name::Positioned<#crate_name::parser::types::Field> _field: &#crate_name::Positioned<#crate_name::parser::types::Field>
) -> #crate_name::ServerResult<#crate_name::serde_json::Value> { ) -> #crate_name::ServerResult<#crate_name::Value> {
Ok(#crate_name::ScalarType::to_value(self).into_json().unwrap()) Ok(#crate_name::ScalarType::to_value(self))
} }
} }
}; };

View File

@ -171,7 +171,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
#[#crate_name::async_trait::async_trait] #[#crate_name::async_trait::async_trait]
impl #generics #crate_name::resolver_utils::ContainerType for #ident #generics #where_clause { impl #generics #crate_name::resolver_utils::ContainerType for #ident #generics #where_clause {
async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::serde_json::Value>> { async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> {
#(#resolvers)* #(#resolvers)*
Ok(None) Ok(None)
} }
@ -181,7 +181,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
#[#crate_name::async_trait::async_trait] #[#crate_name::async_trait::async_trait]
impl #generics #crate_name::OutputValueType for #ident #generics #where_clause { impl #generics #crate_name::OutputValueType for #ident #generics #where_clause {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::serde_json::Value> { async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> {
#crate_name::resolver_utils::resolve_container(ctx, self).await #crate_name::resolver_utils::resolve_container(ctx, self).await
} }
} }

View File

@ -381,7 +381,7 @@ pub fn generate(
fn create_field_stream<'a>( fn create_field_stream<'a>(
&'a self, &'a self,
ctx: &'a #crate_name::Context<'a>, ctx: &'a #crate_name::Context<'a>,
) -> ::std::option::Option<::std::pin::Pin<::std::boxed::Box<dyn #crate_name::futures::Stream<Item = #crate_name::ServerResult<#crate_name::serde_json::Value>> + Send + 'a>>> { ) -> ::std::option::Option<::std::pin::Pin<::std::boxed::Box<dyn #crate_name::futures::Stream<Item = #crate_name::ServerResult<#crate_name::Value>> + Send + 'a>>> {
#(#create_stream)* #(#create_stream)*
None None
} }

View File

@ -160,7 +160,7 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
#[#crate_name::async_trait::async_trait] #[#crate_name::async_trait::async_trait]
impl #generics #crate_name::resolver_utils::ContainerType for #ident #generics { impl #generics #crate_name::resolver_utils::ContainerType for #ident #generics {
async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::serde_json::Value>> { async fn resolve_field(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::ServerResult<::std::option::Option<#crate_name::Value>> {
Ok(None) Ok(None)
} }
@ -174,7 +174,7 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
#[allow(clippy::all, clippy::pedantic)] #[allow(clippy::all, clippy::pedantic)]
#[#crate_name::async_trait::async_trait] #[#crate_name::async_trait::async_trait]
impl #generics #crate_name::OutputValueType for #ident #generics { impl #generics #crate_name::OutputValueType for #ident #generics {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::serde_json::Value> { async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>, _field: &#crate_name::Positioned<#crate_name::parser::types::Field>) -> #crate_name::ServerResult<#crate_name::Value> {
#crate_name::resolver_utils::resolve_container(ctx, self).await #crate_name::resolver_utils::resolve_container(ctx, self).await
} }
} }

View File

@ -10,16 +10,14 @@ I would recommend on checking out this [async-graphql example](https://github.co
## General Concept ## General Concept
In `async-graphql` all user-facing errors are cast to the `Error` type which by default provides In `async-graphql` all user-facing errors are cast to the `Error` type which by default provides
the error message exposed by `std::fmt::Display`. However `Error` also provides an additional the error message exposed by `std::fmt::Display`. However, `Error` actually provides an additional information that can extend the error.
field `Option<serde_json::Value>` which - if given some valid `serde_json::Map` - will be exposed as the extensions key to any error.
A resolver looks like this: A resolver looks like this:
```rust ```rust
async fn parse_with_extensions(&self) -> Result<i32> { async fn parse_with_extensions(&self) -> Result<i32, Error> {
let my_extension = json!({ "details": "CAN_NOT_FETCH" }); Err(Error::new("MyMessage").extend_with(|_, e| e.set("details", "CAN_NOT_FETCH")))
Err(Error::new("MyMessage").extend_with(|| my_extension)) }
}
``` ```
may then return a response like this: may then return a response like this:
@ -53,8 +51,8 @@ use std::num::ParseIntError;
async fn parse_with_extensions(&self) -> Result<i32> { async fn parse_with_extensions(&self) -> Result<i32> {
Ok("234a" Ok("234a"
.parse() .parse()
.map_err(|err: ParseIntError| err.extend_with(|_err| json!({"code": 404})))?) .map_err(|err: ParseIntError| err.extend_with(|_err, e| e.set("code", 404)))?)
} }
``` ```
### Implementing ErrorExtensions for custom errors. ### Implementing ErrorExtensions for custom errors.
@ -62,7 +60,8 @@ If you find yourself attaching extensions to your errors all over the place you
implementing the trait on your custom error type directly. implementing the trait on your custom error type directly.
```rust ```rust
use thiserror::Error; #[macro_use]
extern crate thiserror;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum MyError { pub enum MyError {
@ -79,13 +78,11 @@ pub enum MyError {
impl ErrorExtensions for MyError { impl ErrorExtensions for MyError {
// lets define our base extensions // lets define our base extensions
fn extend(&self) -> Error { fn extend(&self) -> Error {
Error::new(format!("{}", self)).extend_with(|err| Error::new(format!("{}", self)).extend_with(|err, e|
match self { match self {
MyError::NotFound => json!({"code": "NOT_FOUND"}), MyError::NotFound => e.set("code", "NOT_FOUND"),
MyError::ServerError(reason) => json!({ "reason": reason }), MyError::ServerError(reason) => e.set("reason", reason),
MyError::ErrorWithoutExtensions => { MyError::ErrorWithoutExtensions => {}
json!("This will be ignored since it does not represent an object.")
}
}) })
} }
} }
@ -98,7 +95,7 @@ Or further extend your error through `extend_with`.
async fn parse_with_extensions_result(&self) -> Result<i32> { async fn parse_with_extensions_result(&self) -> Result<i32> {
// Err(MyError::NotFound.extend()) // Err(MyError::NotFound.extend())
// OR // OR
Err(MyError::NotFound.extend_with(|_| json!({ "on_the_fly": "some_more_info" }))) Err(MyError::NotFound.extend_with(|_, e| e.set("on_the_fly", "some_more_info")))
} }
``` ```
@ -126,9 +123,8 @@ use async_graphql::*;
async fn parse_with_extensions(&self) -> Result<i32> { async fn parse_with_extensions(&self) -> Result<i32> {
Ok("234a" Ok("234a"
.parse() .parse()
.extend_err(|_| json!({"code": 404}))?) .extend_err(|_, e| e.set("code", 404))?)
} }
``` ```
### Chained extensions ### Chained extensions
Since `ErrorExtensions` and `ResultExt` are implemented for any type `&E where E: std::fmt::Display` Since `ErrorExtensions` and `ResultExt` are implemented for any type `&E where E: std::fmt::Display`
@ -141,10 +137,10 @@ async fn parse_with_extensions(&self) -> Result<i32> {
match "234a".parse() { match "234a".parse() {
Ok(n) => Ok(n), Ok(n) => Ok(n),
Err(e) => Err(e Err(e) => Err(e
.extend_with(|_| json!({"code": 404})) .extend_with(|_, e| e.set("code", 404))
.extend_with(|_| json!({"details": "some more info.."})) .extend_with(|_, e| e.set("details", "some more info.."))
// keys may also overwrite previous keys... // keys may also overwrite previous keys...
.extend_with(|_| json!({"code": 500}))), .extend_with(|_, e| e.set("code", 500))),
} }
} }
``` ```
@ -177,7 +173,7 @@ The disadvantage is that the below code does **NOT** compile:
async fn parse_with_extensions_result(&self) -> Result<i32> { async fn parse_with_extensions_result(&self) -> Result<i32> {
// the trait `error::ErrorExtensions` is not implemented // the trait `error::ErrorExtensions` is not implemented
// for `std::num::ParseIntError` // for `std::num::ParseIntError`
"234a".parse().extend_err(|_| json!({"code": 404})) "234a".parse().extend_err(|_, e| e.set("code", 404))
} }
``` ```
@ -188,7 +184,7 @@ async fn parse_with_extensions_result(&self) -> Result<i32> {
// does work because ErrorExtensions is implemented for &ParseIntError // does work because ErrorExtensions is implemented for &ParseIntError
"234a" "234a"
.parse() .parse()
.map_err(|ref e: ParseIntError| e.extend_with(|_| json!({"code": 404}))) .map_err(|ref e: ParseIntError| e.extend_with(|_, e| e.set("code", 404)))
} }
``` ```

View File

@ -12,16 +12,14 @@
## 一般概念 ## 一般概念
在`Async-graphql`中,所有面向用户的错误都强制转换为`Error`类型,默认情况下会提供 在`Async-graphql`中,所有面向用户的错误都强制转换为`Error`类型,默认情况下会提供
由`std:::fmt::Display`暴露的错误消息。但是,`Error`实际上提供了一个额外的 由`std:::fmt::Display`暴露的错误消息。但是,`Error`实际上提供了一个额外的可以扩展错误的信息。
字段`Option<serde_json::Value>`,如果它是一个`serde_json::Map`,则可以扩展错误的信息。
Resolver函数类似这样 Resolver函数类似这样
```rust ```rust
async fn parse_with_extensions(&self) -> Result<i32, Error> { async fn parse_with_extensions(&self) -> Result<i32, Error> {
let my_extension = json!({ "details": "CAN_NOT_FETCH" }); Err(Error::new("MyMessage").extend_with(|_, e| e.set("details", "CAN_NOT_FETCH")))
Err(Error::new("MyMessage").extend_with(|_| my_extension)) }
}
``` ```
然后可以返回如下响应: 然后可以返回如下响应:
@ -55,8 +53,8 @@ use std::num::ParseIntError;
async fn parse_with_extensions(&self) -> Result<i32> { async fn parse_with_extensions(&self) -> Result<i32> {
Ok("234a" Ok("234a"
.parse() .parse()
.map_err(|err: ParseIntError| err.extend_with(|_err| json!({"code": 404})))?) .map_err(|err: ParseIntError| err.extend_with(|_err, e| e.set("code", 404)))?)
} }
``` ```
### 为自定义错误实现ErrorExtensions ### 为自定义错误实现ErrorExtensions
@ -83,13 +81,11 @@ pub enum MyError {
impl ErrorExtensions for MyError { impl ErrorExtensions for MyError {
// lets define our base extensions // lets define our base extensions
fn extend(&self) -> Error { fn extend(&self) -> Error {
Error::new(format!("{}", self)).extend_with(|err| Error::new(format!("{}", self)).extend_with(|err, e|
match self { match self {
MyError::NotFound => json!({"code": "NOT_FOUND"}), MyError::NotFound => e.set("code", "NOT_FOUND"),
MyError::ServerError(reason) => json!({ "reason": reason }), MyError::ServerError(reason) => e.set("reason", reason),
MyError::ErrorWithoutExtensions => { MyError::ErrorWithoutExtensions => {}
json!("This will be ignored since it does not represent an object.")
}
}) })
} }
} }
@ -101,7 +97,7 @@ impl ErrorExtensions for MyError {
async fn parse_with_extensions_result(&self) -> Result<i32> { async fn parse_with_extensions_result(&self) -> Result<i32> {
// Err(MyError::NotFound.extend()) // Err(MyError::NotFound.extend())
// OR // OR
Err(MyError::NotFound.extend_with(|_| json!({ "on_the_fly": "some_more_info" }))) Err(MyError::NotFound.extend_with(|_, e| e.set("on_the_fly", "some_more_info")))
} }
``` ```
@ -129,9 +125,8 @@ use async_graphql::*;
async fn parse_with_extensions(&self) -> Result<i32> { async fn parse_with_extensions(&self) -> Result<i32> {
Ok("234a" Ok("234a"
.parse() .parse()
.extend_err(|_| json!({"code": 404}))?) .extend_err(|_, e| e.set("code", 404))?)
} }
``` ```
### 链式调用 ### 链式调用
@ -144,10 +139,10 @@ async fn parse_with_extensions(&self) -> Result<i32> {
match "234a".parse() { match "234a".parse() {
Ok(n) => Ok(n), Ok(n) => Ok(n),
Err(e) => Err(e Err(e) => Err(e
.extend_with(|_| json!({"code": 404})) .extend_with(|_, e| e.set("code", 404))
.extend_with(|_| json!({"details": "some more info.."})) .extend_with(|_, e| e.set("details", "some more info.."))
// keys may also overwrite previous keys... // keys may also overwrite previous keys...
.extend_with(|_| json!({"code": 500}))), .extend_with(|_, e| e.set("code", 500))),
} }
} }
``` ```
@ -182,7 +177,7 @@ Rust的稳定版本还未提供特化功能这就是为什么`ErrorExtensions
async fn parse_with_extensions_result(&self) -> Result<i32> { async fn parse_with_extensions_result(&self) -> Result<i32> {
// the trait `error::ErrorExtensions` is not implemented // the trait `error::ErrorExtensions` is not implemented
// for `std::num::ParseIntError` // for `std::num::ParseIntError`
"234a".parse().extend_err(|_| json!({"code": 404})) "234a".parse().extend_err(|_, e| e.set("code", 404))
} }
``` ```
@ -193,7 +188,7 @@ async fn parse_with_extensions_result(&self) -> Result<i32> {
// does work because ErrorExtensions is implemented for &ParseIntError // does work because ErrorExtensions is implemented for &ParseIntError
"234a" "234a"
.parse() .parse()
.map_err(|ref e: ParseIntError| e.extend_with(|_| json!({"code": 404}))) .map_err(|ref e: ParseIntError| e.extend_with(|_, e| e.set("code", 404)))
} }
``` ```

View File

@ -188,17 +188,24 @@ fn upload() -> Result<()> {
struct MutationRoot; struct MutationRoot;
#[Object] #[Object]
impl MutationRoot { impl MutationRoot {
async fn single_upload(&self, file: Upload) -> FileInfo { async fn single_upload(&self, ctx: &Context<'_>, file: Upload) -> FileInfo {
println!("single_upload: filename={}", file.filename()); let upload_value = file.value(ctx).unwrap();
println!("single_upload: content_type={:?}", file.content_type()); println!("single_upload: filename={}", upload_value.filename);
println!(
"single_upload: content_type={:?}",
upload_value.content_type
);
let file_info = FileInfo { let file_info = FileInfo {
filename: file.filename().into(), filename: upload_value.filename.clone(),
mime_type: file.content_type().map(ToString::to_string), mime_type: upload_value.content_type.clone(),
}; };
let mut content = String::new(); let mut content = String::new();
file.into_read().read_to_string(&mut content).unwrap(); upload_value
.into_read()
.read_to_string(&mut content)
.unwrap();
assert_eq!(content, "test".to_owned()); assert_eq!(content, "test".to_owned());
file_info file_info

View File

@ -309,8 +309,5 @@ fn parse_arguments(
fn parse_name(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<Name>> { fn parse_name(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<Name>> {
debug_assert_eq!(pair.as_rule(), Rule::name); debug_assert_eq!(pair.as_rule(), Rule::name);
Ok(Positioned::new( Ok(Positioned::new(Name::new(pair.as_str()), pc.step(&pair)))
Name::new_unchecked(pair.as_str().to_owned()),
pc.step(&pair),
))
} }

View File

@ -7,19 +7,20 @@
//! 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/June2018/).
use crate::pos::Positioned; use crate::pos::Positioned;
use serde::de::{Deserializer, Error as _, Unexpected}; use serde::de::Deserializer;
use serde::ser::{Error as _, Serializer}; use serde::ser::{Error as _, Serializer};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::borrow::Borrow; use std::borrow::{Borrow, Cow};
use std::collections::{hash_map, BTreeMap, HashMap}; use std::collections::{hash_map, BTreeMap, HashMap};
use std::convert::{TryFrom, TryInto}; use std::convert::{TryFrom, TryInto};
use std::fmt::{self, Display, Formatter, Write}; use std::fmt::{self, Display, Formatter, Write};
use std::fs::File;
use std::ops::Deref; use std::ops::Deref;
pub use executable::*; pub use executable::*;
pub use serde_json::Number; pub use serde_json::Number;
pub use service::*; pub use service::*;
use std::iter::FromIterator;
use std::sync::Arc;
mod executable; mod executable;
mod service; mod service;
@ -72,7 +73,7 @@ impl Type {
base: if let Some(ty) = ty.strip_prefix('[') { base: if let Some(ty) = ty.strip_prefix('[') {
BaseType::List(Box::new(Self::new(ty.strip_suffix(']')?)?)) BaseType::List(Box::new(Self::new(ty.strip_suffix(']')?)?))
} else { } else {
BaseType::Named(Name::new(ty.to_owned()).ok()?) BaseType::Named(Name::new(ty))
}, },
nullable, nullable,
}) })
@ -132,9 +133,127 @@ pub enum ConstValue {
List(Vec<ConstValue>), List(Vec<ConstValue>),
/// An object. This is a map of keys to values. /// An object. This is a map of keys to values.
Object(BTreeMap<Name, ConstValue>), Object(BTreeMap<Name, ConstValue>),
/// An uploaded file. }
#[serde(serialize_with = "fail_serialize_upload", skip_deserializing)]
Upload(UploadValue), impl From<()> for ConstValue {
fn from((): ()) -> Self {
ConstValue::Null
}
}
macro_rules! from_integer {
($($ty:ident),*) => {
$(
impl From<$ty> for ConstValue {
fn from(n: $ty) -> Self {
ConstValue::Number(n.into())
}
}
)*
};
}
from_integer!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
impl From<f32> for ConstValue {
fn from(f: f32) -> Self {
From::from(f as f64)
}
}
impl From<f64> for ConstValue {
fn from(f: f64) -> Self {
Number::from_f64(f).map_or(ConstValue::Null, ConstValue::Number)
}
}
impl From<bool> for ConstValue {
fn from(value: bool) -> Self {
ConstValue::Boolean(value)
}
}
impl From<String> for ConstValue {
fn from(value: String) -> Self {
ConstValue::String(value)
}
}
impl<'a> From<&'a str> for ConstValue {
fn from(value: &'a str) -> Self {
ConstValue::String(value.into())
}
}
impl<'a> From<Cow<'a, str>> for ConstValue {
fn from(f: Cow<'a, str>) -> Self {
ConstValue::String(f.into_owned())
}
}
impl<T: Into<ConstValue>> FromIterator<T> for ConstValue {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
ConstValue::List(iter.into_iter().map(Into::into).collect())
}
}
impl<'a, T: Clone + Into<ConstValue>> From<&'a [T]> for ConstValue {
fn from(f: &'a [T]) -> Self {
ConstValue::List(f.iter().cloned().map(Into::into).collect())
}
}
impl<T: Into<ConstValue>> From<Vec<T>> for ConstValue {
fn from(f: Vec<T>) -> Self {
ConstValue::List(f.into_iter().map(Into::into).collect())
}
}
impl From<BTreeMap<Name, ConstValue>> for ConstValue {
fn from(f: BTreeMap<Name, ConstValue>) -> Self {
ConstValue::Object(f)
}
}
impl PartialEq<serde_json::Value> for ConstValue {
fn eq(&self, other: &serde_json::Value) -> bool {
match (self, other) {
(ConstValue::Null, serde_json::Value::Null) => true,
(ConstValue::Number(a), serde_json::Value::Number(b)) => a == b,
(ConstValue::String(a), serde_json::Value::String(b)) => a == b,
(ConstValue::Boolean(a), serde_json::Value::Bool(b)) => a == b,
(ConstValue::Enum(a), serde_json::Value::String(b)) => a == b,
(ConstValue::List(a), serde_json::Value::Array(b)) => {
if a.len() != b.len() {
return false;
}
a.iter().zip(b.iter()).all(|(a, b)| a == b)
}
(ConstValue::Object(a), serde_json::Value::Object(b)) => {
if a.len() != b.len() {
return false;
}
for (a_key, a_value) in a.iter() {
if let Some(b_value) = b.get(a_key.as_str()) {
if b_value != a_value {
return false;
}
} else {
return false;
}
}
true
}
_ => false,
}
}
}
impl PartialEq<ConstValue> for serde_json::Value {
fn eq(&self, other: &ConstValue) -> bool {
other == self
}
} }
impl ConstValue { impl ConstValue {
@ -155,7 +274,6 @@ impl ConstValue {
.map(|(key, value)| (key, value.into_value())) .map(|(key, value)| (key, value.into_value()))
.collect(), .collect(),
), ),
Self::Upload(upload) => Value::Upload(upload),
} }
} }
@ -191,7 +309,7 @@ impl Display for ConstValue {
Self::String(val) => write_quoted(val, f), Self::String(val) => write_quoted(val, f),
Self::Boolean(true) => f.write_str("true"), Self::Boolean(true) => f.write_str("true"),
Self::Boolean(false) => f.write_str("false"), Self::Boolean(false) => f.write_str("false"),
Self::Null | Self::Upload(_) => f.write_str("null"), Self::Null => f.write_str("null"),
Self::Enum(name) => f.write_str(name), Self::Enum(name) => f.write_str(name),
Self::List(items) => write_list(items, f), Self::List(items) => write_list(items, f),
Self::Object(map) => write_object(map, f), Self::Object(map) => write_object(map, f),
@ -205,6 +323,7 @@ impl TryFrom<serde_json::Value> for ConstValue {
Self::deserialize(value) Self::deserialize(value)
} }
} }
impl TryFrom<ConstValue> for serde_json::Value { impl TryFrom<ConstValue> for serde_json::Value {
type Error = serde_json::Error; type Error = serde_json::Error;
fn try_from(value: ConstValue) -> Result<Self, Self::Error> { fn try_from(value: ConstValue) -> Result<Self, Self::Error> {
@ -241,9 +360,6 @@ pub enum Value {
List(Vec<Value>), List(Vec<Value>),
/// An object. This is a map of keys to values. /// An object. This is a map of keys to values.
Object(BTreeMap<Name, Value>), Object(BTreeMap<Name, Value>),
/// An uploaded file.
#[serde(serialize_with = "fail_serialize_upload", skip_deserializing)]
Upload(UploadValue),
} }
impl Value { impl Value {
@ -277,7 +393,6 @@ impl Value {
.map(|(key, value)| Ok((key, value.into_const_with_mut(f)?))) .map(|(key, value)| Ok((key, value.into_const_with_mut(f)?)))
.collect::<Result<_, _>>()?, .collect::<Result<_, _>>()?,
), ),
Self::Upload(upload) => ConstValue::Upload(upload),
}) })
} }
@ -322,7 +437,7 @@ impl Display for Value {
Self::String(val) => write_quoted(val, f), Self::String(val) => write_quoted(val, f),
Self::Boolean(true) => f.write_str("true"), Self::Boolean(true) => f.write_str("true"),
Self::Boolean(false) => f.write_str("false"), Self::Boolean(false) => f.write_str("false"),
Self::Null | Self::Upload(_) => f.write_str("null"), Self::Null => f.write_str("null"),
Self::Enum(name) => f.write_str(name), Self::Enum(name) => f.write_str(name),
Self::List(items) => write_list(items, f), Self::List(items) => write_list(items, f),
Self::Object(map) => write_object(map, f), Self::Object(map) => write_object(map, f),
@ -352,9 +467,6 @@ impl TryFrom<Value> for serde_json::Value {
fn fail_serialize_variable<S: Serializer>(_: &str, _: S) -> Result<S::Ok, S::Error> { fn fail_serialize_variable<S: Serializer>(_: &str, _: S) -> Result<S::Ok, S::Error> {
Err(S::Error::custom("cannot serialize variable")) Err(S::Error::custom("cannot serialize variable"))
} }
fn fail_serialize_upload<S: Serializer>(_: &UploadValue, _: S) -> Result<S::Ok, S::Error> {
Err(S::Error::custom("cannot serialize uploaded file"))
}
fn write_quoted(s: &str, f: &mut Formatter<'_>) -> fmt::Result { fn write_quoted(s: &str, f: &mut Formatter<'_>) -> fmt::Result {
f.write_char('"')?; f.write_char('"')?;
@ -390,51 +502,6 @@ fn write_object<K: Display, V: Display>(
f.write_char('}') f.write_char('}')
} }
/// A file upload value.
pub struct UploadValue {
/// The name of the file.
pub filename: String,
/// The content type of the file.
pub content_type: Option<String>,
/// The file data.
pub content: File,
}
impl UploadValue {
/// Attempt to clone the upload value. This type's `Clone` implementation simply calls this and
/// panics on failure.
///
/// # Errors
///
/// Fails if cloning the inner `File` fails.
pub fn try_clone(&self) -> std::io::Result<Self> {
Ok(Self {
filename: self.filename.clone(),
content_type: self.content_type.clone(),
content: self.content.try_clone()?,
})
}
}
impl Clone for UploadValue {
fn clone(&self) -> Self {
self.try_clone().unwrap()
}
}
impl fmt::Debug for UploadValue {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Upload({})", self.filename)
}
}
impl PartialEq for UploadValue {
fn eq(&self, other: &Self) -> bool {
self.filename == other.filename
}
}
impl Eq for UploadValue {}
/// A const GraphQL directive, such as `@deprecated(reason: "Use the other field)`. This differs /// A const GraphQL directive, such as `@deprecated(reason: "Use the other field)`. This differs
/// from [`Directive`](struct.Directive.html) in that it uses [`ConstValue`](enum.ConstValue.html) /// from [`Directive`](struct.Directive.html) in that it uses [`ConstValue`](enum.ConstValue.html)
/// instead of [`Value`](enum.Value.html). /// instead of [`Value`](enum.Value.html).
@ -509,48 +576,22 @@ impl Directive {
} }
} }
/// A GraphQL name. This is a newtype wrapper around a string with the addition guarantee that it /// A GraphQL name.
/// is a valid GraphQL name (follows the regex `[_A-Za-z][_0-9A-Za-z]*`).
/// ///
/// [Reference](https://spec.graphql.org/June2018/#Name). /// [Reference](https://spec.graphql.org/June2018/#Name).
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[serde(transparent)] pub struct Name(Arc<str>);
pub struct Name(String);
impl Serialize for Name {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
serializer.serialize_str(&self.0)
}
}
impl Name { impl Name {
/// Check whether the name is valid (follows the regex `[_A-Za-z][_0-9A-Za-z]*`). /// Create a new name.
#[must_use] pub fn new(name: &str) -> Self {
pub fn is_valid(name: &str) -> bool { Self(name.into())
let mut bytes = name.bytes();
bytes
.next()
.map_or(false, |c| c.is_ascii_alphabetic() || c == b'_')
&& bytes.all(|c| c.is_ascii_alphanumeric() || c == b'_')
}
/// Create a new name without checking whether it is valid or not. This will always check in
/// debug mode.
///
/// This function is not `unsafe` because an invalid name does not cause UB, but care should be
/// taken to make sure it is a valid name.
#[must_use]
pub fn new_unchecked(name: String) -> Self {
debug_assert!(Self::is_valid(&name));
Self(name)
}
/// Create a new name, checking whether it is valid. Returns ownership of the string if it
/// fails.
///
/// # Errors
///
/// Fails if the name is not a valid name.
pub fn new(name: String) -> Result<Self, String> {
if Self::is_valid(&name) {
Ok(Self(name))
} else {
Err(name)
}
} }
/// Get the name as a string. /// Get the name as a string.
@ -558,12 +599,6 @@ impl Name {
pub fn as_str(&self) -> &str { pub fn as_str(&self) -> &str {
&self.0 &self.0
} }
/// Convert the name to a `String`.
#[must_use]
pub fn into_string(self) -> String {
self.0
}
} }
impl AsRef<str> for Name { impl AsRef<str> for Name {
@ -578,12 +613,6 @@ impl Borrow<str> for Name {
} }
} }
impl From<Name> for String {
fn from(name: Name) -> Self {
name.0
}
}
impl Deref for Name { impl Deref for Name {
type Target = str; type Target = str;
@ -600,17 +629,17 @@ impl Display for Name {
impl PartialEq<String> for Name { impl PartialEq<String> for Name {
fn eq(&self, other: &String) -> bool { fn eq(&self, other: &String) -> bool {
self.0 == *other self.as_str() == other
} }
} }
impl PartialEq<str> for Name { impl PartialEq<str> for Name {
fn eq(&self, other: &str) -> bool { fn eq(&self, other: &str) -> bool {
self.0 == other self.as_str() == other
} }
} }
impl PartialEq<Name> for String { impl PartialEq<Name> for String {
fn eq(&self, other: &Name) -> bool { fn eq(&self, other: &Name) -> bool {
other == self self == other.as_str()
} }
} }
impl PartialEq<Name> for str { impl PartialEq<Name> for str {
@ -631,18 +660,8 @@ impl<'a> PartialEq<Name> for &'a str {
impl<'de> Deserialize<'de> for Name { impl<'de> Deserialize<'de> for Name {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Self::new(String::deserialize(deserializer)?) Ok(Self(
.map_err(|s| D::Error::invalid_value(Unexpected::Str(&s), &"a GraphQL name")) String::deserialize(deserializer)?.into_boxed_str().into(),
))
} }
} }
#[cfg(test)]
#[test]
fn test_valid_names() {
assert!(Name::is_valid("valid_name"));
assert!(Name::is_valid("numbers123_456_789abc"));
assert!(Name::is_valid("MiXeD_CaSe"));
assert!(Name::is_valid("_"));
assert!(!Name::is_valid("invalid name"));
assert!(!Name::is_valid("123and_text"));
}

View File

@ -41,12 +41,12 @@ pub trait InputValueType: Type + Sized {
/// Represents a GraphQL output value /// Represents a GraphQL output value
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait OutputValueType: Type { pub trait OutputValueType: Type {
/// Resolve an output value to `serde_json::Value`. /// Resolve an output value to `async_graphql::Value`.
async fn resolve( async fn resolve(
&self, &self,
ctx: &ContextSelectionSet<'_>, ctx: &ContextSelectionSet<'_>,
field: &Positioned<Field>, field: &Positioned<Field>,
) -> ServerResult<serde_json::Value>; ) -> ServerResult<Value>;
} }
impl<T: Type + Send + Sync> Type for &T { impl<T: Type + Send + Sync> Type for &T {
@ -66,7 +66,7 @@ impl<T: OutputValueType + Send + Sync> OutputValueType for &T {
&self, &self,
ctx: &ContextSelectionSet<'_>, ctx: &ContextSelectionSet<'_>,
field: &Positioned<Field>, field: &Positioned<Field>,
) -> ServerResult<serde_json::Value> { ) -> ServerResult<Value> {
T::resolve(*self, ctx, field).await T::resolve(*self, ctx, field).await
} }
} }
@ -91,7 +91,7 @@ impl<T: OutputValueType + Sync> OutputValueType for Result<T> {
&self, &self,
ctx: &ContextSelectionSet<'_>, ctx: &ContextSelectionSet<'_>,
field: &Positioned<Field>, field: &Positioned<Field>,
) -> ServerResult<serde_json::Value> { ) -> ServerResult<Value> {
match self { match self {
Ok(value) => Ok(value.resolve(ctx, field).await?), Ok(value) => Ok(value.resolve(ctx, field).await?),
Err(err) => Err(err.clone().into_server_error().at(field.pos)), Err(err) => Err(err.clone().into_server_error().at(field.pos)),

View File

@ -5,7 +5,8 @@ use crate::parser::types::{
}; };
use crate::schema::SchemaEnv; use crate::schema::SchemaEnv;
use crate::{ use crate::{
Error, InputValueType, Lookahead, Pos, Positioned, Result, ServerError, ServerResult, Value, Error, InputValueType, Lookahead, Pos, Positioned, Result, ServerError, ServerResult,
UploadValue, Value,
}; };
use fnv::FnvHashMap; use fnv::FnvHashMap;
use serde::ser::{SerializeSeq, Serializer}; use serde::ser::{SerializeSeq, Serializer};
@ -248,6 +249,7 @@ pub struct QueryEnvInner {
pub variables: Variables, pub variables: Variables,
pub operation: Positioned<OperationDefinition>, pub operation: Positioned<OperationDefinition>,
pub fragments: HashMap<Name, Positioned<FragmentDefinition>>, pub fragments: HashMap<Name, Positioned<FragmentDefinition>>,
pub uploads: Vec<UploadValue>,
pub ctx_data: Arc<Data>, pub ctx_data: Arc<Data>,
} }

View File

@ -1,9 +1,22 @@
use crate::{parser, InputValueType, Pos, Value}; use crate::{parser, InputValueType, Pos, Value};
use serde::Serialize; use serde::Serialize;
use std::collections::BTreeMap;
use std::fmt::{self, Debug, Display, Formatter}; use std::fmt::{self, Debug, Display, Formatter};
use std::marker::PhantomData; use std::marker::PhantomData;
use thiserror::Error; use thiserror::Error;
/// Extensions to the error.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
#[serde(transparent)]
pub struct ErrorExtensionValues(BTreeMap<String, Value>);
impl ErrorExtensionValues {
/// Set an extension value.
pub fn set(&mut self, name: impl AsRef<str>, value: impl Into<Value>) {
self.0.insert(name.as_ref().to_string(), value.into());
}
}
/// An error in a GraphQL server. /// An error in a GraphQL server.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct ServerError { pub struct ServerError {
@ -16,8 +29,15 @@ pub struct ServerError {
#[serde(skip_serializing_if = "Vec::is_empty")] #[serde(skip_serializing_if = "Vec::is_empty")]
pub path: Vec<PathSegment>, pub path: Vec<PathSegment>,
/// Extensions to the error. /// Extensions to the error.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "error_extensions_is_empty")]
pub extensions: Option<serde_json::Map<String, serde_json::Value>>, pub extensions: Option<ErrorExtensionValues>,
}
fn error_extensions_is_empty(values: &Option<ErrorExtensionValues>) -> bool {
match values {
Some(values) => values.0.is_empty(),
None => true,
}
} }
impl ServerError { impl ServerError {
@ -152,8 +172,8 @@ pub struct Error {
/// The error message. /// The error message.
pub message: String, pub message: String,
/// Extensions to the error. /// Extensions to the error.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "error_extensions_is_empty")]
pub extensions: Option<serde_json::Map<String, serde_json::Value>>, pub extensions: Option<ErrorExtensionValues>,
} }
impl Error { impl Error {
@ -189,55 +209,6 @@ impl<T: Display> From<T> for Error {
/// An alias for `Result<T, Error>`. /// An alias for `Result<T, Error>`.
pub type Result<T, E = Error> = std::result::Result<T, E>; pub type Result<T, E = Error> = std::result::Result<T, E>;
/*
/// Extend errors with additional information.
///
/// This trait is implemented for `Error` and `Result<T>`.
pub trait ExtendError {
/// Extend the error with the extensions.
///
/// The value must be a map otherwise this function will panic. It takes a value for the
/// ergonomics of being able to use serde_json's `json!` macro.
///
/// If the error already contains extensions they are appended on.
fn extend(self, extensions: serde_json::Value) -> Self;
/// Extend the error with a callback to make the extensions.
fn extend_with(self, f: impl FnOnce(&Error) -> serde_json::Value) -> Self;
}
impl ExtendError for Error {
fn extend(self, extensions: serde_json::Value) -> Self {
let mut extensions = match extensions {
serde_json::Value::Object(map) => map,
_ => panic!("Extend must be called with a map"),
};
Self {
extensions: Some(match self.extensions {
Some(mut existing) => {
existing.append(&mut extensions);
existing
}
None => extensions,
}),
..self
}
}
fn extend_with(self, f: impl FnOnce(&Error) -> serde_json::Value) -> Self {
let ext = f(&self);
self.extend(ext)
}
}
impl<T> ExtendError for Result<T> {
fn extend(self, extensions: serde_json::Value) -> Self {
self.map_err(|e| e.extend(extensions))
}
fn extend_with(self, f: impl FnOnce(&Error) -> serde_json::Value) -> Self {
self.map_err(|e| e.extend_with(f))
}
}*/
/// An error parsing the request. /// An error parsing the request.
#[derive(Debug, Error)] #[derive(Debug, Error)]
#[non_exhaustive] #[non_exhaustive]
@ -305,24 +276,14 @@ pub trait ErrorExtensions: Sized {
/// Add extensions to the error, using a callback to make the extensions. /// Add extensions to the error, using a callback to make the extensions.
fn extend_with<C>(self, cb: C) -> Error fn extend_with<C>(self, cb: C) -> Error
where where
C: FnOnce(&Self) -> serde_json::Value, C: FnOnce(&Self, &mut ErrorExtensionValues),
{ {
let message = self.extend().message; let message = self.extend().message;
match cb(&self) { let mut extensions = self.extend().extensions.unwrap_or_default();
serde_json::Value::Object(cb_res) => { cb(&self, &mut extensions);
let extensions = match self.extend().extensions { Error {
Some(mut extensions) => { message,
extensions.extend(cb_res); extensions: Some(extensions),
extensions
}
None => cb_res.into_iter().collect(),
};
Error {
message,
extensions: Some(extensions),
}
}
_ => panic!("Extend must be called with a map"),
} }
} }
} }
@ -349,7 +310,7 @@ pub trait ResultExt<T, E>: Sized {
/// Extend the error value of the result with the callback. /// Extend the error value of the result with the callback.
fn extend_err<C>(self, cb: C) -> Result<T> fn extend_err<C>(self, cb: C) -> Result<T>
where where
C: FnOnce(&E) -> serde_json::Value; C: FnOnce(&E, &mut ErrorExtensionValues);
/// Extend the result to a `Result`. /// Extend the result to a `Result`.
fn extend(self) -> Result<T>; fn extend(self) -> Result<T>;
@ -363,10 +324,10 @@ where
{ {
fn extend_err<C>(self, cb: C) -> Result<T> fn extend_err<C>(self, cb: C) -> Result<T>
where where
C: FnOnce(&E) -> serde_json::Value, C: FnOnce(&E, &mut ErrorExtensionValues),
{ {
match self { match self {
Err(err) => Err(err.extend_with(|e| cb(e))), Err(err) => Err(err.extend_with(|e, ee| cb(e, ee))),
Ok(value) => Ok(value), Ok(value) => Ok(value),
} }
} }

View File

@ -1,9 +1,10 @@
use crate::extensions::{Extension, ExtensionContext, ExtensionFactory, ResolveInfo}; use crate::extensions::{Extension, ExtensionContext, ExtensionFactory, ResolveInfo};
use crate::Variables; use crate::{Value, Variables};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::ser::SerializeMap; use serde::ser::SerializeMap;
use serde::{Serialize, Serializer}; use serde::{Serialize, Serializer};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::convert::TryInto;
use std::ops::Deref; use std::ops::Deref;
struct PendingResolve { struct PendingResolve {
@ -116,10 +117,11 @@ impl Extension for ApolloTracingExtension {
} }
} }
fn result(&mut self, _ctx: &ExtensionContext<'_>) -> Option<serde_json::Value> { fn result(&mut self, _ctx: &ExtensionContext<'_>) -> Option<Value> {
self.resolves self.resolves
.sort_by(|a, b| a.start_offset.cmp(&b.start_offset)); .sort_by(|a, b| a.start_offset.cmp(&b.start_offset));
Some(serde_json::json!({
serde_json::json!({
"version": 1, "version": 1,
"startTime": self.start_time.to_rfc3339(), "startTime": self.start_time.to_rfc3339(),
"endTime": self.end_time.to_rfc3339(), "endTime": self.end_time.to_rfc3339(),
@ -127,6 +129,8 @@ impl Extension for ApolloTracingExtension {
"execution": { "execution": {
"resolvers": self.resolves "resolvers": self.resolves
} }
})) })
.try_into()
.ok()
} }
} }

View File

@ -18,10 +18,10 @@ pub use self::apollo_tracing::ApolloTracing;
pub use self::logger::Logger; pub use self::logger::Logger;
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
pub use self::tracing::Tracing; pub use self::tracing::Tracing;
use crate::parser::types::ExecutableDocument; use crate::parser::types::{ExecutableDocument, Name};
use crate::Error; use crate::{Error, Value};
use serde_json::Value;
use std::any::{Any, TypeId}; use std::any::{Any, TypeId};
use std::collections::BTreeMap;
pub(crate) type BoxExtension = Box<dyn Extension>; pub(crate) type BoxExtension = Box<dyn Extension>;
@ -141,7 +141,7 @@ pub trait Extension: Sync + Send + 'static {
fn error(&mut self, ctx: &ExtensionContext<'_>, err: &ServerError) {} fn error(&mut self, ctx: &ExtensionContext<'_>, err: &ServerError) {}
/// Get the results /// Get the results
fn result(&mut self, ctx: &ExtensionContext<'_>) -> Option<serde_json::Value> { fn result(&mut self, ctx: &ExtensionContext<'_>) -> Option<Value> {
None None
} }
} }
@ -236,16 +236,16 @@ impl Extension for Extensions {
.iter_mut() .iter_mut()
.filter_map(|e| { .filter_map(|e| {
if let Some(name) = e.name() { if let Some(name) = e.name() {
e.result(ctx).map(|res| (name.to_string(), res)) e.result(ctx).map(|res| (Name::new(name), res))
} else { } else {
None None
} }
}) })
.collect::<serde_json::Map<_, _>>(); .collect::<BTreeMap<_, _>>();
if value.is_empty() { if value.is_empty() {
None None
} else { } else {
Some(value.into()) Some(Value::Object(value))
} }
} else { } else {
None None

View File

@ -1,4 +1,4 @@
use crate::{BatchRequest, ParseRequestError}; use crate::{BatchRequest, ParseRequestError, UploadValue};
use bytes::Bytes; use bytes::Bytes;
use futures::io::AsyncRead; use futures::io::AsyncRead;
use futures::stream::Stream; use futures::stream::Stream;
@ -101,15 +101,16 @@ pub(super) async fn receive_batch_multipart(
for (name, filename, content_type, file) in files { for (name, filename, content_type, file) in files {
if let Some(var_paths) = map.remove(&name) { if let Some(var_paths) = map.remove(&name) {
let upload = UploadValue {
filename,
content_type,
content: file,
};
for var_path in var_paths { for var_path in var_paths {
match &mut request { match &mut request {
BatchRequest::Single(request) => { BatchRequest::Single(request) => {
request.set_upload( request.set_upload(&var_path, upload.try_clone()?);
&var_path,
filename.clone(),
content_type.clone(),
file.try_clone().unwrap(),
);
} }
BatchRequest::Batch(requests) => { BatchRequest::Batch(requests) => {
let mut s = var_path.splitn(2, '.'); let mut s = var_path.splitn(2, '.');
@ -118,12 +119,7 @@ pub(super) async fn receive_batch_multipart(
if let (Some(idx), Some(path)) = (idx, path) { if let (Some(idx), Some(path)) = (idx, path) {
if let Some(request) = requests.get_mut(idx) { if let Some(request) = requests.get_mut(idx) {
request.set_upload( request.set_upload(path, upload.try_clone()?);
path,
filename.clone(),
content_type.clone(),
file.try_clone().unwrap(),
);
} }
} }
} }

View File

@ -162,8 +162,8 @@ pub use context::{
Context, ContextBase, Data, QueryEnv, QueryPathNode, QueryPathSegment, ResolveId, Variables, Context, ContextBase, Data, QueryEnv, QueryPathNode, QueryPathSegment, ResolveId, Variables,
}; };
pub use error::{ pub use error::{
Error, ErrorExtensions, InputValueError, InputValueResult, ParseRequestError, PathSegment, Error, ErrorExtensionValues, ErrorExtensions, InputValueError, InputValueResult,
Result, ResultExt, ServerError, ServerResult, ParseRequestError, PathSegment, Result, ResultExt, ServerError, ServerResult,
}; };
pub use look_ahead::Lookahead; pub use look_ahead::Lookahead;
pub use parser::types::{ConstValue as Value, Number}; pub use parser::types::{ConstValue as Value, Number};

View File

@ -1,10 +1,8 @@
use crate::parser::types::UploadValue; use crate::{Data, ParseRequestError, UploadValue, Value, Variables};
use crate::{Data, ParseRequestError, Value, Variables};
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use std::any::Any; use std::any::Any;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::fs::File;
/// GraphQL request. /// GraphQL request.
/// ///
@ -25,6 +23,10 @@ pub struct Request {
#[serde(default, deserialize_with = "deserialize_variables")] #[serde(default, deserialize_with = "deserialize_variables")]
pub variables: Variables, pub variables: Variables,
/// Uploads
#[serde(skip)]
pub uploads: Vec<UploadValue>,
/// The data of the request that can be accessed through `Context::data`. /// The data of the request that can be accessed through `Context::data`.
/// ///
/// **This data is only valid for this request** /// **This data is only valid for this request**
@ -49,6 +51,7 @@ impl Request {
query: query.into(), query: query.into(),
operation_name: None, operation_name: None,
variables: Variables::default(), variables: Variables::default(),
uploads: Vec::default(),
data: Data::default(), data: Data::default(),
extensions: Default::default(), extensions: Default::default(),
} }
@ -79,22 +82,13 @@ impl Request {
/// `variables.files.2.content` is equivalent to the Rust code /// `variables.files.2.content` is equivalent to the Rust code
/// `request.variables["files"][2]["content"]`. If no variable exists at the path this function /// `request.variables["files"][2]["content"]`. If no variable exists at the path this function
/// won't do anything. /// won't do anything.
pub fn set_upload( pub fn set_upload(&mut self, var_path: &str, upload: UploadValue) {
&mut self,
var_path: &str,
filename: String,
content_type: Option<String>,
content: File,
) {
let variable = match self.variables.variable_path(var_path) { let variable = match self.variables.variable_path(var_path) {
Some(variable) => variable, Some(variable) => variable,
None => return, None => return,
}; };
*variable = Value::Upload(UploadValue { self.uploads.push(upload);
filename, *variable = Value::String(format!("#__graphql_file__:{}", self.uploads.len() - 1));
content_type,
content,
});
} }
} }

View File

@ -1,9 +1,10 @@
use crate::extensions::{ErrorLogger, Extension, ExtensionContext, ResolveInfo}; use crate::extensions::{ErrorLogger, Extension, ExtensionContext, ResolveInfo};
use crate::parser::types::Selection; use crate::parser::types::{Name, Selection};
use crate::registry::MetaType; use crate::registry::MetaType;
use crate::{ use crate::{
Context, ContextSelectionSet, OutputValueType, PathSegment, ServerError, ServerResult, Value, Context, ContextSelectionSet, OutputValueType, PathSegment, ServerError, ServerResult, Value,
}; };
use std::collections::BTreeMap;
use std::future::Future; use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
@ -19,10 +20,10 @@ pub trait ContainerType: OutputValueType {
false false
} }
/// Resolves a field value and outputs it as a json value `serde_json::Value`. /// Resolves a field value and outputs it as a json value `async_graphql::Value`.
/// ///
/// If the field was not found returns None. /// If the field was not found returns None.
async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<serde_json::Value>>; async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>>;
/// Collect all the fields of the container that are queried in the selection set. /// Collect all the fields of the container that are queried in the selection set.
/// ///
@ -42,18 +43,14 @@ pub trait ContainerType: OutputValueType {
/// Find the GraphQL entity with the given name from the parameter. /// Find the GraphQL entity with the given name from the parameter.
/// ///
/// Objects should override this in case they are the query root. /// Objects should override this in case they are the query root.
async fn find_entity( async fn find_entity(&self, _: &Context<'_>, _params: &Value) -> ServerResult<Option<Value>> {
&self,
_: &Context<'_>,
_params: &Value,
) -> ServerResult<Option<serde_json::Value>> {
Ok(None) Ok(None)
} }
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<T: ContainerType + Send + Sync> ContainerType for &T { impl<T: ContainerType + Send + Sync> ContainerType for &T {
async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<serde_json::Value>> { async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>> {
T::resolve_field(*self, ctx).await T::resolve_field(*self, ctx).await
} }
} }
@ -64,55 +61,54 @@ impl<T: ContainerType + Send + Sync> ContainerType for &T {
pub async fn resolve_container<'a, T: ContainerType + Send + Sync>( pub async fn resolve_container<'a, T: ContainerType + Send + Sync>(
ctx: &ContextSelectionSet<'a>, ctx: &ContextSelectionSet<'a>,
root: &'a T, root: &'a T,
) -> ServerResult<serde_json::Value> { ) -> ServerResult<Value> {
let mut fields = Fields(Vec::new()); let mut fields = Fields(Vec::new());
fields.add_set(ctx, root)?; fields.add_set(ctx, root)?;
let futures = fields.0; let futures = fields.0;
let res = futures::future::try_join_all(futures).await?; let res = futures::future::try_join_all(futures).await?;
let mut map = serde_json::Map::new(); let mut map = BTreeMap::new();
for (name, value) in res { for (name, value) in res {
if let serde_json::Value::Object(b) = value { if let Value::Object(b) = value {
if let Some(serde_json::Value::Object(a)) = map.get_mut(&name) { if let Some(Value::Object(a)) = map.get_mut(&name) {
a.extend(b); a.extend(b);
} else { } else {
map.insert(name, b.into()); map.insert(name, Value::Object(b));
} }
} else { } else {
map.insert(name, value); map.insert(name, value);
} }
} }
Ok(map.into()) Ok(Value::Object(map))
} }
/// Resolve an container by executing each of the fields serially. /// Resolve an container by executing each of the fields serially.
pub async fn resolve_container_serial<'a, T: ContainerType + Send + Sync>( pub async fn resolve_container_serial<'a, T: ContainerType + Send + Sync>(
ctx: &ContextSelectionSet<'a>, ctx: &ContextSelectionSet<'a>,
root: &'a T, root: &'a T,
) -> ServerResult<serde_json::Value> { ) -> ServerResult<Value> {
let mut fields = Fields(Vec::new()); let mut fields = Fields(Vec::new());
fields.add_set(ctx, root)?; fields.add_set(ctx, root)?;
let futures = fields.0; let futures = fields.0;
let mut map = serde_json::Map::new(); let mut map = BTreeMap::new();
for field in futures { for field in futures {
let (name, value) = field.await?; let (name, value) = field.await?;
if let serde_json::Value::Object(b) = value { if let Value::Object(b) = value {
if let Some(serde_json::Value::Object(a)) = map.get_mut(&name) { if let Some(Value::Object(a)) = map.get_mut(&name) {
a.extend(b); a.extend(b);
} else { } else {
map.insert(name, b.into()); map.insert(name, Value::Object(b));
} }
} else { } else {
map.insert(name, value); map.insert(name, value);
} }
} }
Ok(map.into()) Ok(Value::Object(map))
} }
type BoxFieldFuture<'a> = type BoxFieldFuture<'a> = Pin<Box<dyn Future<Output = ServerResult<(Name, Value)>> + 'a + Send>>;
Pin<Box<dyn Future<Output = ServerResult<(String, serde_json::Value)>> + 'a + Send>>;
/// A set of fields on an container that are being selected. /// A set of fields on an container that are being selected.
pub struct Fields<'a>(Vec<BoxFieldFuture<'a>>); pub struct Fields<'a>(Vec<BoxFieldFuture<'a>>);
@ -134,17 +130,11 @@ impl<'a> Fields<'a> {
if field.node.name.node == "__typename" { if field.node.name.node == "__typename" {
// Get the typename // Get the typename
let ctx_field = ctx.with_field(field); let ctx_field = ctx.with_field(field);
let field_name = ctx_field let field_name = ctx_field.item.node.response_key().node.clone();
.item
.node
.response_key()
.node
.clone()
.into_string();
let typename = root.introspection_type_name().into_owned(); let typename = root.introspection_type_name().into_owned();
self.0.push(Box::pin(async move { self.0.push(Box::pin(async move {
Ok((field_name, serde_json::Value::String(typename))) Ok((field_name, Value::String(typename)))
})); }));
continue; continue;
} }
@ -164,13 +154,7 @@ impl<'a> Fields<'a> {
let ctx = ctx.clone(); let ctx = ctx.clone();
async move { async move {
let ctx_field = ctx.with_field(field); let ctx_field = ctx.with_field(field);
let field_name = ctx_field let field_name = ctx_field.item.node.response_key().node.clone();
.item
.node
.response_key()
.node
.clone()
.into_string();
let ctx_extension = ExtensionContext { let ctx_extension = ExtensionContext {
schema_data: &ctx.schema_env.data, schema_data: &ctx.schema_env.data,
query_data: &ctx.query_env.ctx_data, query_data: &ctx.query_env.ctx_data,
@ -196,7 +180,7 @@ impl<'a> Fields<'a> {
T::type_name() T::type_name()
)) ))
.at(ctx_field.item.pos) .at(ctx_field.item.pos)
.path(PathSegment::Field(field_name))); .path(PathSegment::Field(field_name.to_string())));
} }
}, },
}; };
@ -209,7 +193,7 @@ impl<'a> Fields<'a> {
let res = match root.resolve_field(&ctx_field).await { let res = match root.resolve_field(&ctx_field).await {
Ok(value) => Ok((field_name, value.unwrap())), Ok(value) => Ok((field_name, value.unwrap())),
Err(e) => Err(e.path(PathSegment::Field(field_name))), Err(e) => Err(e.path(PathSegment::Field(field_name.to_string()))),
} }
.log_error(&ctx_extension, &ctx_field.query_env.extensions)?; .log_error(&ctx_extension, &ctx_field.query_env.extensions)?;

View File

@ -42,5 +42,5 @@ pub fn parse_enum<T: EnumType + InputValueType>(value: Value) -> InputValueResul
/// This can be used to implement `InputValueType::to_value` or `OutputValueType::resolve`. /// This can be used to implement `InputValueType::to_value` or `OutputValueType::resolve`.
pub fn enum_value<T: EnumType>(value: T) -> Value { pub fn enum_value<T: EnumType>(value: T) -> Value {
let item = T::items().iter().find(|item| item.value == value).unwrap(); let item = T::items().iter().find(|item| item.value == value).unwrap();
Value::Enum(Name::new_unchecked(item.name.to_owned())) Value::Enum(Name::new(item.name))
} }

View File

@ -1,13 +1,15 @@
use crate::extensions::{ErrorLogger, Extension, ExtensionContext, ResolveInfo}; use crate::extensions::{ErrorLogger, Extension, ExtensionContext, ResolveInfo};
use crate::parser::types::Field; use crate::parser::types::Field;
use crate::{ContextSelectionSet, OutputValueType, PathSegment, Positioned, ServerResult, Type}; use crate::{
ContextSelectionSet, OutputValueType, PathSegment, Positioned, ServerResult, Type, Value,
};
/// Resolve an list by executing each of the items concurrently. /// Resolve an list by executing each of the items concurrently.
pub async fn resolve_list<'a, T: OutputValueType + Send + Sync + 'a>( pub async fn resolve_list<'a, T: OutputValueType + Send + Sync + 'a>(
ctx: &ContextSelectionSet<'a>, ctx: &ContextSelectionSet<'a>,
field: &Positioned<Field>, field: &Positioned<Field>,
iter: impl IntoIterator<Item = T>, iter: impl IntoIterator<Item = T>,
) -> ServerResult<serde_json::Value> { ) -> ServerResult<Value> {
let mut futures = Vec::new(); let mut futures = Vec::new();
for (idx, item) in iter.into_iter().enumerate() { for (idx, item) in iter.into_iter().enumerate() {
@ -45,5 +47,5 @@ pub async fn resolve_list<'a, T: OutputValueType + Send + Sync + 'a>(
}); });
} }
Ok(futures::future::try_join_all(futures).await?.into()) Ok(Value::List(futures::future::try_join_all(futures).await?))
} }

View File

@ -1,15 +1,15 @@
use crate::{CacheControl, Result, ServerError}; use crate::{CacheControl, Result, ServerError, Value};
use serde::Serialize; use serde::Serialize;
/// Query response /// Query response
#[derive(Debug, Default, Serialize)] #[derive(Debug, Default, Serialize)]
pub struct Response { pub struct Response {
/// Data of query result /// Data of query result
pub data: serde_json::Value, pub data: Value,
/// Extensions result /// Extensions result
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub extensions: Option<serde_json::Value>, pub extensions: Option<Value>,
/// Cache control value /// Cache control value
#[serde(skip)] #[serde(skip)]
@ -23,7 +23,7 @@ pub struct Response {
impl Response { impl Response {
/// Create a new successful response with the data. /// Create a new successful response with the data.
#[must_use] #[must_use]
pub fn new(data: impl Into<serde_json::Value>) -> Self { pub fn new(data: impl Into<Value>) -> Self {
Self { Self {
data: data.into(), data: data.into(),
..Default::default() ..Default::default()
@ -41,7 +41,7 @@ impl Response {
/// Set the extensions result of the response. /// Set the extensions result of the response.
#[must_use] #[must_use]
pub fn extensions(self, extensions: Option<serde_json::Value>) -> Self { pub fn extensions(self, extensions: Option<Value>) -> Self {
Self { extensions, ..self } Self { extensions, ..self }
} }
@ -127,15 +127,15 @@ mod tests {
#[test] #[test]
fn test_batch_response_single() { fn test_batch_response_single() {
let resp = BatchResponse::Single(Response::new(serde_json::Value::Bool(true))); let resp = BatchResponse::Single(Response::new(Value::Boolean(true)));
assert_eq!(serde_json::to_string(&resp).unwrap(), r#"{"data":true}"#); assert_eq!(serde_json::to_string(&resp).unwrap(), r#"{"data":true}"#);
} }
#[test] #[test]
fn test_batch_response_batch() { fn test_batch_response_batch() {
let resp = BatchResponse::Batch(vec![ let resp = BatchResponse::Batch(vec![
Response::new(serde_json::Value::Bool(true)), Response::new(Value::Boolean(true)),
Response::new(serde_json::Value::String("1".to_string())), Response::new(Value::String("1".to_string())),
]); ]);
assert_eq!( assert_eq!(
serde_json::to_string(&resp).unwrap(), serde_json::to_string(&resp).unwrap(),

View File

@ -10,12 +10,13 @@ use crate::types::QueryRoot;
use crate::validation::{check_rules, CheckResult, ValidationMode}; use crate::validation::{check_rules, CheckResult, ValidationMode};
use crate::{ use crate::{
BatchRequest, BatchResponse, CacheControl, ContextBase, ObjectType, QueryEnv, Request, BatchRequest, BatchResponse, CacheControl, ContextBase, ObjectType, QueryEnv, Request,
Response, ServerError, SubscriptionType, Type, ID, Response, ServerError, SubscriptionType, Type, Value, ID,
}; };
use futures::stream::{self, Stream, StreamExt}; use futures::stream::{self, Stream, StreamExt};
use indexmap::map::IndexMap; use indexmap::map::IndexMap;
use itertools::Itertools; use itertools::Itertools;
use std::any::Any; use std::any::Any;
use std::collections::BTreeMap;
use std::ops::Deref; use std::ops::Deref;
use std::sync::atomic::AtomicUsize; use std::sync::atomic::AtomicUsize;
use std::sync::Arc; use std::sync::Arc;
@ -420,6 +421,7 @@ where
variables: request.variables, variables: request.variables,
operation, operation,
fragments: document.fragments, fragments: document.fragments,
uploads: request.uploads,
ctx_data: Arc::new(request.data), ctx_data: Arc::new(request.data),
}; };
Ok((env, cache_control)) Ok((env, cache_control))
@ -544,11 +546,11 @@ where
let is_err = data.is_err(); let is_err = data.is_err();
let extensions = env.extensions.lock().result(&ctx_extension); let extensions = env.extensions.lock().result(&ctx_extension);
yield match data { yield match data {
Ok((name, value)) => Response::new( Ok((name, value)) => {
serde_json::json!({ let mut map = BTreeMap::new();
name: value, map.insert(name, value);
}) Response::new(Value::Object(map))
), },
Err(e) => Response::from_errors(vec![e]), Err(e) => Response::from_errors(vec![e]),
}.extensions(extensions); }.extensions(extensions);
if is_err { if is_err {

View File

@ -1,5 +1,5 @@
use crate::parser::types::{Selection, TypeCondition}; use crate::parser::types::{Name, Selection, TypeCondition};
use crate::{Context, ContextSelectionSet, PathSegment, ServerError, ServerResult, Type}; use crate::{Context, ContextSelectionSet, PathSegment, ServerError, ServerResult, Type, Value};
use futures::{Stream, StreamExt}; use futures::{Stream, StreamExt};
use std::pin::Pin; use std::pin::Pin;
@ -15,11 +15,10 @@ pub trait SubscriptionType: Type {
fn create_field_stream<'a>( fn create_field_stream<'a>(
&'a self, &'a self,
ctx: &'a Context<'a>, ctx: &'a Context<'a>,
) -> Option<Pin<Box<dyn Stream<Item = ServerResult<serde_json::Value>> + Send + 'a>>>; ) -> Option<Pin<Box<dyn Stream<Item = ServerResult<Value>> + Send + 'a>>>;
} }
type BoxFieldStream<'a> = type BoxFieldStream<'a> = Pin<Box<dyn Stream<Item = ServerResult<(Name, Value)>> + 'a + Send>>;
Pin<Box<dyn Stream<Item = ServerResult<(String, serde_json::Value)>> + 'a + Send>>;
pub(crate) fn collect_subscription_streams<'a, T: SubscriptionType + Send + Sync + 'static>( pub(crate) fn collect_subscription_streams<'a, T: SubscriptionType + Send + Sync + 'static>(
ctx: &ContextSelectionSet<'a>, ctx: &ContextSelectionSet<'a>,
@ -40,21 +39,20 @@ pub(crate) fn collect_subscription_streams<'a, T: SubscriptionType + Send + Sync
.node .node
.response_key() .response_key()
.node .node
.as_str(); .clone();
let stream = root.create_field_stream(&ctx); let stream = root.create_field_stream(&ctx);
if let Some(mut stream) = stream { if let Some(mut stream) = stream {
while let Some(item) = stream.next().await { while let Some(item) = stream.next().await {
yield match item { yield match item {
Ok(value) => Ok((field_name.to_owned(), value)), Ok(value) => Ok((field_name.to_owned(), value)),
Err(e) => Err(e.path(PathSegment::Field(field_name.to_owned()))), Err(e) => Err(e.path(PathSegment::Field(field_name.to_string()))),
}; };
} }
} else { } else {
yield Err(ServerError::new(format!(r#"Cannot query field "{}" on type "{}"."#, field_name, T::type_name())) yield Err(ServerError::new(format!(r#"Cannot query field "{}" on type "{}"."#, field_name, T::type_name()))
.at(ctx.item.pos) .at(ctx.item.pos)
.path(PathSegment::Field(field_name.to_owned()))); .path(PathSegment::Field(field_name.to_string())));
return; return;
} }
} }
@ -103,7 +101,7 @@ impl<T: SubscriptionType + Send + Sync> SubscriptionType for &T {
fn create_field_stream<'a>( fn create_field_stream<'a>(
&'a self, &'a self,
ctx: &'a Context<'a>, ctx: &'a Context<'a>,
) -> Option<Pin<Box<dyn Stream<Item = ServerResult<serde_json::Value>> + Send + 'a>>> { ) -> Option<Pin<Box<dyn Stream<Item = ServerResult<Value>> + Send + 'a>>> {
T::create_field_stream(*self, ctx) T::create_field_stream(*self, ctx)
} }
} }

View File

@ -5,7 +5,7 @@ use crate::resolver_utils::{resolve_container, ContainerType};
use crate::types::connection::{CursorType, EmptyFields}; use crate::types::connection::{CursorType, EmptyFields};
use crate::{ use crate::{
registry, Context, ContextSelectionSet, ObjectType, OutputValueType, Positioned, Result, registry, Context, ContextSelectionSet, ObjectType, OutputValueType, Positioned, Result,
ServerResult, Type, ServerResult, Type, Value,
}; };
use futures::{Stream, StreamExt, TryStreamExt}; use futures::{Stream, StreamExt, TryStreamExt};
use indexmap::map::IndexMap; use indexmap::map::IndexMap;
@ -196,7 +196,7 @@ where
EC: ObjectType + Sync + Send, EC: ObjectType + Sync + Send,
EE: ObjectType + Sync + Send, EE: ObjectType + Sync + Send,
{ {
async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<serde_json::Value>> { async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>> {
if ctx.item.node.name.node == "pageInfo" { if ctx.item.node.name.node == "pageInfo" {
let page_info = PageInfo { let page_info = PageInfo {
has_previous_page: self.has_previous_page, has_previous_page: self.has_previous_page,
@ -231,7 +231,7 @@ where
&self, &self,
ctx: &ContextSelectionSet<'_>, ctx: &ContextSelectionSet<'_>,
_field: &Positioned<Field>, _field: &Positioned<Field>,
) -> ServerResult<serde_json::Value> { ) -> ServerResult<Value> {
resolve_container(ctx, self).await resolve_container(ctx, self).await
} }
} }

View File

@ -4,7 +4,7 @@ use crate::resolver_utils::{resolve_container, ContainerType};
use crate::types::connection::CursorType; use crate::types::connection::CursorType;
use crate::{ use crate::{
registry, Context, ContextSelectionSet, ObjectType, OutputValueType, Positioned, ServerResult, registry, Context, ContextSelectionSet, ObjectType, OutputValueType, Positioned, ServerResult,
Type, Type, Value,
}; };
use indexmap::map::IndexMap; use indexmap::map::IndexMap;
use std::borrow::Cow; use std::borrow::Cow;
@ -113,14 +113,14 @@ where
T: OutputValueType + Send + Sync, T: OutputValueType + Send + Sync,
E: ObjectType + Sync + Send, E: ObjectType + Sync + Send,
{ {
async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<serde_json::Value>> { async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>> {
if ctx.item.node.name.node == "node" { if ctx.item.node.name.node == "node" {
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set); let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
return OutputValueType::resolve(&self.node, &ctx_obj, ctx.item) return OutputValueType::resolve(&self.node, &ctx_obj, ctx.item)
.await .await
.map(Some); .map(Some);
} else if ctx.item.node.name.node == "cursor" { } else if ctx.item.node.name.node == "cursor" {
return Ok(Some(self.cursor.encode_cursor().into())); return Ok(Some(Value::String(self.cursor.encode_cursor())));
} }
self.additional_fields.resolve_field(ctx).await self.additional_fields.resolve_field(ctx).await
@ -138,7 +138,7 @@ where
&self, &self,
ctx: &ContextSelectionSet<'_>, ctx: &ContextSelectionSet<'_>,
_field: &Positioned<Field>, _field: &Positioned<Field>,
) -> ServerResult<serde_json::Value> { ) -> ServerResult<Value> {
resolve_container(ctx, self).await resolve_container(ctx, self).await
} }
} }

View File

@ -2,7 +2,7 @@ use crate::parser::types::Field;
use crate::resolver_utils::ContainerType; use crate::resolver_utils::ContainerType;
use crate::{ use crate::{
registry, Context, ContextSelectionSet, ObjectType, OutputValueType, Positioned, ServerError, registry, Context, ContextSelectionSet, ObjectType, OutputValueType, Positioned, ServerError,
ServerResult, Type, ServerResult, Type, Value,
}; };
use std::borrow::Cow; use std::borrow::Cow;
@ -48,7 +48,7 @@ impl ContainerType for EmptyMutation {
true true
} }
async fn resolve_field(&self, _ctx: &Context<'_>) -> ServerResult<Option<serde_json::Value>> { async fn resolve_field(&self, _ctx: &Context<'_>) -> ServerResult<Option<Value>> {
unreachable!() unreachable!()
} }
} }
@ -59,7 +59,7 @@ impl OutputValueType for EmptyMutation {
&self, &self,
_ctx: &ContextSelectionSet<'_>, _ctx: &ContextSelectionSet<'_>,
field: &Positioned<Field>, field: &Positioned<Field>,
) -> ServerResult<serde_json::Value> { ) -> ServerResult<Value> {
Err(ServerError::new("Schema is not configured for mutations.").at(field.pos)) Err(ServerError::new("Schema is not configured for mutations.").at(field.pos))
} }
} }

View File

@ -1,4 +1,4 @@
use crate::{registry, Context, ServerError, ServerResult, SubscriptionType, Type}; use crate::{registry, Context, ServerError, ServerResult, SubscriptionType, Type, Value};
use futures::{stream, Stream}; use futures::{stream, Stream};
use std::borrow::Cow; use std::borrow::Cow;
use std::pin::Pin; use std::pin::Pin;
@ -34,7 +34,7 @@ impl SubscriptionType for EmptySubscription {
fn create_field_stream<'a>( fn create_field_stream<'a>(
&'a self, &'a self,
ctx: &'a Context<'a>, ctx: &'a Context<'a>,
) -> Option<Pin<Box<dyn Stream<Item = ServerResult<serde_json::Value>> + Send + 'a>>> ) -> Option<Pin<Box<dyn Stream<Item = ServerResult<Value>> + Send + 'a>>>
where where
Self: Send + Sync + 'static + Sized, Self: Send + Sync + 'static + Sized,
{ {

View File

@ -1,4 +1,4 @@
use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; use crate::{InputValueError, InputValueResult, Number, Scalar, ScalarType, Value};
/// The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). /// The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).
#[Scalar(internal, name = "Float")] #[Scalar(internal, name = "Float")]
@ -21,7 +21,7 @@ impl ScalarType for f32 {
} }
fn to_value(&self) -> Value { fn to_value(&self) -> Value {
Value::Number(serde_json::Number::from_f64(*self as f64).unwrap()) Value::Number(Number::from_f64(*self as f64).unwrap())
} }
} }

View File

@ -14,7 +14,7 @@ where
match value { match value {
Value::Object(map) => map Value::Object(map) => map
.into_iter() .into_iter()
.map(|(name, value)| Ok((name.into_string(), T::parse(Some(value))?))) .map(|(name, value)| Ok((name.to_string(), T::parse(Some(value))?)))
.collect::<Result<_, _>>() .collect::<Result<_, _>>()
.map_err(InputValueError::propagate), .map_err(InputValueError::propagate),
_ => Err(InputValueError::expected_type(value)), _ => Err(InputValueError::expected_type(value)),
@ -24,9 +24,7 @@ where
fn to_value(&self) -> Value { fn to_value(&self) -> Value {
let mut map = BTreeMap::new(); let mut map = BTreeMap::new();
for (name, value) in self { for (name, value) in self {
if let Ok(name) = Name::new(name.clone()) { map.insert(Name::new(name), value.to_value());
map.insert(name, value.to_value());
}
} }
Value::Object(map) Value::Object(map)
} }

View File

@ -14,7 +14,7 @@ where
match value { match value {
Value::Object(map) => map Value::Object(map) => map
.into_iter() .into_iter()
.map(|(name, value)| Ok((name.into_string(), T::parse(Some(value))?))) .map(|(name, value)| Ok((name.to_string(), T::parse(Some(value))?)))
.collect::<Result<_, _>>() .collect::<Result<_, _>>()
.map_err(InputValueError::propagate), .map_err(InputValueError::propagate),
_ => Err(InputValueError::expected_type(value)), _ => Err(InputValueError::expected_type(value)),
@ -24,9 +24,7 @@ where
fn to_value(&self) -> Value { fn to_value(&self) -> Value {
let mut map = BTreeMap::new(); let mut map = BTreeMap::new();
for (name, value) in self { for (name, value) in self {
if let Ok(name) = Name::new(name.clone()) { map.insert(Name::new(name), value.to_value());
map.insert(name, value.to_value());
}
} }
Value::Object(map) Value::Object(map)
} }

View File

@ -51,7 +51,7 @@ impl<T: OutputValueType + Send + Sync + Ord> OutputValueType for BTreeSet<T> {
&self, &self,
ctx: &ContextSelectionSet<'_>, ctx: &ContextSelectionSet<'_>,
field: &Positioned<Field>, field: &Positioned<Field>,
) -> ServerResult<serde_json::Value> { ) -> ServerResult<Value> {
resolve_list(ctx, field, self).await resolve_list(ctx, field, self).await
} }
} }

View File

@ -53,7 +53,7 @@ impl<T: OutputValueType + Send + Sync + Hash + Eq> OutputValueType for HashSet<T
&self, &self,
ctx: &ContextSelectionSet<'_>, ctx: &ContextSelectionSet<'_>,
field: &Positioned<Field>, field: &Positioned<Field>,
) -> ServerResult<serde_json::Value> { ) -> ServerResult<Value> {
resolve_list(ctx, field, self).await resolve_list(ctx, field, self).await
} }
} }

View File

@ -51,7 +51,7 @@ impl<T: OutputValueType + Send + Sync> OutputValueType for LinkedList<T> {
&self, &self,
ctx: &ContextSelectionSet<'_>, ctx: &ContextSelectionSet<'_>,
field: &Positioned<Field>, field: &Positioned<Field>,
) -> ServerResult<serde_json::Value> { ) -> ServerResult<Value> {
resolve_list(ctx, field, self).await resolve_list(ctx, field, self).await
} }
} }

View File

@ -1,6 +1,8 @@
use crate::parser::types::Field; use crate::parser::types::Field;
use crate::resolver_utils::resolve_list; use crate::resolver_utils::resolve_list;
use crate::{registry, ContextSelectionSet, OutputValueType, Positioned, ServerResult, Type}; use crate::{
registry, ContextSelectionSet, OutputValueType, Positioned, ServerResult, Type, Value,
};
use std::borrow::Cow; use std::borrow::Cow;
impl<'a, T: Type + 'a> Type for &'a [T] { impl<'a, T: Type + 'a> Type for &'a [T] {
@ -24,7 +26,7 @@ impl<T: OutputValueType + Send + Sync> OutputValueType for &[T] {
&self, &self,
ctx: &ContextSelectionSet<'_>, ctx: &ContextSelectionSet<'_>,
field: &Positioned<Field>, field: &Positioned<Field>,
) -> ServerResult<serde_json::Value> { ) -> ServerResult<Value> {
resolve_list(ctx, field, self.iter()).await resolve_list(ctx, field, self.iter()).await
} }
} }

View File

@ -46,7 +46,7 @@ impl<T: OutputValueType + Send + Sync> OutputValueType for Vec<T> {
&self, &self,
ctx: &ContextSelectionSet<'_>, ctx: &ContextSelectionSet<'_>,
field: &Positioned<Field>, field: &Positioned<Field>,
) -> ServerResult<serde_json::Value> { ) -> ServerResult<Value> {
resolve_list(ctx, field, self).await resolve_list(ctx, field, self).await
} }
} }

View File

@ -51,7 +51,7 @@ impl<T: OutputValueType + Send + Sync> OutputValueType for VecDeque<T> {
&self, &self,
ctx: &ContextSelectionSet<'_>, ctx: &ContextSelectionSet<'_>,
field: &Positioned<Field>, field: &Positioned<Field>,
) -> ServerResult<serde_json::Value> { ) -> ServerResult<Value> {
resolve_list(ctx, field, self).await resolve_list(ctx, field, self).await
} }
} }

View File

@ -44,11 +44,11 @@ impl<T: OutputValueType + Sync> OutputValueType for Option<T> {
&self, &self,
ctx: &ContextSelectionSet<'_>, ctx: &ContextSelectionSet<'_>,
field: &Positioned<Field>, field: &Positioned<Field>,
) -> ServerResult<serde_json::Value> { ) -> ServerResult<Value> {
if let Some(inner) = self { if let Some(inner) = self {
OutputValueType::resolve(inner, ctx, field).await OutputValueType::resolve(inner, ctx, field).await
} else { } else {
Ok(serde_json::Value::Null) Ok(Value::Null)
} }
} }
} }

View File

@ -43,7 +43,7 @@ impl<'a> OutputValueType for &'a str {
&self, &self,
_: &ContextSelectionSet<'_>, _: &ContextSelectionSet<'_>,
_field: &Positioned<Field>, _field: &Positioned<Field>,
) -> ServerResult<serde_json::Value> { ) -> ServerResult<Value> {
Ok((*self).into()) Ok(Value::String((*self).to_string()))
} }
} }

View File

@ -7,6 +7,7 @@ use crate::{
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::borrow::Cow; use std::borrow::Cow;
use std::convert::TryInto;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
/// A scalar that can represent any JSON value. /// A scalar that can represent any JSON value.
@ -95,8 +96,11 @@ impl<T: Serialize + Send + Sync> OutputValueType for OutputJson<T> {
&self, &self,
_ctx: &ContextSelectionSet<'_>, _ctx: &ContextSelectionSet<'_>,
_field: &Positioned<Field>, _field: &Positioned<Field>,
) -> ServerResult<serde_json::Value> { ) -> ServerResult<Value> {
Ok(serde_json::to_value(&self.0).unwrap_or_else(|_| serde_json::Value::Null)) Ok(serde_json::to_value(&self.0)
.ok()
.and_then(|json| json.try_into().ok())
.unwrap_or_else(|| Value::Null))
} }
} }

View File

@ -3,7 +3,7 @@ use crate::registry::{MetaType, Registry};
use crate::resolver_utils::resolve_container; use crate::resolver_utils::resolve_container;
use crate::{ use crate::{
CacheControl, ContainerType, Context, ContextSelectionSet, ObjectType, OutputValueType, CacheControl, ContainerType, Context, ContextSelectionSet, ObjectType, OutputValueType,
Positioned, ServerResult, SimpleObject, Type, Positioned, ServerResult, SimpleObject, Type, Value,
}; };
use indexmap::IndexMap; use indexmap::IndexMap;
use std::borrow::Cow; use std::borrow::Cow;
@ -61,7 +61,7 @@ where
A: ObjectType + Send + Sync, A: ObjectType + Send + Sync,
B: ObjectType + Send + Sync, B: ObjectType + Send + Sync,
{ {
async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<serde_json::Value>> { async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>> {
match self.0.resolve_field(ctx).await { match self.0.resolve_field(ctx).await {
Ok(Some(value)) => Ok(Some(value)), Ok(Some(value)) => Ok(Some(value)),
Ok(None) => self.1.resolve_field(ctx).await, Ok(None) => self.1.resolve_field(ctx).await,
@ -80,7 +80,7 @@ where
&self, &self,
ctx: &ContextSelectionSet<'_>, ctx: &ContextSelectionSet<'_>,
_field: &Positioned<Field>, _field: &Positioned<Field>,
) -> ServerResult<serde_json::Value> { ) -> ServerResult<Value> {
resolve_container(ctx, self).await resolve_container(ctx, self).await
} }
} }

View File

@ -25,6 +25,6 @@ pub use maybe_undefined::MaybeUndefined;
pub use merged_object::{MergedObject, MergedObjectTail}; pub use merged_object::{MergedObject, MergedObjectTail};
#[cfg(feature = "string_number")] #[cfg(feature = "string_number")]
pub use string_number::StringNumber; pub use string_number::StringNumber;
pub use upload::Upload; pub use upload::{Upload, UploadValue};
pub(crate) use query_root::QueryRoot; pub(crate) use query_root::QueryRoot;

View File

@ -3,7 +3,7 @@ use crate::parser::types::Field;
use crate::resolver_utils::{resolve_container, ContainerType}; use crate::resolver_utils::{resolve_container, ContainerType};
use crate::{ use crate::{
registry, Any, Context, ContextSelectionSet, ObjectType, OutputValueType, Positioned, registry, Any, Context, ContextSelectionSet, ObjectType, OutputValueType, Positioned,
ServerError, ServerResult, SimpleObject, Type, ServerError, ServerResult, SimpleObject, Type, Value,
}; };
use indexmap::map::IndexMap; use indexmap::map::IndexMap;
@ -81,7 +81,7 @@ impl<T: Type> Type for QueryRoot<T> {
#[async_trait::async_trait] #[async_trait::async_trait]
impl<T: ObjectType + Send + Sync> ContainerType for QueryRoot<T> { impl<T: ObjectType + Send + Sync> ContainerType for QueryRoot<T> {
async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<serde_json::Value>> { async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>> {
if ctx.item.node.name.node == "__schema" { if ctx.item.node.name.node == "__schema" {
if self.disable_introspection { if self.disable_introspection {
return Ok(None); return Ok(None);
@ -122,7 +122,7 @@ impl<T: ObjectType + Send + Sync> ContainerType for QueryRoot<T> {
.ok_or_else(|| ServerError::new("Entity not found.").at(ctx.item.pos))?, .ok_or_else(|| ServerError::new("Entity not found.").at(ctx.item.pos))?,
); );
} }
return Ok(Some(res.into())); return Ok(Some(Value::List(res)));
} else if ctx.item.node.name.node == "_service" { } else if ctx.item.node.name.node == "_service" {
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set); let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
return OutputValueType::resolve( return OutputValueType::resolve(
@ -146,7 +146,7 @@ impl<T: ObjectType + Send + Sync> OutputValueType for QueryRoot<T> {
&self, &self,
ctx: &ContextSelectionSet<'_>, ctx: &ContextSelectionSet<'_>,
_field: &Positioned<Field>, _field: &Positioned<Field>,
) -> ServerResult<serde_json::Value> { ) -> ServerResult<Value> {
resolve_container(ctx, self).await resolve_container(ctx, self).await
} }
} }

View File

@ -1,8 +1,54 @@
use crate::parser::types::UploadValue; use crate::{registry, Context, InputValueError, InputValueResult, InputValueType, Type, Value};
use crate::{registry, InputValueError, InputValueResult, InputValueType, Type, Value}; use futures::AsyncRead;
use std::borrow::Cow; use std::borrow::Cow;
use std::fs::File;
use std::io::Read; use std::io::Read;
/// A file upload value.
pub struct UploadValue {
/// The name of the file.
pub filename: String,
/// The content type of the file.
pub content_type: Option<String>,
/// The file data.
pub content: File,
}
impl UploadValue {
/// Attempt to clone the upload value. This type's `Clone` implementation simply calls this and
/// panics on failure.
///
/// # Errors
///
/// Fails if cloning the inner `File` fails.
pub fn try_clone(&self) -> std::io::Result<Self> {
Ok(Self {
filename: self.filename.clone(),
content_type: self.content_type.clone(),
content: self.content.try_clone()?,
})
}
/// Convert to a `Read`.
///
/// **Note**: this is a *synchronous/blocking* reader.
pub fn into_read(self) -> impl Read + Sync + Send + 'static {
self.content
}
#[cfg(feature = "unblock")]
#[cfg_attr(feature = "nightly", doc(cfg(feature = "unblock")))]
/// Convert to a `AsyncRead`.
pub fn into_async_read(self) -> impl AsyncRead + Sync + Send + 'static {
blocking::Unblock::new(self.content)
}
/// Returns the size of the file, in bytes.
pub fn size(&self) -> std::io::Result<u64> {
self.content.metadata().map(|meta| meta.len())
}
}
/// Uploaded file /// Uploaded file
/// ///
/// **Reference:** <https://github.com/jaydenseric/graphql-multipart-request-spec> /// **Reference:** <https://github.com/jaydenseric/graphql-multipart-request-spec>
@ -23,8 +69,8 @@ use std::io::Read;
/// ///
/// #[Object] /// #[Object]
/// impl MutationRoot { /// impl MutationRoot {
/// async fn upload(&self, file: Upload) -> bool { /// async fn upload(&self, ctx: &Context<'_>, file: Upload) -> bool {
/// println!("upload: filename={}", file.filename()); /// println!("upload: filename={}", file.value(ctx).unwrap().filename);
/// true /// true
/// } /// }
/// } /// }
@ -42,36 +88,12 @@ use std::io::Read;
/// --form 'map={ "0": ["variables.file"] }' \ /// --form 'map={ "0": ["variables.file"] }' \
/// --form '0=@myFile.txt' /// --form '0=@myFile.txt'
/// ``` /// ```
pub struct Upload(UploadValue); pub struct Upload(usize);
impl Upload { impl Upload {
/// Filename /// Get the upload value.
pub fn filename(&self) -> &str { pub fn value(&self, ctx: &Context<'_>) -> std::io::Result<UploadValue> {
self.0.filename.as_str() ctx.query_env.uploads[self.0].try_clone()
}
/// Content type, such as `application/json`, `image/jpg` ...
pub fn content_type(&self) -> Option<&str> {
self.0.content_type.as_deref()
}
/// Returns the size of the file, in bytes.
pub fn size(&self) -> std::io::Result<u64> {
self.0.content.metadata().map(|meta| meta.len())
}
/// Convert to a `Read`.
///
/// **Note**: this is a *synchronous/blocking* reader.
pub fn into_read(self) -> impl Read + Sync + Send + 'static {
self.0.content
}
#[cfg(feature = "unblock")]
#[cfg_attr(feature = "nightly", doc(cfg(feature = "unblock")))]
/// Convert to a `AsyncRead`.
pub fn into_async_read(self) -> impl futures::AsyncRead + Sync + Send + 'static {
blocking::Unblock::new(self.0.content)
} }
} }
@ -84,19 +106,21 @@ impl Type for Upload {
registry.create_type::<Self, _>(|_| registry::MetaType::Scalar { registry.create_type::<Self, _>(|_| registry::MetaType::Scalar {
name: Self::type_name().to_string(), name: Self::type_name().to_string(),
description: None, description: None,
is_valid: |value| matches!(value, Value::Upload(_)), is_valid: |value| matches!(value, Value::String(_)),
}) })
} }
} }
impl InputValueType for Upload { impl InputValueType for Upload {
fn parse(value: Option<Value>) -> InputValueResult<Self> { fn parse(value: Option<Value>) -> InputValueResult<Self> {
const PREFIX: &str = "#__graphql_file__:";
let value = value.unwrap_or_default(); let value = value.unwrap_or_default();
if let Value::Upload(upload) = value { if let Value::String(s) = &value {
Ok(Upload(upload)) if s.starts_with(PREFIX) {
} else { return Ok(Upload(s[PREFIX.len()..].parse::<usize>().unwrap()));
Err(InputValueError::expected_type(value)) }
} }
Err(InputValueError::expected_type(value))
} }
fn to_value(&self) -> Value { fn to_value(&self) -> Value {

View File

@ -7,26 +7,20 @@ pub async fn test_error_extensions() {
#[Object] #[Object]
impl Query { impl Query {
async fn extend_err(&self) -> Result<i32> { async fn extend_err(&self) -> Result<i32> {
Err("my error".extend_with(|err| { Err("my error".extend_with(|err, e| {
serde_json::json!({ e.set("msg", err.to_string());
"msg":err, e.set("code", 100);
"code":100,
})
})) }))
} }
async fn extend_result(&self) -> Result<i32> { async fn extend_result(&self) -> Result<i32> {
Err(Error::from("my error")) Err(Error::from("my error"))
.extend_err(|_| { .extend_err(|_, e| {
serde_json::json!({ e.set("msg", "my error");
"msg": "my error", e.set("code", 100);
"code": 100
})
}) })
.extend_err(|_| { .extend_err(|_, e| {
serde_json::json!({ e.set("code2", 20);
"code2": 20
})
}) })
} }
} }

View File

@ -126,18 +126,19 @@ pub async fn test_field_features() {
stream stream
.next() .next()
.await .await
.map(|resp| resp.into_result().unwrap().data), .map(|resp| resp.into_result().unwrap().data)
Some(serde_json::json!({ .unwrap(),
serde_json::json!({
"values": 10 "values": 10
})) })
); );
let mut stream = schema.execute_stream("subscription { valuesBson }").boxed(); let mut stream = schema.execute_stream("subscription { valuesBson }").boxed();
assert_eq!( assert_eq!(
stream.next().await.map(|resp| resp.data), stream.next().await.map(|resp| resp.data).unwrap(),
Some(serde_json::json!({ serde_json::json!({
"valuesBson": 10 "valuesBson": 10
})) })
); );
assert_eq!( assert_eq!(

View File

@ -1689,10 +1689,9 @@ pub async fn test_input_validator_variable() {
let validator_length = 6; let validator_length = 6;
for case in &test_cases { for case in &test_cases {
let mut variables = Variables::default(); let mut variables = Variables::default();
variables.0.insert( variables
Name::new("id".to_owned()).unwrap(), .0
Value::String(case.to_string()), .insert(Name::new("id"), Value::String(case.to_string()));
);
let field_query = "query($id: String!) {fieldParameter(id: $id)}"; let field_query = "query($id: String!) {fieldParameter(id: $id)}";
let object_query = "query($id: String!) {inputObject(input: {id: $id})}"; let object_query = "query($id: String!) {inputObject(input: {id: $id})}";

View File

@ -86,11 +86,11 @@ pub async fn test_list_type() {
); );
let mut res = schema.execute(&query).await.data; let mut res = schema.execute(&query).await.data;
if let serde_json::Value::Object(obj) = &mut res { if let Value::Object(obj) = &mut res {
if let Some(value_hash_set) = obj.get_mut("valueHashSet") { if let Some(value_hash_set) = obj.get_mut("valueHashSet") {
if let serde_json::Value::Array(array) = value_hash_set { if let Value::List(array) = value_hash_set {
array.sort_by(|a, b| { array.sort_by(|a, b| {
if let (serde_json::Value::Number(a), serde_json::Value::Number(b)) = (a, b) { if let (Value::Number(a), Value::Number(b)) = (a, b) {
if let (Some(a), Some(b)) = (a.as_i64(), b.as_i64()) { if let (Some(a), Some(b)) = (a.as_i64(), b.as_i64()) {
return a.cmp(&b); return a.cmp(&b);
} }

View File

@ -196,10 +196,10 @@ pub async fn test_merged_subscription() {
.boxed(); .boxed();
for i in 0i32..10 { for i in 0i32..10 {
assert_eq!( assert_eq!(
Some(serde_json::json!({ serde_json::json!({
"events1": i, "events1": i,
})), }),
stream.next().await stream.next().await.unwrap()
); );
} }
assert!(stream.next().await.is_none()); assert!(stream.next().await.is_none());
@ -212,10 +212,10 @@ pub async fn test_merged_subscription() {
.boxed(); .boxed();
for i in 10i32..20 { for i in 10i32..20 {
assert_eq!( assert_eq!(
Some(serde_json::json!({ serde_json::json!({
"events2": i, "events2": i,
})), }),
stream.next().await stream.next().await.unwrap()
); );
} }
assert!(stream.next().await.is_none()); assert!(stream.next().await.is_none());

View File

@ -68,8 +68,8 @@ pub async fn test_input_value_custom_error() {
.boxed(); .boxed();
for i in 0..10 { for i in 0..10 {
assert_eq!( assert_eq!(
Some(Ok(serde_json::json!({ "type": i }))), serde_json::json!({ "type": i }),
stream.next().await stream.next().await.unwrap().unwrap()
); );
} }
assert!(stream.next().await.is_none()); assert!(stream.next().await.is_none());

View File

@ -36,8 +36,8 @@ pub async fn test_subscription() {
.boxed(); .boxed();
for i in 10..20 { for i in 10..20 {
assert_eq!( assert_eq!(
Some(serde_json::json!({ "values": i })), serde_json::json!({ "values": i }),
stream.next().await stream.next().await.unwrap()
); );
} }
assert!(stream.next().await.is_none()); assert!(stream.next().await.is_none());
@ -50,8 +50,8 @@ pub async fn test_subscription() {
.boxed(); .boxed();
for i in 10..20 { for i in 10..20 {
assert_eq!( assert_eq!(
Some(serde_json::json!({ "events": {"a": i, "b": i * 10} })), serde_json::json!({ "events": {"a": i, "b": i * 10} }),
stream.next().await stream.next().await.unwrap()
); );
} }
assert!(stream.next().await.is_none()); assert!(stream.next().await.is_none());
@ -96,12 +96,12 @@ pub async fn test_subscription_with_ctx_data() {
.map(|resp| resp.data) .map(|resp| resp.data)
.boxed(); .boxed();
assert_eq!( assert_eq!(
Some(serde_json::json!({ "values": 100 })), serde_json::json!({ "values": 100 }),
stream.next().await stream.next().await.unwrap()
); );
assert_eq!( assert_eq!(
Some(serde_json::json!({ "objects": { "value": 100 } })), serde_json::json!({ "objects": { "value": 100 } }),
stream.next().await stream.next().await.unwrap()
); );
assert!(stream.next().await.is_none()); assert!(stream.next().await.is_none());
} }
@ -138,8 +138,8 @@ pub async fn test_subscription_with_token() {
.map(|resp| resp.into_result().unwrap().data) .map(|resp| resp.into_result().unwrap().data)
.boxed(); .boxed();
assert_eq!( assert_eq!(
Some(serde_json::json!({ "values": 100 })), serde_json::json!({ "values": 100 }),
stream.next().await stream.next().await.unwrap()
); );
assert!(stream.next().await.is_none()); assert!(stream.next().await.is_none());
} }
@ -197,8 +197,8 @@ pub async fn test_subscription_inline_fragment() {
.boxed(); .boxed();
for i in 10..20 { for i in 10..20 {
assert_eq!( assert_eq!(
Some(serde_json::json!({ "events": {"a": i, "b": i * 10} })), serde_json::json!({ "events": {"a": i, "b": i * 10} }),
stream.next().await stream.next().await.unwrap()
); );
} }
assert!(stream.next().await.is_none()); assert!(stream.next().await.is_none());
@ -252,8 +252,8 @@ pub async fn test_subscription_fragment() {
.boxed(); .boxed();
for i in 10i32..20 { for i in 10i32..20 {
assert_eq!( assert_eq!(
Some(serde_json::json!({ "events": {"a": i, "b": i * 10} })), serde_json::json!({ "events": {"a": i, "b": i * 10} }),
stream.next().await stream.next().await.unwrap()
); );
} }
assert!(stream.next().await.is_none()); assert!(stream.next().await.is_none());
@ -308,8 +308,8 @@ pub async fn test_subscription_fragment2() {
.boxed(); .boxed();
for i in 10..20 { for i in 10..20 {
assert_eq!( assert_eq!(
Some(serde_json::json!({ "events": {"a": i, "b": i * 10} })), serde_json::json!({ "events": {"a": i, "b": i * 10} }),
stream.next().await stream.next().await.unwrap()
); );
} }
assert!(stream.next().await.is_none()); assert!(stream.next().await.is_none());
@ -354,8 +354,8 @@ pub async fn test_subscription_error() {
.boxed(); .boxed();
for i in 0i32..5 { for i in 0i32..5 {
assert_eq!( assert_eq!(
Some(Ok(serde_json::json!({ "events": { "value": i } }))), serde_json::json!({ "events": { "value": i } }),
stream.next().await stream.next().await.unwrap().unwrap()
); );
} }
assert_eq!( assert_eq!(
@ -405,8 +405,8 @@ pub async fn test_subscription_fieldresult() {
.boxed(); .boxed();
for i in 0i32..5 { for i in 0i32..5 {
assert_eq!( assert_eq!(
Some(Ok(serde_json::json!({ "values": i }))), serde_json::json!({ "values": i }),
stream.next().await stream.next().await.unwrap().unwrap()
); );
} }
assert_eq!( assert_eq!(

View File

@ -1,4 +1,5 @@
use async_graphql::*; use async_graphql::*;
use std::collections::HashMap;
#[async_std::test] #[async_std::test]
pub async fn test_variables() { pub async fn test_variables() {
@ -271,3 +272,34 @@ pub async fn test_variables_enum() {
}) })
); );
} }
#[async_std::test]
pub async fn test_variables_json() {
struct QueryRoot;
#[Object]
impl QueryRoot {
pub async fn value(&self, value: Json<HashMap<String, i32>>) -> i32 {
*value.get("a-b").unwrap()
}
}
let schema = Schema::new(QueryRoot, EmptyMutation, EmptySubscription);
let query = Request::new(
r#"
query QueryWithVariables($value: JSON) {
value(value: $value)
}
"#,
)
.variables(Variables::from_json(serde_json::json!({
"value": { "a-b": 123 },
})));
assert_eq!(
schema.execute(query).await.into_result().unwrap().data,
serde_json::json!({
"value": 123,
})
);
}