Some improvements.
This commit is contained in:
parent
7fe59d9f90
commit
87ba51fdd0
|
@ -150,8 +150,8 @@ pub fn generate(enum_args: &args::Enum) -> GeneratorResult<TokenStream> {
|
|||
|
||||
#[#crate_name::async_trait::async_trait]
|
||||
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> {
|
||||
Ok(#crate_name::resolver_utils::enum_value(*self).into_json().unwrap())
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -125,7 +125,7 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
|
|||
|
||||
put_fields.push(quote! {
|
||||
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)
|
||||
);
|
||||
});
|
||||
|
|
|
@ -312,7 +312,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
|
|||
#[allow(clippy::all, clippy::pedantic)]
|
||||
#[#crate_name::async_trait::async_trait]
|
||||
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)*
|
||||
Ok(None)
|
||||
}
|
||||
|
@ -327,7 +327,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
|
|||
#[allow(clippy::all, clippy::pedantic)]
|
||||
#[#crate_name::async_trait::async_trait]
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,7 +90,7 @@ pub fn generate(object_args: &args::MergedObject) -> GeneratorResult<TokenStream
|
|||
#[allow(clippy::all, clippy::pedantic)]
|
||||
#[#crate_name::async_trait::async_trait]
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ pub fn generate(object_args: &args::MergedObject) -> GeneratorResult<TokenStream
|
|||
#[allow(clippy::all, clippy::pedantic)]
|
||||
#[#crate_name::async_trait::async_trait]
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ pub fn generate(object_args: &args::MergedSubscription) -> GeneratorResult<Token
|
|||
fn create_field_stream<'a>(
|
||||
&'a self,
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -490,12 +490,12 @@ pub fn generate(
|
|||
#[allow(unused_braces, unused_variables, unused_parens, unused_mut)]
|
||||
#[#crate_name::async_trait::async_trait]
|
||||
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)*
|
||||
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 {
|
||||
#crate_name::Value::Object(params) => params,
|
||||
_ => return Ok(None),
|
||||
|
@ -516,7 +516,7 @@ pub fn generate(
|
|||
#[allow(clippy::all, clippy::pedantic)]
|
||||
#[#crate_name::async_trait::async_trait]
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,8 +64,8 @@ pub fn generate(
|
|||
&self,
|
||||
_: &#crate_name::ContextSelectionSet<'_>,
|
||||
_field: &#crate_name::Positioned<#crate_name::parser::types::Field>
|
||||
) -> #crate_name::ServerResult<#crate_name::serde_json::Value> {
|
||||
Ok(#crate_name::ScalarType::to_value(self).into_json().unwrap())
|
||||
) -> #crate_name::ServerResult<#crate_name::Value> {
|
||||
Ok(#crate_name::ScalarType::to_value(self))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -171,7 +171,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
|
|||
#[#crate_name::async_trait::async_trait]
|
||||
|
||||
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)*
|
||||
Ok(None)
|
||||
}
|
||||
|
@ -181,7 +181,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
|
|||
#[#crate_name::async_trait::async_trait]
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -381,7 +381,7 @@ pub fn generate(
|
|||
fn create_field_stream<'a>(
|
||||
&'a self,
|
||||
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)*
|
||||
None
|
||||
}
|
||||
|
|
|
@ -160,7 +160,7 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
|
|||
#[#crate_name::async_trait::async_trait]
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -174,7 +174,7 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
|
|||
#[allow(clippy::all, clippy::pedantic)]
|
||||
#[#crate_name::async_trait::async_trait]
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,16 +10,14 @@ I would recommend on checking out this [async-graphql example](https://github.co
|
|||
|
||||
## General Concept
|
||||
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
|
||||
field `Option<serde_json::Value>` which - if given some valid `serde_json::Map` - will be exposed as the extensions key to any error.
|
||||
the error message exposed by `std::fmt::Display`. However, `Error` actually provides an additional information that can extend the error.
|
||||
|
||||
A resolver looks like this:
|
||||
|
||||
```rust
|
||||
async fn parse_with_extensions(&self) -> Result<i32> {
|
||||
let my_extension = json!({ "details": "CAN_NOT_FETCH" });
|
||||
Err(Error::new("MyMessage").extend_with(|| my_extension))
|
||||
}
|
||||
async fn parse_with_extensions(&self) -> Result<i32, Error> {
|
||||
Err(Error::new("MyMessage").extend_with(|_, e| e.set("details", "CAN_NOT_FETCH")))
|
||||
}
|
||||
```
|
||||
|
||||
may then return a response like this:
|
||||
|
@ -53,8 +51,8 @@ use std::num::ParseIntError;
|
|||
async fn parse_with_extensions(&self) -> Result<i32> {
|
||||
Ok("234a"
|
||||
.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.
|
||||
|
@ -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.
|
||||
|
||||
```rust
|
||||
use thiserror::Error;
|
||||
#[macro_use]
|
||||
extern crate thiserror;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MyError {
|
||||
|
@ -79,13 +78,11 @@ pub enum MyError {
|
|||
impl ErrorExtensions for MyError {
|
||||
// lets define our base extensions
|
||||
fn extend(&self) -> Error {
|
||||
Error::new(format!("{}", self)).extend_with(|err|
|
||||
Error::new(format!("{}", self)).extend_with(|err, e|
|
||||
match self {
|
||||
MyError::NotFound => json!({"code": "NOT_FOUND"}),
|
||||
MyError::ServerError(reason) => json!({ "reason": reason }),
|
||||
MyError::ErrorWithoutExtensions => {
|
||||
json!("This will be ignored since it does not represent an object.")
|
||||
}
|
||||
MyError::NotFound => e.set("code", "NOT_FOUND"),
|
||||
MyError::ServerError(reason) => e.set("reason", reason),
|
||||
MyError::ErrorWithoutExtensions => {}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +95,7 @@ Or further extend your error through `extend_with`.
|
|||
async fn parse_with_extensions_result(&self) -> Result<i32> {
|
||||
// Err(MyError::NotFound.extend())
|
||||
// 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> {
|
||||
Ok("234a"
|
||||
.parse()
|
||||
.extend_err(|_| json!({"code": 404}))?)
|
||||
}
|
||||
|
||||
.extend_err(|_, e| e.set("code", 404))?)
|
||||
}
|
||||
```
|
||||
### Chained extensions
|
||||
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() {
|
||||
Ok(n) => Ok(n),
|
||||
Err(e) => Err(e
|
||||
.extend_with(|_| json!({"code": 404}))
|
||||
.extend_with(|_| json!({"details": "some more info.."}))
|
||||
.extend_with(|_, e| e.set("code", 404))
|
||||
.extend_with(|_, e| e.set("details", "some more info.."))
|
||||
// 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> {
|
||||
// the trait `error::ErrorExtensions` is not implemented
|
||||
// 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
|
||||
"234a"
|
||||
.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)))
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -12,16 +12,14 @@
|
|||
## 一般概念
|
||||
|
||||
在`Async-graphql`中,所有面向用户的错误都强制转换为`Error`类型,默认情况下会提供
|
||||
由`std:::fmt::Display`暴露的错误消息。但是,`Error`实际上提供了一个额外的
|
||||
字段`Option<serde_json::Value>`,如果它是一个`serde_json::Map`,则可以扩展错误的信息。
|
||||
由`std:::fmt::Display`暴露的错误消息。但是,`Error`实际上提供了一个额外的可以扩展错误的信息。
|
||||
|
||||
Resolver函数类似这样:
|
||||
|
||||
```rust
|
||||
async fn parse_with_extensions(&self) -> Result<i32, Error> {
|
||||
let my_extension = json!({ "details": "CAN_NOT_FETCH" });
|
||||
Err(Error::new("MyMessage").extend_with(|_| my_extension))
|
||||
}
|
||||
Err(Error::new("MyMessage").extend_with(|_, e| e.set("details", "CAN_NOT_FETCH")))
|
||||
}
|
||||
```
|
||||
|
||||
然后可以返回如下响应:
|
||||
|
@ -55,8 +53,8 @@ use std::num::ParseIntError;
|
|||
async fn parse_with_extensions(&self) -> Result<i32> {
|
||||
Ok("234a"
|
||||
.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
|
||||
|
@ -83,13 +81,11 @@ pub enum MyError {
|
|||
impl ErrorExtensions for MyError {
|
||||
// lets define our base extensions
|
||||
fn extend(&self) -> Error {
|
||||
Error::new(format!("{}", self)).extend_with(|err|
|
||||
Error::new(format!("{}", self)).extend_with(|err, e|
|
||||
match self {
|
||||
MyError::NotFound => json!({"code": "NOT_FOUND"}),
|
||||
MyError::ServerError(reason) => json!({ "reason": reason }),
|
||||
MyError::ErrorWithoutExtensions => {
|
||||
json!("This will be ignored since it does not represent an object.")
|
||||
}
|
||||
MyError::NotFound => e.set("code", "NOT_FOUND"),
|
||||
MyError::ServerError(reason) => e.set("reason", reason),
|
||||
MyError::ErrorWithoutExtensions => {}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -101,7 +97,7 @@ impl ErrorExtensions for MyError {
|
|||
async fn parse_with_extensions_result(&self) -> Result<i32> {
|
||||
// Err(MyError::NotFound.extend())
|
||||
// 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> {
|
||||
Ok("234a"
|
||||
.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() {
|
||||
Ok(n) => Ok(n),
|
||||
Err(e) => Err(e
|
||||
.extend_with(|_| json!({"code": 404}))
|
||||
.extend_with(|_| json!({"details": "some more info.."}))
|
||||
.extend_with(|_, e| e.set("code", 404))
|
||||
.extend_with(|_, e| e.set("details", "some more info.."))
|
||||
// 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> {
|
||||
// the trait `error::ErrorExtensions` is not implemented
|
||||
// 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
|
||||
"234a"
|
||||
.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)))
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -188,17 +188,24 @@ fn upload() -> Result<()> {
|
|||
struct MutationRoot;
|
||||
#[Object]
|
||||
impl MutationRoot {
|
||||
async fn single_upload(&self, file: Upload) -> FileInfo {
|
||||
println!("single_upload: filename={}", file.filename());
|
||||
println!("single_upload: content_type={:?}", file.content_type());
|
||||
async fn single_upload(&self, ctx: &Context<'_>, file: Upload) -> FileInfo {
|
||||
let upload_value = file.value(ctx).unwrap();
|
||||
println!("single_upload: filename={}", upload_value.filename);
|
||||
println!(
|
||||
"single_upload: content_type={:?}",
|
||||
upload_value.content_type
|
||||
);
|
||||
|
||||
let file_info = FileInfo {
|
||||
filename: file.filename().into(),
|
||||
mime_type: file.content_type().map(ToString::to_string),
|
||||
filename: upload_value.filename.clone(),
|
||||
mime_type: upload_value.content_type.clone(),
|
||||
};
|
||||
|
||||
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());
|
||||
|
||||
file_info
|
||||
|
|
|
@ -309,8 +309,5 @@ fn parse_arguments(
|
|||
|
||||
fn parse_name(pair: Pair<Rule>, pc: &mut PositionCalculator) -> Result<Positioned<Name>> {
|
||||
debug_assert_eq!(pair.as_rule(), Rule::name);
|
||||
Ok(Positioned::new(
|
||||
Name::new_unchecked(pair.as_str().to_owned()),
|
||||
pc.step(&pair),
|
||||
))
|
||||
Ok(Positioned::new(Name::new(pair.as_str()), pc.step(&pair)))
|
||||
}
|
||||
|
|
|
@ -7,19 +7,20 @@
|
|||
//! This follows the [June 2018 edition of the GraphQL spec](https://spec.graphql.org/June2018/).
|
||||
|
||||
use crate::pos::Positioned;
|
||||
use serde::de::{Deserializer, Error as _, Unexpected};
|
||||
use serde::de::Deserializer;
|
||||
use serde::ser::{Error as _, Serializer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::borrow::Borrow;
|
||||
use std::borrow::{Borrow, Cow};
|
||||
use std::collections::{hash_map, BTreeMap, HashMap};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::fmt::{self, Display, Formatter, Write};
|
||||
use std::fs::File;
|
||||
use std::ops::Deref;
|
||||
|
||||
pub use executable::*;
|
||||
pub use serde_json::Number;
|
||||
pub use service::*;
|
||||
use std::iter::FromIterator;
|
||||
use std::sync::Arc;
|
||||
|
||||
mod executable;
|
||||
mod service;
|
||||
|
@ -72,7 +73,7 @@ impl Type {
|
|||
base: if let Some(ty) = ty.strip_prefix('[') {
|
||||
BaseType::List(Box::new(Self::new(ty.strip_suffix(']')?)?))
|
||||
} else {
|
||||
BaseType::Named(Name::new(ty.to_owned()).ok()?)
|
||||
BaseType::Named(Name::new(ty))
|
||||
},
|
||||
nullable,
|
||||
})
|
||||
|
@ -132,9 +133,127 @@ pub enum ConstValue {
|
|||
List(Vec<ConstValue>),
|
||||
/// An object. This is a map of keys to values.
|
||||
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 {
|
||||
|
@ -155,7 +274,6 @@ impl ConstValue {
|
|||
.map(|(key, value)| (key, value.into_value()))
|
||||
.collect(),
|
||||
),
|
||||
Self::Upload(upload) => Value::Upload(upload),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,7 +309,7 @@ impl Display for ConstValue {
|
|||
Self::String(val) => write_quoted(val, f),
|
||||
Self::Boolean(true) => f.write_str("true"),
|
||||
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::List(items) => write_list(items, f),
|
||||
Self::Object(map) => write_object(map, f),
|
||||
|
@ -205,6 +323,7 @@ impl TryFrom<serde_json::Value> for ConstValue {
|
|||
Self::deserialize(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ConstValue> for serde_json::Value {
|
||||
type Error = serde_json::Error;
|
||||
fn try_from(value: ConstValue) -> Result<Self, Self::Error> {
|
||||
|
@ -241,9 +360,6 @@ pub enum Value {
|
|||
List(Vec<Value>),
|
||||
/// An object. This is a map of keys to values.
|
||||
Object(BTreeMap<Name, Value>),
|
||||
/// An uploaded file.
|
||||
#[serde(serialize_with = "fail_serialize_upload", skip_deserializing)]
|
||||
Upload(UploadValue),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
|
@ -277,7 +393,6 @@ impl Value {
|
|||
.map(|(key, value)| Ok((key, value.into_const_with_mut(f)?)))
|
||||
.collect::<Result<_, _>>()?,
|
||||
),
|
||||
Self::Upload(upload) => ConstValue::Upload(upload),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -322,7 +437,7 @@ impl Display for Value {
|
|||
Self::String(val) => write_quoted(val, f),
|
||||
Self::Boolean(true) => f.write_str("true"),
|
||||
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::List(items) => write_list(items, 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> {
|
||||
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 {
|
||||
f.write_char('"')?;
|
||||
|
@ -390,51 +502,6 @@ fn write_object<K: Display, V: Display>(
|
|||
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
|
||||
/// from [`Directive`](struct.Directive.html) in that it uses [`ConstValue`](enum.ConstValue.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
|
||||
/// is a valid GraphQL name (follows the regex `[_A-Za-z][_0-9A-Za-z]*`).
|
||||
/// A GraphQL name.
|
||||
///
|
||||
/// [Reference](https://spec.graphql.org/June2018/#Name).
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct Name(String);
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Name(Arc<str>);
|
||||
|
||||
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 {
|
||||
/// Check whether the name is valid (follows the regex `[_A-Za-z][_0-9A-Za-z]*`).
|
||||
#[must_use]
|
||||
pub fn is_valid(name: &str) -> bool {
|
||||
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)
|
||||
}
|
||||
/// Create a new name.
|
||||
pub fn new(name: &str) -> Self {
|
||||
Self(name.into())
|
||||
}
|
||||
|
||||
/// Get the name as a string.
|
||||
|
@ -558,12 +599,6 @@ impl Name {
|
|||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Convert the name to a `String`.
|
||||
#[must_use]
|
||||
pub fn into_string(self) -> String {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
type Target = str;
|
||||
|
||||
|
@ -600,17 +629,17 @@ impl Display for Name {
|
|||
|
||||
impl PartialEq<String> for Name {
|
||||
fn eq(&self, other: &String) -> bool {
|
||||
self.0 == *other
|
||||
self.as_str() == other
|
||||
}
|
||||
}
|
||||
impl PartialEq<str> for Name {
|
||||
fn eq(&self, other: &str) -> bool {
|
||||
self.0 == other
|
||||
self.as_str() == other
|
||||
}
|
||||
}
|
||||
impl PartialEq<Name> for String {
|
||||
fn eq(&self, other: &Name) -> bool {
|
||||
other == self
|
||||
self == other.as_str()
|
||||
}
|
||||
}
|
||||
impl PartialEq<Name> for str {
|
||||
|
@ -631,18 +660,8 @@ impl<'a> PartialEq<Name> for &'a str {
|
|||
|
||||
impl<'de> Deserialize<'de> for Name {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
Self::new(String::deserialize(deserializer)?)
|
||||
.map_err(|s| D::Error::invalid_value(Unexpected::Str(&s), &"a GraphQL name"))
|
||||
Ok(Self(
|
||||
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"));
|
||||
}
|
||||
|
|
|
@ -41,12 +41,12 @@ pub trait InputValueType: Type + Sized {
|
|||
/// Represents a GraphQL output value
|
||||
#[async_trait::async_trait]
|
||||
pub trait OutputValueType: Type {
|
||||
/// Resolve an output value to `serde_json::Value`.
|
||||
/// Resolve an output value to `async_graphql::Value`.
|
||||
async fn resolve(
|
||||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<serde_json::Value>;
|
||||
) -> ServerResult<Value>;
|
||||
}
|
||||
|
||||
impl<T: Type + Send + Sync> Type for &T {
|
||||
|
@ -66,7 +66,7 @@ impl<T: OutputValueType + Send + Sync> OutputValueType for &T {
|
|||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<serde_json::Value> {
|
||||
) -> ServerResult<Value> {
|
||||
T::resolve(*self, ctx, field).await
|
||||
}
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ impl<T: OutputValueType + Sync> OutputValueType for Result<T> {
|
|||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<serde_json::Value> {
|
||||
) -> ServerResult<Value> {
|
||||
match self {
|
||||
Ok(value) => Ok(value.resolve(ctx, field).await?),
|
||||
Err(err) => Err(err.clone().into_server_error().at(field.pos)),
|
||||
|
|
|
@ -5,7 +5,8 @@ use crate::parser::types::{
|
|||
};
|
||||
use crate::schema::SchemaEnv;
|
||||
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 serde::ser::{SerializeSeq, Serializer};
|
||||
|
@ -248,6 +249,7 @@ pub struct QueryEnvInner {
|
|||
pub variables: Variables,
|
||||
pub operation: Positioned<OperationDefinition>,
|
||||
pub fragments: HashMap<Name, Positioned<FragmentDefinition>>,
|
||||
pub uploads: Vec<UploadValue>,
|
||||
pub ctx_data: Arc<Data>,
|
||||
}
|
||||
|
||||
|
|
105
src/error.rs
105
src/error.rs
|
@ -1,9 +1,22 @@
|
|||
use crate::{parser, InputValueType, Pos, Value};
|
||||
use serde::Serialize;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::marker::PhantomData;
|
||||
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.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct ServerError {
|
||||
|
@ -16,8 +29,15 @@ pub struct ServerError {
|
|||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub path: Vec<PathSegment>,
|
||||
/// Extensions to the error.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub extensions: Option<serde_json::Map<String, serde_json::Value>>,
|
||||
#[serde(skip_serializing_if = "error_extensions_is_empty")]
|
||||
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 {
|
||||
|
@ -152,8 +172,8 @@ pub struct Error {
|
|||
/// The error message.
|
||||
pub message: String,
|
||||
/// Extensions to the error.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub extensions: Option<serde_json::Map<String, serde_json::Value>>,
|
||||
#[serde(skip_serializing_if = "error_extensions_is_empty")]
|
||||
pub extensions: Option<ErrorExtensionValues>,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
|
@ -189,55 +209,6 @@ impl<T: Display> From<T> for Error {
|
|||
/// An alias for `Result<T, Error>`.
|
||||
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.
|
||||
#[derive(Debug, Error)]
|
||||
#[non_exhaustive]
|
||||
|
@ -305,24 +276,14 @@ pub trait ErrorExtensions: Sized {
|
|||
/// Add extensions to the error, using a callback to make the extensions.
|
||||
fn extend_with<C>(self, cb: C) -> Error
|
||||
where
|
||||
C: FnOnce(&Self) -> serde_json::Value,
|
||||
C: FnOnce(&Self, &mut ErrorExtensionValues),
|
||||
{
|
||||
let message = self.extend().message;
|
||||
match cb(&self) {
|
||||
serde_json::Value::Object(cb_res) => {
|
||||
let extensions = match self.extend().extensions {
|
||||
Some(mut extensions) => {
|
||||
extensions.extend(cb_res);
|
||||
extensions
|
||||
}
|
||||
None => cb_res.into_iter().collect(),
|
||||
};
|
||||
Error {
|
||||
message,
|
||||
extensions: Some(extensions),
|
||||
}
|
||||
}
|
||||
_ => panic!("Extend must be called with a map"),
|
||||
let mut extensions = self.extend().extensions.unwrap_or_default();
|
||||
cb(&self, &mut extensions);
|
||||
Error {
|
||||
message,
|
||||
extensions: Some(extensions),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -349,7 +310,7 @@ pub trait ResultExt<T, E>: Sized {
|
|||
/// Extend the error value of the result with the callback.
|
||||
fn extend_err<C>(self, cb: C) -> Result<T>
|
||||
where
|
||||
C: FnOnce(&E) -> serde_json::Value;
|
||||
C: FnOnce(&E, &mut ErrorExtensionValues);
|
||||
|
||||
/// Extend the result to a `Result`.
|
||||
fn extend(self) -> Result<T>;
|
||||
|
@ -363,10 +324,10 @@ where
|
|||
{
|
||||
fn extend_err<C>(self, cb: C) -> Result<T>
|
||||
where
|
||||
C: FnOnce(&E) -> serde_json::Value,
|
||||
C: FnOnce(&E, &mut ErrorExtensionValues),
|
||||
{
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use crate::extensions::{Extension, ExtensionContext, ExtensionFactory, ResolveInfo};
|
||||
use crate::Variables;
|
||||
use crate::{Value, Variables};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::ser::SerializeMap;
|
||||
use serde::{Serialize, Serializer};
|
||||
use std::collections::BTreeMap;
|
||||
use std::convert::TryInto;
|
||||
use std::ops::Deref;
|
||||
|
||||
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
|
||||
.sort_by(|a, b| a.start_offset.cmp(&b.start_offset));
|
||||
Some(serde_json::json!({
|
||||
|
||||
serde_json::json!({
|
||||
"version": 1,
|
||||
"startTime": self.start_time.to_rfc3339(),
|
||||
"endTime": self.end_time.to_rfc3339(),
|
||||
|
@ -127,6 +129,8 @@ impl Extension for ApolloTracingExtension {
|
|||
"execution": {
|
||||
"resolvers": self.resolves
|
||||
}
|
||||
}))
|
||||
})
|
||||
.try_into()
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,10 @@ pub use self::apollo_tracing::ApolloTracing;
|
|||
pub use self::logger::Logger;
|
||||
#[cfg(feature = "tracing")]
|
||||
pub use self::tracing::Tracing;
|
||||
use crate::parser::types::ExecutableDocument;
|
||||
use crate::Error;
|
||||
use serde_json::Value;
|
||||
use crate::parser::types::{ExecutableDocument, Name};
|
||||
use crate::{Error, Value};
|
||||
use std::any::{Any, TypeId};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
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) {}
|
||||
|
||||
/// Get the results
|
||||
fn result(&mut self, ctx: &ExtensionContext<'_>) -> Option<serde_json::Value> {
|
||||
fn result(&mut self, ctx: &ExtensionContext<'_>) -> Option<Value> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -236,16 +236,16 @@ impl Extension for Extensions {
|
|||
.iter_mut()
|
||||
.filter_map(|e| {
|
||||
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 {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<serde_json::Map<_, _>>();
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
if value.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(value.into())
|
||||
Some(Value::Object(value))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{BatchRequest, ParseRequestError};
|
||||
use crate::{BatchRequest, ParseRequestError, UploadValue};
|
||||
use bytes::Bytes;
|
||||
use futures::io::AsyncRead;
|
||||
use futures::stream::Stream;
|
||||
|
@ -101,15 +101,16 @@ pub(super) async fn receive_batch_multipart(
|
|||
|
||||
for (name, filename, content_type, file) in files {
|
||||
if let Some(var_paths) = map.remove(&name) {
|
||||
let upload = UploadValue {
|
||||
filename,
|
||||
content_type,
|
||||
content: file,
|
||||
};
|
||||
|
||||
for var_path in var_paths {
|
||||
match &mut request {
|
||||
BatchRequest::Single(request) => {
|
||||
request.set_upload(
|
||||
&var_path,
|
||||
filename.clone(),
|
||||
content_type.clone(),
|
||||
file.try_clone().unwrap(),
|
||||
);
|
||||
request.set_upload(&var_path, upload.try_clone()?);
|
||||
}
|
||||
BatchRequest::Batch(requests) => {
|
||||
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(request) = requests.get_mut(idx) {
|
||||
request.set_upload(
|
||||
path,
|
||||
filename.clone(),
|
||||
content_type.clone(),
|
||||
file.try_clone().unwrap(),
|
||||
);
|
||||
request.set_upload(path, upload.try_clone()?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -162,8 +162,8 @@ pub use context::{
|
|||
Context, ContextBase, Data, QueryEnv, QueryPathNode, QueryPathSegment, ResolveId, Variables,
|
||||
};
|
||||
pub use error::{
|
||||
Error, ErrorExtensions, InputValueError, InputValueResult, ParseRequestError, PathSegment,
|
||||
Result, ResultExt, ServerError, ServerResult,
|
||||
Error, ErrorExtensionValues, ErrorExtensions, InputValueError, InputValueResult,
|
||||
ParseRequestError, PathSegment, Result, ResultExt, ServerError, ServerResult,
|
||||
};
|
||||
pub use look_ahead::Lookahead;
|
||||
pub use parser::types::{ConstValue as Value, Number};
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
use crate::parser::types::UploadValue;
|
||||
use crate::{Data, ParseRequestError, Value, Variables};
|
||||
use crate::{Data, ParseRequestError, UploadValue, Value, Variables};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::fs::File;
|
||||
|
||||
/// GraphQL request.
|
||||
///
|
||||
|
@ -25,6 +23,10 @@ pub struct Request {
|
|||
#[serde(default, deserialize_with = "deserialize_variables")]
|
||||
pub variables: Variables,
|
||||
|
||||
/// Uploads
|
||||
#[serde(skip)]
|
||||
pub uploads: Vec<UploadValue>,
|
||||
|
||||
/// The data of the request that can be accessed through `Context::data`.
|
||||
///
|
||||
/// **This data is only valid for this request**
|
||||
|
@ -49,6 +51,7 @@ impl Request {
|
|||
query: query.into(),
|
||||
operation_name: None,
|
||||
variables: Variables::default(),
|
||||
uploads: Vec::default(),
|
||||
data: Data::default(),
|
||||
extensions: Default::default(),
|
||||
}
|
||||
|
@ -79,22 +82,13 @@ impl Request {
|
|||
/// `variables.files.2.content` is equivalent to the Rust code
|
||||
/// `request.variables["files"][2]["content"]`. If no variable exists at the path this function
|
||||
/// won't do anything.
|
||||
pub fn set_upload(
|
||||
&mut self,
|
||||
var_path: &str,
|
||||
filename: String,
|
||||
content_type: Option<String>,
|
||||
content: File,
|
||||
) {
|
||||
pub fn set_upload(&mut self, var_path: &str, upload: UploadValue) {
|
||||
let variable = match self.variables.variable_path(var_path) {
|
||||
Some(variable) => variable,
|
||||
None => return,
|
||||
};
|
||||
*variable = Value::Upload(UploadValue {
|
||||
filename,
|
||||
content_type,
|
||||
content,
|
||||
});
|
||||
self.uploads.push(upload);
|
||||
*variable = Value::String(format!("#__graphql_file__:{}", self.uploads.len() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use crate::extensions::{ErrorLogger, Extension, ExtensionContext, ResolveInfo};
|
||||
use crate::parser::types::Selection;
|
||||
use crate::parser::types::{Name, Selection};
|
||||
use crate::registry::MetaType;
|
||||
use crate::{
|
||||
Context, ContextSelectionSet, OutputValueType, PathSegment, ServerError, ServerResult, Value,
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
|
@ -19,10 +20,10 @@ pub trait ContainerType: OutputValueType {
|
|||
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.
|
||||
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.
|
||||
///
|
||||
|
@ -42,18 +43,14 @@ pub trait ContainerType: OutputValueType {
|
|||
/// Find the GraphQL entity with the given name from the parameter.
|
||||
///
|
||||
/// Objects should override this in case they are the query root.
|
||||
async fn find_entity(
|
||||
&self,
|
||||
_: &Context<'_>,
|
||||
_params: &Value,
|
||||
) -> ServerResult<Option<serde_json::Value>> {
|
||||
async fn find_entity(&self, _: &Context<'_>, _params: &Value) -> ServerResult<Option<Value>> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -64,55 +61,54 @@ impl<T: ContainerType + Send + Sync> ContainerType for &T {
|
|||
pub async fn resolve_container<'a, T: ContainerType + Send + Sync>(
|
||||
ctx: &ContextSelectionSet<'a>,
|
||||
root: &'a T,
|
||||
) -> ServerResult<serde_json::Value> {
|
||||
) -> ServerResult<Value> {
|
||||
let mut fields = Fields(Vec::new());
|
||||
fields.add_set(ctx, root)?;
|
||||
let futures = fields.0;
|
||||
|
||||
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 {
|
||||
if let serde_json::Value::Object(b) = value {
|
||||
if let Some(serde_json::Value::Object(a)) = map.get_mut(&name) {
|
||||
if let Value::Object(b) = value {
|
||||
if let Some(Value::Object(a)) = map.get_mut(&name) {
|
||||
a.extend(b);
|
||||
} else {
|
||||
map.insert(name, b.into());
|
||||
map.insert(name, Value::Object(b));
|
||||
}
|
||||
} else {
|
||||
map.insert(name, value);
|
||||
}
|
||||
}
|
||||
Ok(map.into())
|
||||
Ok(Value::Object(map))
|
||||
}
|
||||
|
||||
/// Resolve an container by executing each of the fields serially.
|
||||
pub async fn resolve_container_serial<'a, T: ContainerType + Send + Sync>(
|
||||
ctx: &ContextSelectionSet<'a>,
|
||||
root: &'a T,
|
||||
) -> ServerResult<serde_json::Value> {
|
||||
) -> ServerResult<Value> {
|
||||
let mut fields = Fields(Vec::new());
|
||||
fields.add_set(ctx, root)?;
|
||||
let futures = fields.0;
|
||||
|
||||
let mut map = serde_json::Map::new();
|
||||
let mut map = BTreeMap::new();
|
||||
for field in futures {
|
||||
let (name, value) = field.await?;
|
||||
|
||||
if let serde_json::Value::Object(b) = value {
|
||||
if let Some(serde_json::Value::Object(a)) = map.get_mut(&name) {
|
||||
if let Value::Object(b) = value {
|
||||
if let Some(Value::Object(a)) = map.get_mut(&name) {
|
||||
a.extend(b);
|
||||
} else {
|
||||
map.insert(name, b.into());
|
||||
map.insert(name, Value::Object(b));
|
||||
}
|
||||
} else {
|
||||
map.insert(name, value);
|
||||
}
|
||||
}
|
||||
Ok(map.into())
|
||||
Ok(Value::Object(map))
|
||||
}
|
||||
|
||||
type BoxFieldFuture<'a> =
|
||||
Pin<Box<dyn Future<Output = ServerResult<(String, serde_json::Value)>> + 'a + Send>>;
|
||||
type BoxFieldFuture<'a> = Pin<Box<dyn Future<Output = ServerResult<(Name, Value)>> + 'a + Send>>;
|
||||
|
||||
/// A set of fields on an container that are being selected.
|
||||
pub struct Fields<'a>(Vec<BoxFieldFuture<'a>>);
|
||||
|
@ -134,17 +130,11 @@ impl<'a> Fields<'a> {
|
|||
if field.node.name.node == "__typename" {
|
||||
// Get the typename
|
||||
let ctx_field = ctx.with_field(field);
|
||||
let field_name = ctx_field
|
||||
.item
|
||||
.node
|
||||
.response_key()
|
||||
.node
|
||||
.clone()
|
||||
.into_string();
|
||||
let field_name = ctx_field.item.node.response_key().node.clone();
|
||||
let typename = root.introspection_type_name().into_owned();
|
||||
|
||||
self.0.push(Box::pin(async move {
|
||||
Ok((field_name, serde_json::Value::String(typename)))
|
||||
Ok((field_name, Value::String(typename)))
|
||||
}));
|
||||
continue;
|
||||
}
|
||||
|
@ -164,13 +154,7 @@ impl<'a> Fields<'a> {
|
|||
let ctx = ctx.clone();
|
||||
async move {
|
||||
let ctx_field = ctx.with_field(field);
|
||||
let field_name = ctx_field
|
||||
.item
|
||||
.node
|
||||
.response_key()
|
||||
.node
|
||||
.clone()
|
||||
.into_string();
|
||||
let field_name = ctx_field.item.node.response_key().node.clone();
|
||||
let ctx_extension = ExtensionContext {
|
||||
schema_data: &ctx.schema_env.data,
|
||||
query_data: &ctx.query_env.ctx_data,
|
||||
|
@ -196,7 +180,7 @@ impl<'a> Fields<'a> {
|
|||
T::type_name()
|
||||
))
|
||||
.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 {
|
||||
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)?;
|
||||
|
||||
|
|
|
@ -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`.
|
||||
pub fn enum_value<T: EnumType>(value: T) -> Value {
|
||||
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))
|
||||
}
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
use crate::extensions::{ErrorLogger, Extension, ExtensionContext, ResolveInfo};
|
||||
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.
|
||||
pub async fn resolve_list<'a, T: OutputValueType + Send + Sync + 'a>(
|
||||
ctx: &ContextSelectionSet<'a>,
|
||||
field: &Positioned<Field>,
|
||||
iter: impl IntoIterator<Item = T>,
|
||||
) -> ServerResult<serde_json::Value> {
|
||||
) -> ServerResult<Value> {
|
||||
let mut futures = Vec::new();
|
||||
|
||||
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?))
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
use crate::{CacheControl, Result, ServerError};
|
||||
use crate::{CacheControl, Result, ServerError, Value};
|
||||
use serde::Serialize;
|
||||
|
||||
/// Query response
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
pub struct Response {
|
||||
/// Data of query result
|
||||
pub data: serde_json::Value,
|
||||
pub data: Value,
|
||||
|
||||
/// Extensions result
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub extensions: Option<serde_json::Value>,
|
||||
pub extensions: Option<Value>,
|
||||
|
||||
/// Cache control value
|
||||
#[serde(skip)]
|
||||
|
@ -23,7 +23,7 @@ pub struct Response {
|
|||
impl Response {
|
||||
/// Create a new successful response with the data.
|
||||
#[must_use]
|
||||
pub fn new(data: impl Into<serde_json::Value>) -> Self {
|
||||
pub fn new(data: impl Into<Value>) -> Self {
|
||||
Self {
|
||||
data: data.into(),
|
||||
..Default::default()
|
||||
|
@ -41,7 +41,7 @@ impl Response {
|
|||
|
||||
/// Set the extensions result of the response.
|
||||
#[must_use]
|
||||
pub fn extensions(self, extensions: Option<serde_json::Value>) -> Self {
|
||||
pub fn extensions(self, extensions: Option<Value>) -> Self {
|
||||
Self { extensions, ..self }
|
||||
}
|
||||
|
||||
|
@ -127,15 +127,15 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
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}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_batch_response_batch() {
|
||||
let resp = BatchResponse::Batch(vec![
|
||||
Response::new(serde_json::Value::Bool(true)),
|
||||
Response::new(serde_json::Value::String("1".to_string())),
|
||||
Response::new(Value::Boolean(true)),
|
||||
Response::new(Value::String("1".to_string())),
|
||||
]);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&resp).unwrap(),
|
||||
|
|
|
@ -10,12 +10,13 @@ use crate::types::QueryRoot;
|
|||
use crate::validation::{check_rules, CheckResult, ValidationMode};
|
||||
use crate::{
|
||||
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 indexmap::map::IndexMap;
|
||||
use itertools::Itertools;
|
||||
use std::any::Any;
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::Deref;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::Arc;
|
||||
|
@ -420,6 +421,7 @@ where
|
|||
variables: request.variables,
|
||||
operation,
|
||||
fragments: document.fragments,
|
||||
uploads: request.uploads,
|
||||
ctx_data: Arc::new(request.data),
|
||||
};
|
||||
Ok((env, cache_control))
|
||||
|
@ -544,11 +546,11 @@ where
|
|||
let is_err = data.is_err();
|
||||
let extensions = env.extensions.lock().result(&ctx_extension);
|
||||
yield match data {
|
||||
Ok((name, value)) => Response::new(
|
||||
serde_json::json!({
|
||||
name: value,
|
||||
})
|
||||
),
|
||||
Ok((name, value)) => {
|
||||
let mut map = BTreeMap::new();
|
||||
map.insert(name, value);
|
||||
Response::new(Value::Object(map))
|
||||
},
|
||||
Err(e) => Response::from_errors(vec![e]),
|
||||
}.extensions(extensions);
|
||||
if is_err {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::parser::types::{Selection, TypeCondition};
|
||||
use crate::{Context, ContextSelectionSet, PathSegment, ServerError, ServerResult, Type};
|
||||
use crate::parser::types::{Name, Selection, TypeCondition};
|
||||
use crate::{Context, ContextSelectionSet, PathSegment, ServerError, ServerResult, Type, Value};
|
||||
use futures::{Stream, StreamExt};
|
||||
use std::pin::Pin;
|
||||
|
||||
|
@ -15,11 +15,10 @@ pub trait SubscriptionType: Type {
|
|||
fn create_field_stream<'a>(
|
||||
&'a self,
|
||||
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> =
|
||||
Pin<Box<dyn Stream<Item = ServerResult<(String, serde_json::Value)>> + 'a + Send>>;
|
||||
type BoxFieldStream<'a> = Pin<Box<dyn Stream<Item = ServerResult<(Name, Value)>> + 'a + Send>>;
|
||||
|
||||
pub(crate) fn collect_subscription_streams<'a, T: SubscriptionType + Send + Sync + 'static>(
|
||||
ctx: &ContextSelectionSet<'a>,
|
||||
|
@ -40,21 +39,20 @@ pub(crate) fn collect_subscription_streams<'a, T: SubscriptionType + Send + Sync
|
|||
.node
|
||||
.response_key()
|
||||
.node
|
||||
.as_str();
|
||||
|
||||
.clone();
|
||||
|
||||
let stream = root.create_field_stream(&ctx);
|
||||
if let Some(mut stream) = stream {
|
||||
while let Some(item) = stream.next().await {
|
||||
yield match item {
|
||||
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 {
|
||||
yield Err(ServerError::new(format!(r#"Cannot query field "{}" on type "{}"."#, field_name, T::type_name()))
|
||||
.at(ctx.item.pos)
|
||||
.path(PathSegment::Field(field_name.to_owned())));
|
||||
.path(PathSegment::Field(field_name.to_string())));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +101,7 @@ impl<T: SubscriptionType + Send + Sync> SubscriptionType for &T {
|
|||
fn create_field_stream<'a>(
|
||||
&'a self,
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::resolver_utils::{resolve_container, ContainerType};
|
|||
use crate::types::connection::{CursorType, EmptyFields};
|
||||
use crate::{
|
||||
registry, Context, ContextSelectionSet, ObjectType, OutputValueType, Positioned, Result,
|
||||
ServerResult, Type,
|
||||
ServerResult, Type, Value,
|
||||
};
|
||||
use futures::{Stream, StreamExt, TryStreamExt};
|
||||
use indexmap::map::IndexMap;
|
||||
|
@ -196,7 +196,7 @@ where
|
|||
EC: 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" {
|
||||
let page_info = PageInfo {
|
||||
has_previous_page: self.has_previous_page,
|
||||
|
@ -231,7 +231,7 @@ where
|
|||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
_field: &Positioned<Field>,
|
||||
) -> ServerResult<serde_json::Value> {
|
||||
) -> ServerResult<Value> {
|
||||
resolve_container(ctx, self).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::resolver_utils::{resolve_container, ContainerType};
|
|||
use crate::types::connection::CursorType;
|
||||
use crate::{
|
||||
registry, Context, ContextSelectionSet, ObjectType, OutputValueType, Positioned, ServerResult,
|
||||
Type,
|
||||
Type, Value,
|
||||
};
|
||||
use indexmap::map::IndexMap;
|
||||
use std::borrow::Cow;
|
||||
|
@ -113,14 +113,14 @@ where
|
|||
T: OutputValueType + Send + Sync,
|
||||
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" {
|
||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||
return OutputValueType::resolve(&self.node, &ctx_obj, ctx.item)
|
||||
.await
|
||||
.map(Some);
|
||||
} 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
|
||||
|
@ -138,7 +138,7 @@ where
|
|||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
_field: &Positioned<Field>,
|
||||
) -> ServerResult<serde_json::Value> {
|
||||
) -> ServerResult<Value> {
|
||||
resolve_container(ctx, self).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::parser::types::Field;
|
|||
use crate::resolver_utils::ContainerType;
|
||||
use crate::{
|
||||
registry, Context, ContextSelectionSet, ObjectType, OutputValueType, Positioned, ServerError,
|
||||
ServerResult, Type,
|
||||
ServerResult, Type, Value,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
|
||||
|
@ -48,7 +48,7 @@ impl ContainerType for EmptyMutation {
|
|||
true
|
||||
}
|
||||
|
||||
async fn resolve_field(&self, _ctx: &Context<'_>) -> ServerResult<Option<serde_json::Value>> {
|
||||
async fn resolve_field(&self, _ctx: &Context<'_>) -> ServerResult<Option<Value>> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ impl OutputValueType for EmptyMutation {
|
|||
&self,
|
||||
_ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<serde_json::Value> {
|
||||
) -> ServerResult<Value> {
|
||||
Err(ServerError::new("Schema is not configured for mutations.").at(field.pos))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 std::borrow::Cow;
|
||||
use std::pin::Pin;
|
||||
|
@ -34,7 +34,7 @@ impl SubscriptionType for EmptySubscription {
|
|||
fn create_field_stream<'a>(
|
||||
&'a self,
|
||||
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
|
||||
Self: Send + Sync + 'static + Sized,
|
||||
{
|
||||
|
|
4
src/types/external/floats.rs
vendored
4
src/types/external/floats.rs
vendored
|
@ -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).
|
||||
#[Scalar(internal, name = "Float")]
|
||||
|
@ -21,7 +21,7 @@ impl ScalarType for f32 {
|
|||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
6
src/types/external/json_object/btreemap.rs
vendored
6
src/types/external/json_object/btreemap.rs
vendored
|
@ -14,7 +14,7 @@ where
|
|||
match value {
|
||||
Value::Object(map) => map
|
||||
.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<_, _>>()
|
||||
.map_err(InputValueError::propagate),
|
||||
_ => Err(InputValueError::expected_type(value)),
|
||||
|
@ -24,9 +24,7 @@ where
|
|||
fn to_value(&self) -> Value {
|
||||
let mut map = BTreeMap::new();
|
||||
for (name, value) in self {
|
||||
if let Ok(name) = Name::new(name.clone()) {
|
||||
map.insert(name, value.to_value());
|
||||
}
|
||||
map.insert(Name::new(name), value.to_value());
|
||||
}
|
||||
Value::Object(map)
|
||||
}
|
||||
|
|
6
src/types/external/json_object/hashmap.rs
vendored
6
src/types/external/json_object/hashmap.rs
vendored
|
@ -14,7 +14,7 @@ where
|
|||
match value {
|
||||
Value::Object(map) => map
|
||||
.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<_, _>>()
|
||||
.map_err(InputValueError::propagate),
|
||||
_ => Err(InputValueError::expected_type(value)),
|
||||
|
@ -24,9 +24,7 @@ where
|
|||
fn to_value(&self) -> Value {
|
||||
let mut map = BTreeMap::new();
|
||||
for (name, value) in self {
|
||||
if let Ok(name) = Name::new(name.clone()) {
|
||||
map.insert(name, value.to_value());
|
||||
}
|
||||
map.insert(Name::new(name), value.to_value());
|
||||
}
|
||||
Value::Object(map)
|
||||
}
|
||||
|
|
2
src/types/external/list/btree_set.rs
vendored
2
src/types/external/list/btree_set.rs
vendored
|
@ -51,7 +51,7 @@ impl<T: OutputValueType + Send + Sync + Ord> OutputValueType for BTreeSet<T> {
|
|||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<serde_json::Value> {
|
||||
) -> ServerResult<Value> {
|
||||
resolve_list(ctx, field, self).await
|
||||
}
|
||||
}
|
||||
|
|
2
src/types/external/list/hash_set.rs
vendored
2
src/types/external/list/hash_set.rs
vendored
|
@ -53,7 +53,7 @@ impl<T: OutputValueType + Send + Sync + Hash + Eq> OutputValueType for HashSet<T
|
|||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<serde_json::Value> {
|
||||
) -> ServerResult<Value> {
|
||||
resolve_list(ctx, field, self).await
|
||||
}
|
||||
}
|
||||
|
|
2
src/types/external/list/linked_list.rs
vendored
2
src/types/external/list/linked_list.rs
vendored
|
@ -51,7 +51,7 @@ impl<T: OutputValueType + Send + Sync> OutputValueType for LinkedList<T> {
|
|||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<serde_json::Value> {
|
||||
) -> ServerResult<Value> {
|
||||
resolve_list(ctx, field, self).await
|
||||
}
|
||||
}
|
||||
|
|
6
src/types/external/list/slice.rs
vendored
6
src/types/external/list/slice.rs
vendored
|
@ -1,6 +1,8 @@
|
|||
use crate::parser::types::Field;
|
||||
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;
|
||||
|
||||
impl<'a, T: Type + 'a> Type for &'a [T] {
|
||||
|
@ -24,7 +26,7 @@ impl<T: OutputValueType + Send + Sync> OutputValueType for &[T] {
|
|||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<serde_json::Value> {
|
||||
) -> ServerResult<Value> {
|
||||
resolve_list(ctx, field, self.iter()).await
|
||||
}
|
||||
}
|
||||
|
|
2
src/types/external/list/vec.rs
vendored
2
src/types/external/list/vec.rs
vendored
|
@ -46,7 +46,7 @@ impl<T: OutputValueType + Send + Sync> OutputValueType for Vec<T> {
|
|||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<serde_json::Value> {
|
||||
) -> ServerResult<Value> {
|
||||
resolve_list(ctx, field, self).await
|
||||
}
|
||||
}
|
||||
|
|
2
src/types/external/list/vec_deque.rs
vendored
2
src/types/external/list/vec_deque.rs
vendored
|
@ -51,7 +51,7 @@ impl<T: OutputValueType + Send + Sync> OutputValueType for VecDeque<T> {
|
|||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<serde_json::Value> {
|
||||
) -> ServerResult<Value> {
|
||||
resolve_list(ctx, field, self).await
|
||||
}
|
||||
}
|
||||
|
|
4
src/types/external/optional.rs
vendored
4
src/types/external/optional.rs
vendored
|
@ -44,11 +44,11 @@ impl<T: OutputValueType + Sync> OutputValueType for Option<T> {
|
|||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
field: &Positioned<Field>,
|
||||
) -> ServerResult<serde_json::Value> {
|
||||
) -> ServerResult<Value> {
|
||||
if let Some(inner) = self {
|
||||
OutputValueType::resolve(inner, ctx, field).await
|
||||
} else {
|
||||
Ok(serde_json::Value::Null)
|
||||
Ok(Value::Null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
4
src/types/external/string.rs
vendored
4
src/types/external/string.rs
vendored
|
@ -43,7 +43,7 @@ impl<'a> OutputValueType for &'a str {
|
|||
&self,
|
||||
_: &ContextSelectionSet<'_>,
|
||||
_field: &Positioned<Field>,
|
||||
) -> ServerResult<serde_json::Value> {
|
||||
Ok((*self).into())
|
||||
) -> ServerResult<Value> {
|
||||
Ok(Value::String((*self).to_string()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use crate::{
|
|||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::borrow::Cow;
|
||||
use std::convert::TryInto;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
/// A scalar that can represent any JSON value.
|
||||
|
@ -95,8 +96,11 @@ impl<T: Serialize + Send + Sync> OutputValueType for OutputJson<T> {
|
|||
&self,
|
||||
_ctx: &ContextSelectionSet<'_>,
|
||||
_field: &Positioned<Field>,
|
||||
) -> ServerResult<serde_json::Value> {
|
||||
Ok(serde_json::to_value(&self.0).unwrap_or_else(|_| serde_json::Value::Null))
|
||||
) -> ServerResult<Value> {
|
||||
Ok(serde_json::to_value(&self.0)
|
||||
.ok()
|
||||
.and_then(|json| json.try_into().ok())
|
||||
.unwrap_or_else(|| Value::Null))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::registry::{MetaType, Registry};
|
|||
use crate::resolver_utils::resolve_container;
|
||||
use crate::{
|
||||
CacheControl, ContainerType, Context, ContextSelectionSet, ObjectType, OutputValueType,
|
||||
Positioned, ServerResult, SimpleObject, Type,
|
||||
Positioned, ServerResult, SimpleObject, Type, Value,
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
use std::borrow::Cow;
|
||||
|
@ -61,7 +61,7 @@ where
|
|||
A: 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 {
|
||||
Ok(Some(value)) => Ok(Some(value)),
|
||||
Ok(None) => self.1.resolve_field(ctx).await,
|
||||
|
@ -80,7 +80,7 @@ where
|
|||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
_field: &Positioned<Field>,
|
||||
) -> ServerResult<serde_json::Value> {
|
||||
) -> ServerResult<Value> {
|
||||
resolve_container(ctx, self).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,6 @@ pub use maybe_undefined::MaybeUndefined;
|
|||
pub use merged_object::{MergedObject, MergedObjectTail};
|
||||
#[cfg(feature = "string_number")]
|
||||
pub use string_number::StringNumber;
|
||||
pub use upload::Upload;
|
||||
pub use upload::{Upload, UploadValue};
|
||||
|
||||
pub(crate) use query_root::QueryRoot;
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::parser::types::Field;
|
|||
use crate::resolver_utils::{resolve_container, ContainerType};
|
||||
use crate::{
|
||||
registry, Any, Context, ContextSelectionSet, ObjectType, OutputValueType, Positioned,
|
||||
ServerError, ServerResult, SimpleObject, Type,
|
||||
ServerError, ServerResult, SimpleObject, Type, Value,
|
||||
};
|
||||
|
||||
use indexmap::map::IndexMap;
|
||||
|
@ -81,7 +81,7 @@ impl<T: Type> Type for QueryRoot<T> {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
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 self.disable_introspection {
|
||||
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))?,
|
||||
);
|
||||
}
|
||||
return Ok(Some(res.into()));
|
||||
return Ok(Some(Value::List(res)));
|
||||
} else if ctx.item.node.name.node == "_service" {
|
||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||
return OutputValueType::resolve(
|
||||
|
@ -146,7 +146,7 @@ impl<T: ObjectType + Send + Sync> OutputValueType for QueryRoot<T> {
|
|||
&self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
_field: &Positioned<Field>,
|
||||
) -> ServerResult<serde_json::Value> {
|
||||
) -> ServerResult<Value> {
|
||||
resolve_container(ctx, self).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,54 @@
|
|||
use crate::parser::types::UploadValue;
|
||||
use crate::{registry, InputValueError, InputValueResult, InputValueType, Type, Value};
|
||||
use crate::{registry, Context, InputValueError, InputValueResult, InputValueType, Type, Value};
|
||||
use futures::AsyncRead;
|
||||
use std::borrow::Cow;
|
||||
use std::fs::File;
|
||||
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
|
||||
///
|
||||
/// **Reference:** <https://github.com/jaydenseric/graphql-multipart-request-spec>
|
||||
|
@ -23,8 +69,8 @@ use std::io::Read;
|
|||
///
|
||||
/// #[Object]
|
||||
/// impl MutationRoot {
|
||||
/// async fn upload(&self, file: Upload) -> bool {
|
||||
/// println!("upload: filename={}", file.filename());
|
||||
/// async fn upload(&self, ctx: &Context<'_>, file: Upload) -> bool {
|
||||
/// println!("upload: filename={}", file.value(ctx).unwrap().filename);
|
||||
/// true
|
||||
/// }
|
||||
/// }
|
||||
|
@ -42,36 +88,12 @@ use std::io::Read;
|
|||
/// --form 'map={ "0": ["variables.file"] }' \
|
||||
/// --form '0=@myFile.txt'
|
||||
/// ```
|
||||
pub struct Upload(UploadValue);
|
||||
pub struct Upload(usize);
|
||||
|
||||
impl Upload {
|
||||
/// Filename
|
||||
pub fn filename(&self) -> &str {
|
||||
self.0.filename.as_str()
|
||||
}
|
||||
|
||||
/// 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)
|
||||
/// Get the upload value.
|
||||
pub fn value(&self, ctx: &Context<'_>) -> std::io::Result<UploadValue> {
|
||||
ctx.query_env.uploads[self.0].try_clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,19 +106,21 @@ impl Type for Upload {
|
|||
registry.create_type::<Self, _>(|_| registry::MetaType::Scalar {
|
||||
name: Self::type_name().to_string(),
|
||||
description: None,
|
||||
is_valid: |value| matches!(value, Value::Upload(_)),
|
||||
is_valid: |value| matches!(value, Value::String(_)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl InputValueType for Upload {
|
||||
fn parse(value: Option<Value>) -> InputValueResult<Self> {
|
||||
const PREFIX: &str = "#__graphql_file__:";
|
||||
let value = value.unwrap_or_default();
|
||||
if let Value::Upload(upload) = value {
|
||||
Ok(Upload(upload))
|
||||
} else {
|
||||
Err(InputValueError::expected_type(value))
|
||||
if let Value::String(s) = &value {
|
||||
if s.starts_with(PREFIX) {
|
||||
return Ok(Upload(s[PREFIX.len()..].parse::<usize>().unwrap()));
|
||||
}
|
||||
}
|
||||
Err(InputValueError::expected_type(value))
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
|
|
|
@ -7,26 +7,20 @@ pub async fn test_error_extensions() {
|
|||
#[Object]
|
||||
impl Query {
|
||||
async fn extend_err(&self) -> Result<i32> {
|
||||
Err("my error".extend_with(|err| {
|
||||
serde_json::json!({
|
||||
"msg":err,
|
||||
"code":100,
|
||||
})
|
||||
Err("my error".extend_with(|err, e| {
|
||||
e.set("msg", err.to_string());
|
||||
e.set("code", 100);
|
||||
}))
|
||||
}
|
||||
|
||||
async fn extend_result(&self) -> Result<i32> {
|
||||
Err(Error::from("my error"))
|
||||
.extend_err(|_| {
|
||||
serde_json::json!({
|
||||
"msg": "my error",
|
||||
"code": 100
|
||||
})
|
||||
.extend_err(|_, e| {
|
||||
e.set("msg", "my error");
|
||||
e.set("code", 100);
|
||||
})
|
||||
.extend_err(|_| {
|
||||
serde_json::json!({
|
||||
"code2": 20
|
||||
})
|
||||
.extend_err(|_, e| {
|
||||
e.set("code2", 20);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,18 +126,19 @@ pub async fn test_field_features() {
|
|||
stream
|
||||
.next()
|
||||
.await
|
||||
.map(|resp| resp.into_result().unwrap().data),
|
||||
Some(serde_json::json!({
|
||||
.map(|resp| resp.into_result().unwrap().data)
|
||||
.unwrap(),
|
||||
serde_json::json!({
|
||||
"values": 10
|
||||
}))
|
||||
})
|
||||
);
|
||||
|
||||
let mut stream = schema.execute_stream("subscription { valuesBson }").boxed();
|
||||
assert_eq!(
|
||||
stream.next().await.map(|resp| resp.data),
|
||||
Some(serde_json::json!({
|
||||
stream.next().await.map(|resp| resp.data).unwrap(),
|
||||
serde_json::json!({
|
||||
"valuesBson": 10
|
||||
}))
|
||||
})
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
|
|
|
@ -1689,10 +1689,9 @@ pub async fn test_input_validator_variable() {
|
|||
let validator_length = 6;
|
||||
for case in &test_cases {
|
||||
let mut variables = Variables::default();
|
||||
variables.0.insert(
|
||||
Name::new("id".to_owned()).unwrap(),
|
||||
Value::String(case.to_string()),
|
||||
);
|
||||
variables
|
||||
.0
|
||||
.insert(Name::new("id"), Value::String(case.to_string()));
|
||||
|
||||
let field_query = "query($id: String!) {fieldParameter(id: $id)}";
|
||||
let object_query = "query($id: String!) {inputObject(input: {id: $id})}";
|
||||
|
|
|
@ -86,11 +86,11 @@ pub async fn test_list_type() {
|
|||
);
|
||||
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 serde_json::Value::Array(array) = value_hash_set {
|
||||
if let Value::List(array) = value_hash_set {
|
||||
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()) {
|
||||
return a.cmp(&b);
|
||||
}
|
||||
|
|
|
@ -196,10 +196,10 @@ pub async fn test_merged_subscription() {
|
|||
.boxed();
|
||||
for i in 0i32..10 {
|
||||
assert_eq!(
|
||||
Some(serde_json::json!({
|
||||
serde_json::json!({
|
||||
"events1": i,
|
||||
})),
|
||||
stream.next().await
|
||||
}),
|
||||
stream.next().await.unwrap()
|
||||
);
|
||||
}
|
||||
assert!(stream.next().await.is_none());
|
||||
|
@ -212,10 +212,10 @@ pub async fn test_merged_subscription() {
|
|||
.boxed();
|
||||
for i in 10i32..20 {
|
||||
assert_eq!(
|
||||
Some(serde_json::json!({
|
||||
serde_json::json!({
|
||||
"events2": i,
|
||||
})),
|
||||
stream.next().await
|
||||
}),
|
||||
stream.next().await.unwrap()
|
||||
);
|
||||
}
|
||||
assert!(stream.next().await.is_none());
|
||||
|
|
|
@ -68,8 +68,8 @@ pub async fn test_input_value_custom_error() {
|
|||
.boxed();
|
||||
for i in 0..10 {
|
||||
assert_eq!(
|
||||
Some(Ok(serde_json::json!({ "type": i }))),
|
||||
stream.next().await
|
||||
serde_json::json!({ "type": i }),
|
||||
stream.next().await.unwrap().unwrap()
|
||||
);
|
||||
}
|
||||
assert!(stream.next().await.is_none());
|
||||
|
|
|
@ -36,8 +36,8 @@ pub async fn test_subscription() {
|
|||
.boxed();
|
||||
for i in 10..20 {
|
||||
assert_eq!(
|
||||
Some(serde_json::json!({ "values": i })),
|
||||
stream.next().await
|
||||
serde_json::json!({ "values": i }),
|
||||
stream.next().await.unwrap()
|
||||
);
|
||||
}
|
||||
assert!(stream.next().await.is_none());
|
||||
|
@ -50,8 +50,8 @@ pub async fn test_subscription() {
|
|||
.boxed();
|
||||
for i in 10..20 {
|
||||
assert_eq!(
|
||||
Some(serde_json::json!({ "events": {"a": i, "b": i * 10} })),
|
||||
stream.next().await
|
||||
serde_json::json!({ "events": {"a": i, "b": i * 10} }),
|
||||
stream.next().await.unwrap()
|
||||
);
|
||||
}
|
||||
assert!(stream.next().await.is_none());
|
||||
|
@ -96,12 +96,12 @@ pub async fn test_subscription_with_ctx_data() {
|
|||
.map(|resp| resp.data)
|
||||
.boxed();
|
||||
assert_eq!(
|
||||
Some(serde_json::json!({ "values": 100 })),
|
||||
stream.next().await
|
||||
serde_json::json!({ "values": 100 }),
|
||||
stream.next().await.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Some(serde_json::json!({ "objects": { "value": 100 } })),
|
||||
stream.next().await
|
||||
serde_json::json!({ "objects": { "value": 100 } }),
|
||||
stream.next().await.unwrap()
|
||||
);
|
||||
assert!(stream.next().await.is_none());
|
||||
}
|
||||
|
@ -138,8 +138,8 @@ pub async fn test_subscription_with_token() {
|
|||
.map(|resp| resp.into_result().unwrap().data)
|
||||
.boxed();
|
||||
assert_eq!(
|
||||
Some(serde_json::json!({ "values": 100 })),
|
||||
stream.next().await
|
||||
serde_json::json!({ "values": 100 }),
|
||||
stream.next().await.unwrap()
|
||||
);
|
||||
assert!(stream.next().await.is_none());
|
||||
}
|
||||
|
@ -197,8 +197,8 @@ pub async fn test_subscription_inline_fragment() {
|
|||
.boxed();
|
||||
for i in 10..20 {
|
||||
assert_eq!(
|
||||
Some(serde_json::json!({ "events": {"a": i, "b": i * 10} })),
|
||||
stream.next().await
|
||||
serde_json::json!({ "events": {"a": i, "b": i * 10} }),
|
||||
stream.next().await.unwrap()
|
||||
);
|
||||
}
|
||||
assert!(stream.next().await.is_none());
|
||||
|
@ -252,8 +252,8 @@ pub async fn test_subscription_fragment() {
|
|||
.boxed();
|
||||
for i in 10i32..20 {
|
||||
assert_eq!(
|
||||
Some(serde_json::json!({ "events": {"a": i, "b": i * 10} })),
|
||||
stream.next().await
|
||||
serde_json::json!({ "events": {"a": i, "b": i * 10} }),
|
||||
stream.next().await.unwrap()
|
||||
);
|
||||
}
|
||||
assert!(stream.next().await.is_none());
|
||||
|
@ -308,8 +308,8 @@ pub async fn test_subscription_fragment2() {
|
|||
.boxed();
|
||||
for i in 10..20 {
|
||||
assert_eq!(
|
||||
Some(serde_json::json!({ "events": {"a": i, "b": i * 10} })),
|
||||
stream.next().await
|
||||
serde_json::json!({ "events": {"a": i, "b": i * 10} }),
|
||||
stream.next().await.unwrap()
|
||||
);
|
||||
}
|
||||
assert!(stream.next().await.is_none());
|
||||
|
@ -354,8 +354,8 @@ pub async fn test_subscription_error() {
|
|||
.boxed();
|
||||
for i in 0i32..5 {
|
||||
assert_eq!(
|
||||
Some(Ok(serde_json::json!({ "events": { "value": i } }))),
|
||||
stream.next().await
|
||||
serde_json::json!({ "events": { "value": i } }),
|
||||
stream.next().await.unwrap().unwrap()
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
|
@ -405,8 +405,8 @@ pub async fn test_subscription_fieldresult() {
|
|||
.boxed();
|
||||
for i in 0i32..5 {
|
||||
assert_eq!(
|
||||
Some(Ok(serde_json::json!({ "values": i }))),
|
||||
stream.next().await
|
||||
serde_json::json!({ "values": i }),
|
||||
stream.next().await.unwrap().unwrap()
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use async_graphql::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[async_std::test]
|
||||
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,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user