Merge branch 'upload-stream'
This commit is contained in:
commit
2d1eb41598
|
@ -35,7 +35,7 @@ chrono = "0.4.10"
|
||||||
slab = "0.4.2"
|
slab = "0.4.2"
|
||||||
once_cell = "1.3.1"
|
once_cell = "1.3.1"
|
||||||
itertools = "0.9.0"
|
itertools = "0.9.0"
|
||||||
tempdir = "0.3.7"
|
tempfile = "3.1.0"
|
||||||
httparse = "1.3.4"
|
httparse = "1.3.4"
|
||||||
mime = "0.3.16"
|
mime = "0.3.16"
|
||||||
http = "0.2.1"
|
http = "0.2.1"
|
||||||
|
|
|
@ -156,7 +156,7 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
|
||||||
}
|
}
|
||||||
|
|
||||||
impl #crate_name::InputValueType for #ident {
|
impl #crate_name::InputValueType for #ident {
|
||||||
fn parse(value: &#crate_name::Value) -> #crate_name::InputValueResult<Self> {
|
fn parse(value: #crate_name::Value) -> #crate_name::InputValueResult<Self> {
|
||||||
#crate_name::EnumType::parse_enum(value)
|
#crate_name::EnumType::parse_enum(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,17 +81,17 @@ pub fn generate(object_args: &args::InputObject, input: &DeriveInput) -> Result<
|
||||||
get_fields.push(quote! {
|
get_fields.push(quote! {
|
||||||
let #ident:#ty = {
|
let #ident:#ty = {
|
||||||
match obj.get(#name) {
|
match obj.get(#name) {
|
||||||
Some(value) => #crate_name::InputValueType::parse(value)?,
|
Some(value) => #crate_name::InputValueType::parse(value.clone())?,
|
||||||
None => {
|
None => {
|
||||||
let default = #default_repr;
|
let default = #default_repr;
|
||||||
#crate_name::InputValueType::parse(&default)?
|
#crate_name::InputValueType::parse(default)?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
get_fields.push(quote! {
|
get_fields.push(quote! {
|
||||||
let #ident:#ty = #crate_name::InputValueType::parse(obj.get(#name).unwrap_or(&#crate_name::Value::Null))?;
|
let #ident:#ty = #crate_name::InputValueType::parse(obj.get(#name).cloned().unwrap_or(#crate_name::Value::Null))?;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,14 +129,14 @@ pub fn generate(object_args: &args::InputObject, input: &DeriveInput) -> Result<
|
||||||
}
|
}
|
||||||
|
|
||||||
impl #crate_name::InputValueType for #ident {
|
impl #crate_name::InputValueType for #ident {
|
||||||
fn parse(value: &#crate_name::Value) -> #crate_name::InputValueResult<Self> {
|
fn parse(value: #crate_name::Value) -> #crate_name::InputValueResult<Self> {
|
||||||
use #crate_name::Type;
|
use #crate_name::Type;
|
||||||
|
|
||||||
if let #crate_name::Value::Object(obj) = value {
|
if let #crate_name::Value::Object(obj) = &value {
|
||||||
#(#get_fields)*
|
#(#get_fields)*
|
||||||
Ok(Self { #(#fields),* })
|
Ok(Self { #(#fields),* })
|
||||||
} else {
|
} else {
|
||||||
Err(#crate_name::InputValueError::ExpectedType)
|
Err(#crate_name::InputValueError::ExpectedType(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
|
||||||
};
|
};
|
||||||
if let Type::Path(p) = &field.ty {
|
if let Type::Path(p) = &field.ty {
|
||||||
// This validates that the field type wasn't already used
|
// This validates that the field type wasn't already used
|
||||||
if enum_items.insert(p) == false {
|
if !enum_items.insert(p) {
|
||||||
return Err(Error::new_spanned(
|
return Err(Error::new_spanned(
|
||||||
field,
|
field,
|
||||||
"This type already used in another variant",
|
"This type already used in another variant",
|
||||||
|
|
|
@ -165,7 +165,7 @@ pub fn Scalar(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl #generic #crate_name::InputValueType for #self_ty #where_clause {
|
impl #generic #crate_name::InputValueType for #self_ty #where_clause {
|
||||||
fn parse(value: &#crate_name::Value) -> #crate_name::InputValueResult<Self> {
|
fn parse(value: #crate_name::Value) -> #crate_name::InputValueResult<Self> {
|
||||||
<#self_ty as #crate_name::ScalarType>::parse(value)
|
<#self_ty as #crate_name::ScalarType>::parse(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,7 +126,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
||||||
});
|
});
|
||||||
key_getter.push(quote! {
|
key_getter.push(quote! {
|
||||||
params.get(#name).and_then(|value| {
|
params.get(#name).and_then(|value| {
|
||||||
let value: Option<#ty> = #crate_name::InputValueType::parse(value).ok();
|
let value: Option<#ty> = #crate_name::InputValueType::parse(value.clone()).ok();
|
||||||
value
|
value
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -61,7 +61,7 @@ pub fn generate(union_args: &args::Interface, input: &DeriveInput) -> Result<Tok
|
||||||
};
|
};
|
||||||
if let Type::Path(p) = &field.ty {
|
if let Type::Path(p) = &field.ty {
|
||||||
// This validates that the field type wasn't already used
|
// This validates that the field type wasn't already used
|
||||||
if enum_items.insert(p) == false {
|
if !enum_items.insert(p) {
|
||||||
return Err(Error::new_spanned(
|
return Err(Error::new_spanned(
|
||||||
field,
|
field,
|
||||||
"This type already used in another variant",
|
"This type already used in another variant",
|
||||||
|
|
|
@ -58,6 +58,7 @@ pub fn build_value_repr(crate_name: &TokenStream, value: &Value) -> TokenStream
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Value::Upload(_) => quote! { #crate_name::Value::Null },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,4 +10,4 @@ mod value;
|
||||||
|
|
||||||
pub use pos::{Pos, Positioned};
|
pub use pos::{Pos, Positioned};
|
||||||
pub use query_parser::{parse_query, parse_value, Error, Result};
|
pub use query_parser::{parse_query, parse_value, Error, Result};
|
||||||
pub use value::Value;
|
pub use value::{UploadValue, Value};
|
||||||
|
|
|
@ -1,5 +1,29 @@
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::fmt::Formatter;
|
||||||
|
use std::fs::File;
|
||||||
|
|
||||||
|
pub struct UploadValue {
|
||||||
|
pub filename: String,
|
||||||
|
pub content_type: Option<String>,
|
||||||
|
pub content: File,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for UploadValue {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "Upload({})", self.filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for UploadValue {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
filename: self.filename.clone(),
|
||||||
|
content_type: self.content_type.clone(),
|
||||||
|
content: self.content.try_clone().unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents a GraphQL value
|
/// Represents a GraphQL value
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -14,6 +38,7 @@ pub enum Value {
|
||||||
Enum(String),
|
Enum(String),
|
||||||
List(Vec<Value>),
|
List(Vec<Value>),
|
||||||
Object(BTreeMap<String, Value>),
|
Object(BTreeMap<String, Value>),
|
||||||
|
Upload(UploadValue),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Value {
|
impl PartialEq for Value {
|
||||||
|
@ -54,6 +79,7 @@ impl PartialEq for Value {
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
(Upload(a), Upload(b)) => a.filename == b.filename,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,6 +138,7 @@ impl fmt::Display for Value {
|
||||||
}
|
}
|
||||||
write!(f, "}}")
|
write!(f, "}}")
|
||||||
}
|
}
|
||||||
|
Value::Upload(_) => write!(f, "null"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,6 +163,7 @@ impl From<Value> for serde_json::Value {
|
||||||
.map(|(name, value)| (name, value.into()))
|
.map(|(name, value)| (name, value.into()))
|
||||||
.collect(),
|
.collect(),
|
||||||
),
|
),
|
||||||
|
Value::Upload(_) => serde_json::Value::Null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
mod test_utils;
|
mod test_utils;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use smol::{Task, Timer};
|
use smol::{Task, Timer};
|
||||||
|
use std::io::Read;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tide::Request;
|
use tide::Request;
|
||||||
|
|
||||||
|
@ -167,21 +168,18 @@ fn upload() -> Result<()> {
|
||||||
#[Object]
|
#[Object]
|
||||||
impl MutationRoot {
|
impl MutationRoot {
|
||||||
async fn single_upload(&self, file: Upload) -> FileInfo {
|
async fn single_upload(&self, file: Upload) -> FileInfo {
|
||||||
println!("single_upload: filename={}", file.filename);
|
println!("single_upload: filename={}", file.filename());
|
||||||
println!("single_upload: content_type={:?}", file.content_type);
|
println!("single_upload: content_type={:?}", file.content_type());
|
||||||
println!("single_upload: path={:?}", file.path);
|
|
||||||
|
|
||||||
let file_path = file.path.clone();
|
|
||||||
let content = Task::blocking(async move { std::fs::read_to_string(file_path) })
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
assert_eq!(content, Some("test\r\n".to_owned()));
|
|
||||||
|
|
||||||
let file_info = FileInfo {
|
let file_info = FileInfo {
|
||||||
filename: file.filename,
|
filename: file.filename().into(),
|
||||||
mime_type: file.content_type,
|
mime_type: file.content_type().map(ToString::to_string),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut content = String::new();
|
||||||
|
file.into_read().read_to_string(&mut content).ok();
|
||||||
|
assert_eq!(content, "test\r\n".to_owned());
|
||||||
|
|
||||||
file_info
|
file_info
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ impl ScalarType for StringNumber {
|
||||||
"StringNumber"
|
"StringNumber"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(value: &Value) -> InputValueResult<Self> {
|
fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
if let Value::String(value) = value {
|
if let Value::String(value) = value {
|
||||||
// Parse the integer value
|
// Parse the integer value
|
||||||
value.parse().map(StringNumber)?
|
value.parse().map(StringNumber)?
|
||||||
|
|
|
@ -19,7 +19,7 @@ impl ScalarType for StringNumber {
|
||||||
"StringNumber"
|
"StringNumber"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(value: &Value) -> InputValueResult<Self> {
|
fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
if let Value::String(value) = value {
|
if let Value::String(value) = value {
|
||||||
// 解析整数
|
// 解析整数
|
||||||
value.parse().map(StringNumber)?
|
value.parse().map(StringNumber)?
|
||||||
|
|
19
src/base.rs
19
src/base.rs
|
@ -52,7 +52,7 @@ pub trait Type {
|
||||||
/// Represents a GraphQL input value
|
/// Represents a GraphQL input value
|
||||||
pub trait InputValueType: Type + Sized {
|
pub trait InputValueType: Type + Sized {
|
||||||
/// Parse from `Value`
|
/// Parse from `Value`
|
||||||
fn parse(value: &Value) -> InputValueResult<Self>;
|
fn parse(value: Value) -> InputValueResult<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a GraphQL output value
|
/// Represents a GraphQL output value
|
||||||
|
@ -128,11 +128,11 @@ pub trait InputObjectType: InputValueType {}
|
||||||
/// "MyInt"
|
/// "MyInt"
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn parse(value: &Value) -> InputValueResult<Self> {
|
/// fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
/// if let Value::Int(n) = value {
|
/// if let Value::Int(n) = value {
|
||||||
/// Ok(MyInt(*n as i32))
|
/// Ok(MyInt(n as i32))
|
||||||
/// } else {
|
/// } else {
|
||||||
/// Err(InputValueError::ExpectedType)
|
/// Err(InputValueError::ExpectedType(value))
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
|
@ -151,16 +151,13 @@ pub trait ScalarType: Sized + Send {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a scalar value, return `Some(Self)` if successful, otherwise return `None`.
|
/// Parse a scalar value, return `Some(Self)` if successful, otherwise return `None`.
|
||||||
fn parse(value: &Value) -> InputValueResult<Self>;
|
fn parse(value: Value) -> InputValueResult<Self>;
|
||||||
|
|
||||||
/// Checks for a valid scalar value.
|
/// Checks for a valid scalar value.
|
||||||
///
|
///
|
||||||
/// The default implementation is to try to parse it, and in some cases you can implement this on your own to improve performance.
|
/// Implementing this function can find incorrect input values during the verification phase, which can improve performance.
|
||||||
fn is_valid(value: &Value) -> bool {
|
fn is_valid(_value: &Value) -> bool {
|
||||||
match Self::parse(value) {
|
true
|
||||||
Ok(_) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert the scalar value to json value.
|
/// Convert the scalar value to json value.
|
||||||
|
|
|
@ -3,11 +3,12 @@ use crate::parser::ast::{Directive, Field, FragmentDefinition, SelectionSet, Var
|
||||||
use crate::registry::Registry;
|
use crate::registry::Registry;
|
||||||
use crate::{InputValueType, QueryError, Result, Schema, Type};
|
use crate::{InputValueType, QueryError, Result, Schema, Type};
|
||||||
use crate::{Pos, Positioned, Value};
|
use crate::{Pos, Positioned, Value};
|
||||||
|
use async_graphql_parser::UploadValue;
|
||||||
use fnv::FnvHashMap;
|
use fnv::FnvHashMap;
|
||||||
use std::any::{Any, TypeId};
|
use std::any::{Any, TypeId};
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
use std::fs::File;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::path::Path;
|
|
||||||
use std::sync::atomic::AtomicUsize;
|
use std::sync::atomic::AtomicUsize;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -56,9 +57,9 @@ impl Variables {
|
||||||
pub(crate) fn set_upload(
|
pub(crate) fn set_upload(
|
||||||
&mut self,
|
&mut self,
|
||||||
var_path: &str,
|
var_path: &str,
|
||||||
filename: &str,
|
filename: String,
|
||||||
content_type: Option<&str>,
|
content_type: Option<String>,
|
||||||
path: &Path,
|
content: File,
|
||||||
) {
|
) {
|
||||||
let mut it = var_path.split('.').peekable();
|
let mut it = var_path.split('.').peekable();
|
||||||
|
|
||||||
|
@ -76,7 +77,11 @@ impl Variables {
|
||||||
if let Value::List(ls) = current {
|
if let Value::List(ls) = current {
|
||||||
if let Some(value) = ls.get_mut(idx as usize) {
|
if let Some(value) = ls.get_mut(idx as usize) {
|
||||||
if !has_next {
|
if !has_next {
|
||||||
*value = Value::String(file_string(filename, content_type, path));
|
*value = Value::Upload(UploadValue {
|
||||||
|
filename,
|
||||||
|
content_type,
|
||||||
|
content,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
current = value;
|
current = value;
|
||||||
|
@ -88,7 +93,11 @@ impl Variables {
|
||||||
} else if let Value::Object(obj) = current {
|
} else if let Value::Object(obj) = current {
|
||||||
if let Some(value) = obj.get_mut(s) {
|
if let Some(value) = obj.get_mut(s) {
|
||||||
if !has_next {
|
if !has_next {
|
||||||
*value = Value::String(file_string(filename, content_type, path));
|
*value = Value::Upload(UploadValue {
|
||||||
|
filename,
|
||||||
|
content_type,
|
||||||
|
content,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
current = value;
|
current = value;
|
||||||
|
@ -101,14 +110,6 @@ impl Variables {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn file_string(filename: &str, content_type: Option<&str>, path: &Path) -> String {
|
|
||||||
if let Some(content_type) = content_type {
|
|
||||||
format!("file:{}:{}|", filename, content_type) + &path.display().to_string()
|
|
||||||
} else {
|
|
||||||
format!("file:{}|", filename) + &path.display().to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
/// Schema/Context data
|
/// Schema/Context data
|
||||||
pub struct Data(FnvHashMap<TypeId, Box<dyn Any + Sync + Send>>);
|
pub struct Data(FnvHashMap<TypeId, Box<dyn Any + Sync + Send>>);
|
||||||
|
@ -411,16 +412,12 @@ impl<'a, T> ContextBase<'a, T> {
|
||||||
if directive.name.as_str() == "skip" {
|
if directive.name.as_str() == "skip" {
|
||||||
if let Some(value) = directive.get_argument("if") {
|
if let Some(value) = directive.get_argument("if") {
|
||||||
match InputValueType::parse(
|
match InputValueType::parse(
|
||||||
&self.resolve_input_value(value.clone_inner(), value.position())?,
|
self.resolve_input_value(value.clone_inner(), value.position())?,
|
||||||
) {
|
) {
|
||||||
Ok(true) => return Ok(true),
|
Ok(true) => return Ok(true),
|
||||||
Ok(false) => {}
|
Ok(false) => {}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return Err(err.into_error(
|
return Err(err.into_error(value.pos, bool::qualified_type_name()))
|
||||||
value.pos,
|
|
||||||
bool::qualified_type_name(),
|
|
||||||
value.clone_inner(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -434,16 +431,12 @@ impl<'a, T> ContextBase<'a, T> {
|
||||||
} else if directive.name.as_str() == "include" {
|
} else if directive.name.as_str() == "include" {
|
||||||
if let Some(value) = directive.get_argument("if") {
|
if let Some(value) = directive.get_argument("if") {
|
||||||
match InputValueType::parse(
|
match InputValueType::parse(
|
||||||
&self.resolve_input_value(value.clone_inner(), value.position())?,
|
self.resolve_input_value(value.clone_inner(), value.position())?,
|
||||||
) {
|
) {
|
||||||
Ok(false) => return Ok(true),
|
Ok(false) => return Ok(true),
|
||||||
Ok(true) => {}
|
Ok(true) => {}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return Err(err.into_error(
|
return Err(err.into_error(value.pos, bool::qualified_type_name()))
|
||||||
value.pos,
|
|
||||||
bool::qualified_type_name(),
|
|
||||||
value.clone_inner(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -499,18 +492,18 @@ impl<'a> ContextBase<'a, &'a Positioned<Field>> {
|
||||||
Some(value) => {
|
Some(value) => {
|
||||||
let pos = value.position();
|
let pos = value.position();
|
||||||
let value = self.resolve_input_value(value.into_inner(), pos)?;
|
let value = self.resolve_input_value(value.into_inner(), pos)?;
|
||||||
match InputValueType::parse(&value) {
|
match InputValueType::parse(value) {
|
||||||
Ok(res) => Ok(res),
|
Ok(res) => Ok(res),
|
||||||
Err(err) => Err(err.into_error(pos, T::qualified_type_name(), value)),
|
Err(err) => Err(err.into_error(pos, T::qualified_type_name())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let value = default();
|
let value = default();
|
||||||
match InputValueType::parse(&value) {
|
match InputValueType::parse(value) {
|
||||||
Ok(res) => Ok(res),
|
Ok(res) => Ok(res),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// The default value has no valid location.
|
// The default value has no valid location.
|
||||||
Err(err.into_error(Pos::default(), T::qualified_type_name(), value))
|
Err(err.into_error(Pos::default(), T::qualified_type_name()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ pub enum InputValueError {
|
||||||
Custom(String),
|
Custom(String),
|
||||||
|
|
||||||
/// The type of input value does not match the expectation.
|
/// The type of input value does not match the expectation.
|
||||||
ExpectedType,
|
ExpectedType(Value),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Display> From<T> for InputValueError {
|
impl<T: Display> From<T> for InputValueError {
|
||||||
|
@ -19,14 +19,14 @@ impl<T: Display> From<T> for InputValueError {
|
||||||
|
|
||||||
impl InputValueError {
|
impl InputValueError {
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
pub fn into_error(self, pos: Pos, expected_type: String, value: Value) -> Error {
|
pub fn into_error(self, pos: Pos, expected_type: String) -> Error {
|
||||||
match self {
|
match self {
|
||||||
InputValueError::Custom(reason) => Error::Query {
|
InputValueError::Custom(reason) => Error::Query {
|
||||||
pos,
|
pos,
|
||||||
path: None,
|
path: None,
|
||||||
err: QueryError::ParseInputValue { reason },
|
err: QueryError::ParseInputValue { reason },
|
||||||
},
|
},
|
||||||
InputValueError::ExpectedType => Error::Query {
|
InputValueError::ExpectedType(value) => Error::Query {
|
||||||
pos,
|
pos,
|
||||||
path: None,
|
path: None,
|
||||||
err: QueryError::ExpectedInputType {
|
err: QueryError::ExpectedInputType {
|
||||||
|
|
|
@ -32,7 +32,6 @@ where
|
||||||
let mut multipart = Multipart::parse(
|
let mut multipart = Multipart::parse(
|
||||||
self.1,
|
self.1,
|
||||||
boundary.as_str(),
|
boundary.as_str(),
|
||||||
opts.temp_dir.as_deref(),
|
|
||||||
opts.max_file_size,
|
opts.max_file_size,
|
||||||
opts.max_num_files,
|
opts.max_num_files,
|
||||||
)
|
)
|
||||||
|
@ -59,14 +58,14 @@ where
|
||||||
if let Some(name) = &part.name {
|
if let Some(name) = &part.name {
|
||||||
if let Some(var_paths) = map.remove(name) {
|
if let Some(var_paths) = map.remove(name) {
|
||||||
for var_path in var_paths {
|
for var_path in var_paths {
|
||||||
if let (Some(filename), PartData::File(path)) =
|
if let (Some(filename), PartData::File(content)) =
|
||||||
(&part.filename, &part.data)
|
(&part.filename, &part.data)
|
||||||
{
|
{
|
||||||
builder.set_upload(
|
builder.set_upload(
|
||||||
&var_path,
|
&var_path,
|
||||||
&filename,
|
filename.clone(),
|
||||||
part.content_type.as_deref(),
|
part.content_type.clone(),
|
||||||
path,
|
content.try_clone().unwrap(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,10 +77,6 @@ where
|
||||||
return Err(ParseRequestError::MissingFiles);
|
return Err(ParseRequestError::MissingFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(temp_dir) = multipart.temp_dir {
|
|
||||||
builder.set_files_holder(temp_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(builder)
|
Ok(builder)
|
||||||
} else {
|
} else {
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
|
|
|
@ -5,16 +5,14 @@ use futures::{AsyncBufRead, AsyncRead};
|
||||||
use http::{header::HeaderName, HeaderMap, HeaderValue};
|
use http::{header::HeaderName, HeaderMap, HeaderValue};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Cursor, Read, Write};
|
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use tempdir::TempDir;
|
|
||||||
|
|
||||||
const MAX_HEADERS: usize = 16;
|
const MAX_HEADERS: usize = 16;
|
||||||
|
|
||||||
pub enum PartData {
|
pub enum PartData {
|
||||||
Bytes(Vec<u8>),
|
Bytes(Vec<u8>),
|
||||||
File(PathBuf),
|
File(File),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Part {
|
pub struct Part {
|
||||||
|
@ -26,10 +24,10 @@ pub struct Part {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Part {
|
impl Part {
|
||||||
pub fn create_reader<'a>(&'a self) -> Result<Box<dyn Read + 'a>, std::io::Error> {
|
pub fn create_reader(self) -> Result<Box<dyn Read>, std::io::Error> {
|
||||||
let reader: Box<dyn Read> = match &self.data {
|
let reader: Box<dyn Read> = match self.data {
|
||||||
PartData::Bytes(bytes) => Box::new(Cursor::new(bytes)),
|
PartData::Bytes(bytes) => Box::new(Cursor::new(bytes)),
|
||||||
PartData::File(path) => Box::new(File::open(path)?),
|
PartData::File(content) => Box::new(content),
|
||||||
};
|
};
|
||||||
Ok(reader)
|
Ok(reader)
|
||||||
}
|
}
|
||||||
|
@ -55,7 +53,6 @@ impl ContentDisposition {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Multipart {
|
pub struct Multipart {
|
||||||
pub temp_dir: Option<TempDir>,
|
|
||||||
pub parts: Vec<Part>,
|
pub parts: Vec<Part>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,12 +60,10 @@ impl Multipart {
|
||||||
pub async fn parse<R: AsyncRead + Unpin>(
|
pub async fn parse<R: AsyncRead + Unpin>(
|
||||||
reader: R,
|
reader: R,
|
||||||
boundary: &str,
|
boundary: &str,
|
||||||
temp_dir_in: Option<&Path>,
|
|
||||||
max_file_size: Option<usize>,
|
max_file_size: Option<usize>,
|
||||||
max_num_files: Option<usize>,
|
max_num_files: Option<usize>,
|
||||||
) -> Result<Multipart, ParseRequestError> {
|
) -> Result<Multipart, ParseRequestError> {
|
||||||
let mut reader = BufReader::new(reader);
|
let mut reader = BufReader::new(reader);
|
||||||
let mut temp_dir = None;
|
|
||||||
let mut parts = Vec::new();
|
let mut parts = Vec::new();
|
||||||
let boundary = format!("--{}", boundary);
|
let boundary = format!("--{}", boundary);
|
||||||
let max_num_files = max_num_files.unwrap_or(std::usize::MAX);
|
let max_num_files = max_num_files.unwrap_or(std::usize::MAX);
|
||||||
|
@ -79,17 +74,7 @@ impl Multipart {
|
||||||
reader.except_token(boundary.as_bytes()).await?;
|
reader.except_token(boundary.as_bytes()).await?;
|
||||||
reader.except_token(b"\r\n").await?;
|
reader.except_token(b"\r\n").await?;
|
||||||
let headers = Self::parse_headers(&mut reader).await?;
|
let headers = Self::parse_headers(&mut reader).await?;
|
||||||
parts.push(
|
parts.push(Self::parse_body(&mut reader, &headers, max_file_size, &boundary).await?);
|
||||||
Self::parse_body(
|
|
||||||
&mut reader,
|
|
||||||
&headers,
|
|
||||||
&mut temp_dir,
|
|
||||||
temp_dir_in,
|
|
||||||
max_file_size,
|
|
||||||
&boundary,
|
|
||||||
)
|
|
||||||
.await?,
|
|
||||||
);
|
|
||||||
Multipart::check_max_num_files(&mut parts, max_num_files, &mut current_num_files)?;
|
Multipart::check_max_num_files(&mut parts, max_num_files, &mut current_num_files)?;
|
||||||
|
|
||||||
// next parts
|
// next parts
|
||||||
|
@ -100,21 +85,11 @@ impl Multipart {
|
||||||
}
|
}
|
||||||
|
|
||||||
let headers = Self::parse_headers(&mut reader).await?;
|
let headers = Self::parse_headers(&mut reader).await?;
|
||||||
parts.push(
|
parts.push(Self::parse_body(&mut reader, &headers, max_file_size, &boundary).await?);
|
||||||
Self::parse_body(
|
|
||||||
&mut reader,
|
|
||||||
&headers,
|
|
||||||
&mut temp_dir,
|
|
||||||
temp_dir_in,
|
|
||||||
max_file_size,
|
|
||||||
&boundary,
|
|
||||||
)
|
|
||||||
.await?,
|
|
||||||
);
|
|
||||||
Multipart::check_max_num_files(&mut parts, max_num_files, &mut current_num_files)?;
|
Multipart::check_max_num_files(&mut parts, max_num_files, &mut current_num_files)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Multipart { temp_dir, parts })
|
Ok(Multipart { parts })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_max_num_files(
|
fn check_max_num_files(
|
||||||
|
@ -171,8 +146,6 @@ impl Multipart {
|
||||||
async fn parse_body<R: AsyncBufRead + Unpin>(
|
async fn parse_body<R: AsyncBufRead + Unpin>(
|
||||||
mut reader: R,
|
mut reader: R,
|
||||||
headers: &HeaderMap,
|
headers: &HeaderMap,
|
||||||
temp_dir: &mut Option<TempDir>,
|
|
||||||
temp_dir_in: Option<&Path>,
|
|
||||||
max_file_size: usize,
|
max_file_size: usize,
|
||||||
boundary: &str,
|
boundary: &str,
|
||||||
) -> Result<Part, ParseRequestError> {
|
) -> Result<Part, ParseRequestError> {
|
||||||
|
@ -193,17 +166,9 @@ impl Multipart {
|
||||||
let mut state = ReadUntilState::default();
|
let mut state = ReadUntilState::default();
|
||||||
let mut total_size = 0;
|
let mut total_size = 0;
|
||||||
|
|
||||||
let part_data = if let Some(filename) = &content_disposition.filename {
|
let part_data = if content_disposition.filename.is_some() {
|
||||||
if temp_dir.is_none() {
|
// Create a temporary file.
|
||||||
if let Some(temp_dir_in) = temp_dir_in {
|
let mut file = tempfile::tempfile()?;
|
||||||
*temp_dir = Some(TempDir::new_in(temp_dir_in, "async-graphql")?);
|
|
||||||
} else {
|
|
||||||
*temp_dir = Some(TempDir::new("async-graphql")?);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let temp_dir = temp_dir.as_mut().unwrap();
|
|
||||||
let path = temp_dir.path().join(filename);
|
|
||||||
let mut file = File::create(&path)?;
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let (size, found) = reader
|
let (size, found) = reader
|
||||||
|
@ -218,7 +183,8 @@ impl Multipart {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PartData::File(path)
|
file.seek(SeekFrom::Start(0))?;
|
||||||
|
PartData::File(file)
|
||||||
} else {
|
} else {
|
||||||
let mut body = Vec::new();
|
let mut body = Vec::new();
|
||||||
|
|
||||||
|
@ -273,8 +239,7 @@ mod tests {
|
||||||
Content-Type: text/plain; charset=utf-8\r\n\r\n\
|
Content-Type: text/plain; charset=utf-8\r\n\r\n\
|
||||||
data\
|
data\
|
||||||
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n";
|
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n";
|
||||||
let multipart =
|
let multipart = Multipart::parse(data, "abbc761f78ff4d7cb7573b5a23f96ef0", None, None)
|
||||||
Multipart::parse(data, "abbc761f78ff4d7cb7573b5a23f96ef0", None, None, None)
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(multipart.parts.len(), 2);
|
assert_eq!(multipart.parts.len(), 2);
|
||||||
|
@ -307,35 +272,23 @@ mod tests {
|
||||||
data\
|
data\
|
||||||
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n";
|
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n";
|
||||||
|
|
||||||
assert!(Multipart::parse(
|
assert!(
|
||||||
data,
|
Multipart::parse(data, "abbc761f78ff4d7cb7573b5a23f96ef0", Some(5), None,)
|
||||||
"abbc761f78ff4d7cb7573b5a23f96ef0",
|
|
||||||
None,
|
|
||||||
Some(5),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.is_ok());
|
.is_ok()
|
||||||
|
);
|
||||||
|
|
||||||
assert!(Multipart::parse(
|
assert!(
|
||||||
data,
|
Multipart::parse(data, "abbc761f78ff4d7cb7573b5a23f96ef0", Some(6), None,)
|
||||||
"abbc761f78ff4d7cb7573b5a23f96ef0",
|
|
||||||
None,
|
|
||||||
Some(6),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.is_ok());
|
.is_ok()
|
||||||
|
);
|
||||||
|
|
||||||
assert!(Multipart::parse(
|
assert!(
|
||||||
data,
|
Multipart::parse(data, "abbc761f78ff4d7cb7573b5a23f96ef0", Some(4), None,)
|
||||||
"abbc761f78ff4d7cb7573b5a23f96ef0",
|
|
||||||
None,
|
|
||||||
Some(4),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.is_err());
|
.is_err()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_std::test]
|
#[async_std::test]
|
||||||
|
@ -352,34 +305,22 @@ mod tests {
|
||||||
data\
|
data\
|
||||||
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n";
|
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n";
|
||||||
|
|
||||||
assert!(Multipart::parse(
|
assert!(
|
||||||
data,
|
Multipart::parse(data, "abbc761f78ff4d7cb7573b5a23f96ef0", None, Some(1))
|
||||||
"abbc761f78ff4d7cb7573b5a23f96ef0",
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
Some(1)
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.is_err());
|
.is_err()
|
||||||
|
);
|
||||||
|
|
||||||
assert!(Multipart::parse(
|
assert!(
|
||||||
data,
|
Multipart::parse(data, "abbc761f78ff4d7cb7573b5a23f96ef0", None, Some(2))
|
||||||
"abbc761f78ff4d7cb7573b5a23f96ef0",
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
Some(2)
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.is_ok());
|
.is_ok()
|
||||||
|
);
|
||||||
|
|
||||||
assert!(Multipart::parse(
|
assert!(
|
||||||
data,
|
Multipart::parse(data, "abbc761f78ff4d7cb7573b5a23f96ef0", None, Some(3))
|
||||||
"abbc761f78ff4d7cb7573b5a23f96ef0",
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
Some(3)
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.is_ok());
|
.is_ok()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
19
src/query.rs
19
src/query.rs
|
@ -14,9 +14,9 @@ use crate::{
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::{Path, PathBuf};
|
use std::fs::File;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::atomic::AtomicUsize;
|
use std::sync::atomic::AtomicUsize;
|
||||||
use tempdir::TempDir;
|
|
||||||
|
|
||||||
/// IntoQueryBuilder options
|
/// IntoQueryBuilder options
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
|
@ -65,7 +65,6 @@ pub struct QueryBuilder {
|
||||||
pub(crate) operation_name: Option<String>,
|
pub(crate) operation_name: Option<String>,
|
||||||
pub(crate) variables: Variables,
|
pub(crate) variables: Variables,
|
||||||
pub(crate) ctx_data: Option<Data>,
|
pub(crate) ctx_data: Option<Data>,
|
||||||
pub(crate) files_holder: Option<TempDir>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QueryBuilder {
|
impl QueryBuilder {
|
||||||
|
@ -76,7 +75,6 @@ impl QueryBuilder {
|
||||||
operation_name: None,
|
operation_name: None,
|
||||||
variables: Default::default(),
|
variables: Default::default(),
|
||||||
ctx_data: None,
|
ctx_data: None,
|
||||||
files_holder: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,21 +105,16 @@ impl QueryBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set file holder
|
|
||||||
pub fn set_files_holder(&mut self, files_holder: TempDir) {
|
|
||||||
self.files_holder = Some(files_holder);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set uploaded file path
|
/// Set uploaded file path
|
||||||
pub fn set_upload(
|
pub fn set_upload(
|
||||||
&mut self,
|
&mut self,
|
||||||
var_path: &str,
|
var_path: &str,
|
||||||
filename: &str,
|
filename: String,
|
||||||
content_type: Option<&str>,
|
content_type: Option<String>,
|
||||||
path: &Path,
|
content: File,
|
||||||
) {
|
) {
|
||||||
self.variables
|
self.variables
|
||||||
.set_upload(var_path, filename, content_type, path);
|
.set_upload(var_path, filename, content_type, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute the query.
|
/// Execute the query.
|
||||||
|
|
|
@ -18,8 +18,8 @@ impl ScalarType for Any {
|
||||||
Some("The `_Any` scalar is used to pass representations of entities from external services into the root `_entities` field for execution.")
|
Some("The `_Any` scalar is used to pass representations of entities from external services into the root `_entities` field for execution.")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(value: &Value) -> InputValueResult<Self> {
|
fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
Ok(Self(value.clone()))
|
Ok(Self(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_valid(_value: &Value) -> bool {
|
fn is_valid(_value: &Value) -> bool {
|
||||||
|
|
|
@ -11,10 +11,17 @@ impl ScalarType for bool {
|
||||||
Some("The `Boolean` scalar type represents `true` or `false`.")
|
Some("The `Boolean` scalar type represents `true` or `false`.")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(value: &Value) -> InputValueResult<Self> {
|
fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
Value::Boolean(n) => Ok(*n),
|
Value::Boolean(n) => Ok(n),
|
||||||
_ => Err(InputValueError::ExpectedType),
|
_ => Err(InputValueError::ExpectedType(value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid(value: &Value) -> bool {
|
||||||
|
match value {
|
||||||
|
Value::Boolean(_) => true,
|
||||||
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,10 @@ impl ScalarType for ObjectId {
|
||||||
"ObjectId"
|
"ObjectId"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(value: &Value) -> InputValueResult<Self> {
|
fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
Value::String(s) => Ok(ObjectId::with_string(&s)?),
|
Value::String(s) => Ok(ObjectId::with_string(&s)?),
|
||||||
_ => Err(InputValueError::ExpectedType),
|
_ => Err(InputValueError::ExpectedType(value)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ impl ScalarType for UtcDateTime {
|
||||||
"DateTime"
|
"DateTime"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(value: &Value) -> InputValueResult<Self> {
|
fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
DateTime::<Utc>::parse(value).map(UtcDateTime::from)
|
DateTime::<Utc>::parse(value).map(UtcDateTime::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,10 @@ impl ScalarType for Tz {
|
||||||
"TimeZone"
|
"TimeZone"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(value: &Value) -> InputValueResult<Self> {
|
fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
Value::String(s) => Ok(Tz::from_str(&s)?),
|
Value::String(s) => Ok(Tz::from_str(&s)?),
|
||||||
_ => Err(InputValueError::ExpectedType),
|
_ => Err(InputValueError::ExpectedType(value)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,10 +11,10 @@ impl ScalarType for DateTime<Utc> {
|
||||||
"DateTime"
|
"DateTime"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(value: &Value) -> InputValueResult<Self> {
|
fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
Value::String(s) => Ok(Utc.datetime_from_str(&s, "%+")?),
|
Value::String(s) => Ok(Utc.datetime_from_str(&s, "%+")?),
|
||||||
_ => Err(InputValueError::ExpectedType),
|
_ => Err(InputValueError::ExpectedType(value)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,11 +14,11 @@ macro_rules! impl_float_scalars {
|
||||||
Some("The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).")
|
Some("The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(value: &Value) -> InputValueResult<Self> {
|
fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
Value::Int(n) => Ok(*n as Self),
|
Value::Int(n) => Ok(n as Self),
|
||||||
Value::Float(n) => Ok(*n as Self),
|
Value::Float(n) => Ok(n as Self),
|
||||||
_ => Err(InputValueError::ExpectedType)
|
_ => Err(InputValueError::ExpectedType(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,11 +77,11 @@ impl ScalarType for ID {
|
||||||
"ID"
|
"ID"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(value: &Value) -> InputValueResult<Self> {
|
fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
Value::Int(n) => Ok(ID(n.to_string())),
|
Value::Int(n) => Ok(ID(n.to_string())),
|
||||||
Value::String(s) => Ok(ID(s.clone())),
|
Value::String(s) => Ok(ID(s)),
|
||||||
_ => Err(InputValueError::ExpectedType),
|
_ => Err(InputValueError::ExpectedType(value)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,10 +14,10 @@ macro_rules! impl_integer_scalars {
|
||||||
Some("The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.")
|
Some("The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(value: &Value) -> InputValueResult<Self> {
|
fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
Value::Int(n) => Ok(*n as Self),
|
Value::Int(n) => Ok(n as Self),
|
||||||
_ => Err(InputValueError::ExpectedType)
|
_ => Err(InputValueError::ExpectedType(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,11 +51,11 @@ macro_rules! impl_int64_scalars {
|
||||||
Some("The `Int64` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^64) and 2^64 - 1.")
|
Some("The `Int64` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^64) and 2^64 - 1.")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(value: &Value) -> InputValueResult<Self> {
|
fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
Value::Int(n) => Ok(*n as Self),
|
Value::Int(n) => Ok(n as Self),
|
||||||
Value::String(s) => Ok(s.parse()?),
|
Value::String(s) => Ok(s.parse()?),
|
||||||
_ => Err(InputValueError::ExpectedType)
|
_ => Err(InputValueError::ExpectedType(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,8 +28,8 @@ impl<T: DeserializeOwned + Serialize + Send + Sync> ScalarType for Json<T> {
|
||||||
"JSON"
|
"JSON"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(value: &Value) -> InputValueResult<Self> {
|
fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
Ok(serde_json::from_value(value.clone().into()).map(Json)?)
|
Ok(serde_json::from_value(value.into()).map(Json)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_json(&self) -> Result<serde_json::Value> {
|
fn to_json(&self) -> Result<serde_json::Value> {
|
||||||
|
|
|
@ -18,10 +18,10 @@ impl ScalarType for String {
|
||||||
Some(STRING_DESC)
|
Some(STRING_DESC)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(value: &Value) -> InputValueResult<Self> {
|
fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
Value::String(s) => Ok(s.clone()),
|
Value::String(s) => Ok(s),
|
||||||
_ => Err(InputValueError::ExpectedType),
|
_ => Err(InputValueError::ExpectedType(value)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,10 @@ impl ScalarType for Url {
|
||||||
"Url"
|
"Url"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(value: &Value) -> InputValueResult<Self> {
|
fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
Value::String(s) => Ok(Url::parse(s)?),
|
Value::String(s) => Ok(Url::parse(&s)?),
|
||||||
_ => Err(InputValueError::ExpectedType),
|
_ => Err(InputValueError::ExpectedType(value)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,10 @@ impl ScalarType for Uuid {
|
||||||
"UUID"
|
"UUID"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(value: &Value) -> InputValueResult<Self> {
|
fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
Value::String(s) => Ok(Uuid::parse_str(&s)?),
|
Value::String(s) => Ok(Uuid::parse_str(&s)?),
|
||||||
_ => Err(InputValueError::ExpectedType),
|
_ => Err(InputValueError::ExpectedType(value)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,10 +44,10 @@ impl ScalarType for Cursor {
|
||||||
"Cursor"
|
"Cursor"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(value: &Value) -> InputValueResult<Self> {
|
fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
Value::String(s) => Ok(Cursor(s.into())),
|
Value::String(s) => Ok(Cursor(s)),
|
||||||
_ => Err(InputValueError::ExpectedType),
|
_ => Err(InputValueError::ExpectedType(value)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,11 +11,11 @@ pub struct EnumItem<T> {
|
||||||
pub trait EnumType: Type + Sized + Eq + Send + Copy + Sized + 'static {
|
pub trait EnumType: Type + Sized + Eq + Send + Copy + Sized + 'static {
|
||||||
fn items() -> &'static [EnumItem<Self>];
|
fn items() -> &'static [EnumItem<Self>];
|
||||||
|
|
||||||
fn parse_enum(value: &Value) -> InputValueResult<Self> {
|
fn parse_enum(value: Value) -> InputValueResult<Self> {
|
||||||
let value = match value {
|
let value = match value {
|
||||||
Value::Enum(s) => s.as_str(),
|
Value::Enum(s) => s,
|
||||||
Value::String(s) => s.as_str(),
|
Value::String(s) => s,
|
||||||
_ => return Err(InputValueError::ExpectedType),
|
_ => return Err(InputValueError::ExpectedType(value)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let items = Self::items();
|
let items = Self::items();
|
||||||
|
|
|
@ -20,7 +20,7 @@ impl<T: Type> Type for Vec<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: InputValueType> InputValueType for Vec<T> {
|
impl<T: InputValueType> InputValueType for Vec<T> {
|
||||||
fn parse(value: &Value) -> InputValueResult<Self> {
|
fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
Value::List(values) => {
|
Value::List(values) => {
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
|
|
|
@ -20,7 +20,7 @@ impl<T: Type> Type for Option<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: InputValueType> InputValueType for Option<T> {
|
impl<T: InputValueType> InputValueType for Option<T> {
|
||||||
fn parse(value: &Value) -> InputValueResult<Self> {
|
fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
match value {
|
match value {
|
||||||
Value::Null => Ok(None),
|
Value::Null => Ok(None),
|
||||||
_ => Ok(Some(T::parse(value)?)),
|
_ => Ok(Some(T::parse(value)?)),
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{registry, InputValueError, InputValueResult, InputValueType, Type, Value};
|
use crate::{registry, InputValueError, InputValueResult, InputValueType, Type, Value};
|
||||||
|
use async_graphql_parser::UploadValue;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::path::PathBuf;
|
use std::io::Read;
|
||||||
|
|
||||||
/// Uploaded file
|
/// Uploaded file
|
||||||
///
|
///
|
||||||
|
@ -23,7 +24,7 @@ use std::path::PathBuf;
|
||||||
/// #[async_graphql::Object]
|
/// #[async_graphql::Object]
|
||||||
/// impl MutationRoot {
|
/// impl MutationRoot {
|
||||||
/// async fn upload(&self, file: Upload) -> bool {
|
/// async fn upload(&self, file: Upload) -> bool {
|
||||||
/// println!("upload: filename={}", file.filename);
|
/// println!("upload: filename={}", file.filename());
|
||||||
/// true
|
/// true
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
@ -41,15 +42,23 @@ use std::path::PathBuf;
|
||||||
/// --form 'map={ "0": ["variables.file"] }' \
|
/// --form 'map={ "0": ["variables.file"] }' \
|
||||||
/// --form '0=@myFile.txt'
|
/// --form '0=@myFile.txt'
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Upload {
|
pub struct Upload(UploadValue);
|
||||||
|
|
||||||
|
impl Upload {
|
||||||
/// Filename
|
/// Filename
|
||||||
pub filename: String,
|
pub fn filename(&self) -> &str {
|
||||||
|
self.0.filename.as_str()
|
||||||
|
}
|
||||||
|
|
||||||
/// Content type, such as `application/json`, `image/jpg` ...
|
/// Content type, such as `application/json`, `image/jpg` ...
|
||||||
pub content_type: Option<String>,
|
pub fn content_type(&self) -> Option<&str> {
|
||||||
|
self.0.content_type.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
/// Temporary file path
|
/// Convert to a read
|
||||||
pub path: PathBuf,
|
pub fn into_read(self) -> impl Read + Sync + Send + 'static {
|
||||||
|
self.0.content
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Type for Upload {
|
impl<'a> Type for Upload {
|
||||||
|
@ -70,31 +79,11 @@ impl<'a> Type for Upload {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> InputValueType for Upload {
|
impl<'a> InputValueType for Upload {
|
||||||
fn parse(value: &Value) -> InputValueResult<Self> {
|
fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
if let Value::String(s) = value {
|
if let Value::Upload(upload) = value {
|
||||||
if s.starts_with("file:") {
|
Ok(Upload(upload))
|
||||||
let s = &s[5..];
|
|
||||||
if let Some(idx) = s.find('|') {
|
|
||||||
let name_and_type = &s[..idx];
|
|
||||||
let path = &s[idx + 1..];
|
|
||||||
if let Some(type_idx) = name_and_type.find(':') {
|
|
||||||
let name = &name_and_type[..type_idx];
|
|
||||||
let mime_type = &name_and_type[type_idx + 1..];
|
|
||||||
return Ok(Self {
|
|
||||||
filename: name.to_string(),
|
|
||||||
content_type: Some(mime_type.to_string()),
|
|
||||||
path: PathBuf::from(path),
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
return Ok(Self {
|
Err(InputValueError::ExpectedType(value))
|
||||||
filename: name_and_type.to_string(),
|
|
||||||
content_type: None,
|
|
||||||
path: PathBuf::from(path),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Err(InputValueError::ExpectedType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user