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]
|
#[#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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)))
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -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)))
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"));
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)),
|
||||||
|
|
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
105
src/error.rs
105
src/error.rs
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)?;
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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?))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
{
|
{
|
||||||
|
|
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).
|
/// 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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
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 {
|
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)
|
||||||
}
|
}
|
||||||
|
|
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 {
|
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)
|
||||||
}
|
}
|
||||||
|
|
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,
|
&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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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,
|
&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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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,
|
&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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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::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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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,
|
&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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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,
|
&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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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,
|
&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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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,
|
&self,
|
||||||
_: &ContextSelectionSet<'_>,
|
_: &ContextSelectionSet<'_>,
|
||||||
_field: &Positioned<Field>,
|
_field: &Positioned<Field>,
|
||||||
) -> ServerResult<serde_json::Value> {
|
) -> ServerResult<Value> {
|
||||||
Ok((*self).into())
|
Ok(Value::String((*self).to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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!(
|
||||||
|
|
|
@ -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})}";
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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!(
|
||||||
|
|
|
@ -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,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user