Support Upload Stream #15

I think the previous implementation is not elegant enough, the `QueryBuilder::set_files_holder` function looks disgusting, so I refactored it.
By the way, the performance of parsing InputValue has been optimized, and unnecessary clones have been removed.
This commit is contained in:
sunli 2020-05-11 21:47:24 +08:00
parent b521e6c64d
commit 83579077d9
35 changed files with 189 additions and 277 deletions

View File

@ -35,7 +35,7 @@ chrono = "0.4.10"
slab = "0.4.2"
once_cell = "1.3.1"
itertools = "0.9.0"
tempdir = "0.3.7"
tempfile = "3.1.0"
httparse = "1.3.4"
mime = "0.3.16"
http = "0.2.1"

View File

@ -156,7 +156,7 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
}
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)
}
}

View File

@ -81,17 +81,17 @@ pub fn generate(object_args: &args::InputObject, input: &DeriveInput) -> Result<
get_fields.push(quote! {
let #ident:#ty = {
match obj.get(#name) {
Some(value) => #crate_name::InputValueType::parse(value)?,
Some(value) => #crate_name::InputValueType::parse(value.clone())?,
None => {
let default = #default_repr;
#crate_name::InputValueType::parse(&default)?
#crate_name::InputValueType::parse(default)?
}
}
};
});
} else {
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 {
fn parse(value: &#crate_name::Value) -> #crate_name::InputValueResult<Self> {
fn parse(value: #crate_name::Value) -> #crate_name::InputValueResult<Self> {
use #crate_name::Type;
if let #crate_name::Value::Object(obj) = value {
if let #crate_name::Value::Object(obj) = &value {
#(#get_fields)*
Ok(Self { #(#fields),* })
} else {
Err(#crate_name::InputValueError::ExpectedType)
Err(#crate_name::InputValueError::ExpectedType(value))
}
}
}

View File

@ -69,7 +69,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
};
if let Type::Path(p) = &field.ty {
// 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(
field,
"This type already used in another variant",

View File

@ -165,7 +165,7 @@ pub fn Scalar(args: TokenStream, input: TokenStream) -> TokenStream {
}
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)
}
}

View File

@ -126,7 +126,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
});
key_getter.push(quote! {
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
})
});

View File

@ -61,7 +61,7 @@ pub fn generate(union_args: &args::Interface, input: &DeriveInput) -> Result<Tok
};
if let Type::Path(p) = &field.ty {
// 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(
field,
"This type already used in another variant",

View File

@ -58,6 +58,7 @@ pub fn build_value_repr(crate_name: &TokenStream, value: &Value) -> TokenStream
}
}
}
Value::Upload(_) => quote! { #crate_name::Value::Null },
}
}

View File

@ -1,17 +1,27 @@
use std::collections::BTreeMap;
use std::fmt;
use std::fmt::Formatter;
use std::io::Read;
use std::fs::File;
pub struct UploadValue {
pub filename: String,
pub content_type: Option<String>,
pub path: Option<Box<dyn Read>>,
pub content: File,
}
impl fmt::Debug for UploadValue {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!("Upload({})", self.filename)
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(),
}
}
}
@ -128,7 +138,7 @@ impl fmt::Display for Value {
}
write!(f, "}}")
}
Value::Upload(upload) => write!(f, "null"),
Value::Upload(_) => write!(f, "null"),
}
}
}

View File

@ -1,6 +1,7 @@
mod test_utils;
use serde_json::json;
use smol::{Task, Timer};
use std::io::Read;
use std::time::Duration;
use tide::Request;
@ -167,21 +168,18 @@ fn upload() -> Result<()> {
#[Object]
impl MutationRoot {
async fn single_upload(&self, file: Upload) -> FileInfo {
println!("single_upload: filename={}", file.filename);
println!("single_upload: content_type={:?}", file.content_type);
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()));
println!("single_upload: filename={}", file.filename());
println!("single_upload: content_type={:?}", file.content_type());
let file_info = FileInfo {
filename: file.filename,
mime_type: file.content_type,
filename: file.filename().into(),
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
}
}

View File

@ -19,7 +19,7 @@ impl ScalarType for StringNumber {
"StringNumber"
}
fn parse(value: &Value) -> InputValueResult<Self> {
fn parse(value: Value) -> InputValueResult<Self> {
if let Value::String(value) = value {
// Parse the integer value
value.parse().map(StringNumber)?

View File

@ -19,7 +19,7 @@ impl ScalarType for StringNumber {
"StringNumber"
}
fn parse(value: &Value) -> InputValueResult<Self> {
fn parse(value: Value) -> InputValueResult<Self> {
if let Value::String(value) = value {
// 解析整数
value.parse().map(StringNumber)?

View File

@ -52,7 +52,7 @@ pub trait Type {
/// Represents a GraphQL input value
pub trait InputValueType: Type + Sized {
/// Parse from `Value`
fn parse(value: &Value) -> InputValueResult<Self>;
fn parse(value: Value) -> InputValueResult<Self>;
}
/// Represents a GraphQL output value
@ -128,11 +128,11 @@ pub trait InputObjectType: InputValueType {}
/// "MyInt"
/// }
///
/// fn parse(value: &Value) -> InputValueResult<Self> {
/// fn parse(value: Value) -> InputValueResult<Self> {
/// if let Value::Int(n) = value {
/// Ok(MyInt(*n as i32))
/// Ok(MyInt(n as i32))
/// } 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`.
fn parse(value: &Value) -> InputValueResult<Self>;
fn parse(value: Value) -> InputValueResult<Self>;
/// 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.
fn is_valid(value: &Value) -> bool {
match Self::parse(value) {
Ok(_) => true,
_ => false,
}
/// Implementing this function can find incorrect input values during the verification phase, which can improve performance.
fn is_valid(_value: &Value) -> bool {
true
}
/// Convert the scalar value to json value.

View File

@ -3,11 +3,12 @@ use crate::parser::ast::{Directive, Field, FragmentDefinition, SelectionSet, Var
use crate::registry::Registry;
use crate::{InputValueType, QueryError, Result, Schema, Type};
use crate::{Pos, Positioned, Value};
use async_graphql_parser::UploadValue;
use fnv::FnvHashMap;
use std::any::{Any, TypeId};
use std::collections::{BTreeMap, HashMap};
use std::fs::File;
use std::ops::{Deref, DerefMut};
use std::path::Path;
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;
@ -56,9 +57,9 @@ impl Variables {
pub(crate) fn set_upload(
&mut self,
var_path: &str,
filename: &str,
content_type: Option<&str>,
path: &Path,
filename: String,
content_type: Option<String>,
content: File,
) {
let mut it = var_path.split('.').peekable();
@ -76,7 +77,11 @@ impl Variables {
if let Value::List(ls) = current {
if let Some(value) = ls.get_mut(idx as usize) {
if !has_next {
*value = Value::String(file_string(filename, content_type, path));
*value = Value::Upload(UploadValue {
filename,
content_type,
content,
});
return;
} else {
current = value;
@ -88,7 +93,11 @@ impl Variables {
} else if let Value::Object(obj) = current {
if let Some(value) = obj.get_mut(s) {
if !has_next {
*value = Value::String(file_string(filename, content_type, path));
*value = Value::Upload(UploadValue {
filename,
content_type,
content,
});
return;
} else {
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)]
/// Schema/Context data
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 let Some(value) = directive.get_argument("if") {
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(false) => {}
Err(err) => {
return Err(err.into_error(
value.pos,
bool::qualified_type_name(),
value.clone_inner(),
))
return Err(err.into_error(value.pos, bool::qualified_type_name()))
}
}
} else {
@ -434,16 +431,12 @@ impl<'a, T> ContextBase<'a, T> {
} else if directive.name.as_str() == "include" {
if let Some(value) = directive.get_argument("if") {
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(true) => {}
Err(err) => {
return Err(err.into_error(
value.pos,
bool::qualified_type_name(),
value.clone_inner(),
))
return Err(err.into_error(value.pos, bool::qualified_type_name()))
}
}
} else {
@ -499,18 +492,18 @@ impl<'a> ContextBase<'a, &'a Positioned<Field>> {
Some(value) => {
let pos = value.position();
let value = self.resolve_input_value(value.into_inner(), pos)?;
match InputValueType::parse(&value) {
match InputValueType::parse(value) {
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 => {
let value = default();
match InputValueType::parse(&value) {
match InputValueType::parse(value) {
Ok(res) => Ok(res),
Err(err) => {
// 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()))
}
}
}

View File

@ -8,7 +8,7 @@ pub enum InputValueError {
Custom(String),
/// The type of input value does not match the expectation.
ExpectedType,
ExpectedType(Value),
}
impl<T: Display> From<T> for InputValueError {
@ -19,14 +19,14 @@ impl<T: Display> From<T> for InputValueError {
impl InputValueError {
#[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 {
InputValueError::Custom(reason) => Error::Query {
pos,
path: None,
err: QueryError::ParseInputValue { reason },
},
InputValueError::ExpectedType => Error::Query {
InputValueError::ExpectedType(value) => Error::Query {
pos,
path: None,
err: QueryError::ExpectedInputType {

View File

@ -32,7 +32,6 @@ where
let mut multipart = Multipart::parse(
self.1,
boundary.as_str(),
opts.temp_dir.as_deref(),
opts.max_file_size,
opts.max_num_files,
)
@ -59,14 +58,14 @@ where
if let Some(name) = &part.name {
if let Some(var_paths) = map.remove(name) {
for var_path in var_paths {
if let (Some(filename), PartData::File(path)) =
if let (Some(filename), PartData::File(content)) =
(&part.filename, &part.data)
{
builder.set_upload(
&var_path,
&filename,
part.content_type.as_deref(),
path,
filename.clone(),
part.content_type.clone(),
content.try_clone().unwrap(),
);
}
}
@ -78,10 +77,6 @@ where
return Err(ParseRequestError::MissingFiles);
}
if let Some(temp_dir) = multipart.temp_dir {
builder.set_files_holder(temp_dir);
}
Ok(builder)
} else {
let mut data = Vec::new();

View File

@ -5,16 +5,14 @@ use futures::{AsyncBufRead, AsyncRead};
use http::{header::HeaderName, HeaderMap, HeaderValue};
use itertools::Itertools;
use std::fs::File;
use std::io::{Cursor, Read, Write};
use std::path::{Path, PathBuf};
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use std::str::FromStr;
use tempdir::TempDir;
const MAX_HEADERS: usize = 16;
pub enum PartData {
Bytes(Vec<u8>),
File(PathBuf),
File(File),
}
pub struct Part {
@ -26,10 +24,10 @@ pub struct Part {
}
impl Part {
pub fn create_reader<'a>(&'a self) -> Result<Box<dyn Read + 'a>, std::io::Error> {
let reader: Box<dyn Read> = match &self.data {
pub fn create_reader(self) -> Result<Box<dyn Read>, std::io::Error> {
let reader: Box<dyn Read> = match self.data {
PartData::Bytes(bytes) => Box::new(Cursor::new(bytes)),
PartData::File(path) => Box::new(File::open(path)?),
PartData::File(content) => Box::new(content),
};
Ok(reader)
}
@ -55,7 +53,6 @@ impl ContentDisposition {
}
pub struct Multipart {
pub temp_dir: Option<TempDir>,
pub parts: Vec<Part>,
}
@ -63,12 +60,10 @@ impl Multipart {
pub async fn parse<R: AsyncRead + Unpin>(
reader: R,
boundary: &str,
temp_dir_in: Option<&Path>,
max_file_size: Option<usize>,
max_num_files: Option<usize>,
) -> Result<Multipart, ParseRequestError> {
let mut reader = BufReader::new(reader);
let mut temp_dir = None;
let mut parts = Vec::new();
let boundary = format!("--{}", boundary);
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(b"\r\n").await?;
let headers = Self::parse_headers(&mut reader).await?;
parts.push(
Self::parse_body(
&mut reader,
&headers,
&mut temp_dir,
temp_dir_in,
max_file_size,
&boundary,
)
.await?,
);
parts.push(Self::parse_body(&mut reader, &headers, max_file_size, &boundary).await?);
Multipart::check_max_num_files(&mut parts, max_num_files, &mut current_num_files)?;
// next parts
@ -100,21 +85,11 @@ impl Multipart {
}
let headers = Self::parse_headers(&mut reader).await?;
parts.push(
Self::parse_body(
&mut reader,
&headers,
&mut temp_dir,
temp_dir_in,
max_file_size,
&boundary,
)
.await?,
);
parts.push(Self::parse_body(&mut reader, &headers, max_file_size, &boundary).await?);
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(
@ -171,8 +146,6 @@ impl Multipart {
async fn parse_body<R: AsyncBufRead + Unpin>(
mut reader: R,
headers: &HeaderMap,
temp_dir: &mut Option<TempDir>,
temp_dir_in: Option<&Path>,
max_file_size: usize,
boundary: &str,
) -> Result<Part, ParseRequestError> {
@ -193,17 +166,9 @@ impl Multipart {
let mut state = ReadUntilState::default();
let mut total_size = 0;
let part_data = if let Some(filename) = &content_disposition.filename {
if temp_dir.is_none() {
if let Some(temp_dir_in) = temp_dir_in {
*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)?;
let part_data = if content_disposition.filename.is_some() {
// Create a temporary file.
let mut file = tempfile::tempfile()?;
loop {
let (size, found) = reader
@ -218,7 +183,8 @@ impl Multipart {
break;
}
}
PartData::File(path)
file.seek(SeekFrom::Start(0))?;
PartData::File(file)
} else {
let mut body = Vec::new();
@ -273,10 +239,9 @@ mod tests {
Content-Type: text/plain; charset=utf-8\r\n\r\n\
data\
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n";
let multipart =
Multipart::parse(data, "abbc761f78ff4d7cb7573b5a23f96ef0", None, None, None)
.await
.unwrap();
let multipart = Multipart::parse(data, "abbc761f78ff4d7cb7573b5a23f96ef0", None, None)
.await
.unwrap();
assert_eq!(multipart.parts.len(), 2);
let part_1 = &multipart.parts[0];
@ -307,35 +272,23 @@ mod tests {
data\
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n";
assert!(Multipart::parse(
data,
"abbc761f78ff4d7cb7573b5a23f96ef0",
None,
Some(5),
None,
)
.await
.is_ok());
assert!(
Multipart::parse(data, "abbc761f78ff4d7cb7573b5a23f96ef0", Some(5), None,)
.await
.is_ok()
);
assert!(Multipart::parse(
data,
"abbc761f78ff4d7cb7573b5a23f96ef0",
None,
Some(6),
None,
)
.await
.is_ok());
assert!(
Multipart::parse(data, "abbc761f78ff4d7cb7573b5a23f96ef0", Some(6), None,)
.await
.is_ok()
);
assert!(Multipart::parse(
data,
"abbc761f78ff4d7cb7573b5a23f96ef0",
None,
Some(4),
None,
)
.await
.is_err());
assert!(
Multipart::parse(data, "abbc761f78ff4d7cb7573b5a23f96ef0", Some(4), None,)
.await
.is_err()
);
}
#[async_std::test]
@ -352,34 +305,22 @@ mod tests {
data\
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n";
assert!(Multipart::parse(
data,
"abbc761f78ff4d7cb7573b5a23f96ef0",
None,
None,
Some(1)
)
.await
.is_err());
assert!(
Multipart::parse(data, "abbc761f78ff4d7cb7573b5a23f96ef0", None, Some(1))
.await
.is_err()
);
assert!(Multipart::parse(
data,
"abbc761f78ff4d7cb7573b5a23f96ef0",
None,
None,
Some(2)
)
.await
.is_ok());
assert!(
Multipart::parse(data, "abbc761f78ff4d7cb7573b5a23f96ef0", None, Some(2))
.await
.is_ok()
);
assert!(Multipart::parse(
data,
"abbc761f78ff4d7cb7573b5a23f96ef0",
None,
None,
Some(3)
)
.await
.is_ok());
assert!(
Multipart::parse(data, "abbc761f78ff4d7cb7573b5a23f96ef0", None, Some(3))
.await
.is_ok()
);
}
}

View File

@ -14,9 +14,9 @@ use crate::{
use itertools::Itertools;
use std::any::Any;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::fs::File;
use std::path::PathBuf;
use std::sync::atomic::AtomicUsize;
use tempdir::TempDir;
/// IntoQueryBuilder options
#[derive(Default, Clone)]
@ -65,7 +65,6 @@ pub struct QueryBuilder {
pub(crate) operation_name: Option<String>,
pub(crate) variables: Variables,
pub(crate) ctx_data: Option<Data>,
pub(crate) files_holder: Option<TempDir>,
}
impl QueryBuilder {
@ -76,7 +75,6 @@ impl QueryBuilder {
operation_name: None,
variables: Default::default(),
ctx_data: None,
files_holder: None,
}
}
@ -107,21 +105,16 @@ impl QueryBuilder {
self
}
/// Set file holder
pub fn set_files_holder(&mut self, files_holder: TempDir) {
self.files_holder = Some(files_holder);
}
/// Set uploaded file path
pub fn set_upload(
&mut self,
var_path: &str,
filename: &str,
content_type: Option<&str>,
path: &Path,
filename: String,
content_type: Option<String>,
content: File,
) {
self.variables
.set_upload(var_path, filename, content_type, path);
.set_upload(var_path, filename, content_type, content);
}
/// Execute the query.

View File

@ -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.")
}
fn parse(value: &Value) -> InputValueResult<Self> {
Ok(Self(value.clone()))
fn parse(value: Value) -> InputValueResult<Self> {
Ok(Self(value))
}
fn is_valid(_value: &Value) -> bool {

View File

@ -11,10 +11,17 @@ impl ScalarType for bool {
Some("The `Boolean` scalar type represents `true` or `false`.")
}
fn parse(value: &Value) -> InputValueResult<Self> {
fn parse(value: Value) -> InputValueResult<Self> {
match value {
Value::Boolean(n) => Ok(*n),
_ => Err(InputValueError::ExpectedType),
Value::Boolean(n) => Ok(n),
_ => Err(InputValueError::ExpectedType(value)),
}
}
fn is_valid(value: &Value) -> bool {
match value {
Value::Boolean(_) => true,
_ => false,
}
}

View File

@ -9,10 +9,10 @@ impl ScalarType for ObjectId {
"ObjectId"
}
fn parse(value: &Value) -> InputValueResult<Self> {
fn parse(value: Value) -> InputValueResult<Self> {
match value {
Value::String(s) => Ok(ObjectId::with_string(&s)?),
_ => Err(InputValueError::ExpectedType),
_ => Err(InputValueError::ExpectedType(value)),
}
}
@ -27,7 +27,7 @@ impl ScalarType for UtcDateTime {
"DateTime"
}
fn parse(value: &Value) -> InputValueResult<Self> {
fn parse(value: Value) -> InputValueResult<Self> {
DateTime::<Utc>::parse(value).map(UtcDateTime::from)
}

View File

@ -9,10 +9,10 @@ impl ScalarType for Tz {
"TimeZone"
}
fn parse(value: &Value) -> InputValueResult<Self> {
fn parse(value: Value) -> InputValueResult<Self> {
match value {
Value::String(s) => Ok(Tz::from_str(&s)?),
_ => Err(InputValueError::ExpectedType),
_ => Err(InputValueError::ExpectedType(value)),
}
}

View File

@ -11,10 +11,10 @@ impl ScalarType for DateTime<Utc> {
"DateTime"
}
fn parse(value: &Value) -> InputValueResult<Self> {
fn parse(value: Value) -> InputValueResult<Self> {
match value {
Value::String(s) => Ok(Utc.datetime_from_str(&s, "%+")?),
_ => Err(InputValueError::ExpectedType),
_ => Err(InputValueError::ExpectedType(value)),
}
}

View File

@ -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).")
}
fn parse(value: &Value) -> InputValueResult<Self> {
fn parse(value: Value) -> InputValueResult<Self> {
match value {
Value::Int(n) => Ok(*n as Self),
Value::Float(n) => Ok(*n as Self),
_ => Err(InputValueError::ExpectedType)
Value::Int(n) => Ok(n as Self),
Value::Float(n) => Ok(n as Self),
_ => Err(InputValueError::ExpectedType(value))
}
}

View File

@ -77,11 +77,11 @@ impl ScalarType for ID {
"ID"
}
fn parse(value: &Value) -> InputValueResult<Self> {
fn parse(value: Value) -> InputValueResult<Self> {
match value {
Value::Int(n) => Ok(ID(n.to_string())),
Value::String(s) => Ok(ID(s.clone())),
_ => Err(InputValueError::ExpectedType),
Value::String(s) => Ok(ID(s)),
_ => Err(InputValueError::ExpectedType(value)),
}
}

View File

@ -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.")
}
fn parse(value: &Value) -> InputValueResult<Self> {
fn parse(value: Value) -> InputValueResult<Self> {
match value {
Value::Int(n) => Ok(*n as Self),
_ => Err(InputValueError::ExpectedType)
Value::Int(n) => Ok(n as Self),
_ => 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.")
}
fn parse(value: &Value) -> InputValueResult<Self> {
fn parse(value: Value) -> InputValueResult<Self> {
match value {
Value::Int(n) => Ok(*n as Self),
Value::Int(n) => Ok(n as Self),
Value::String(s) => Ok(s.parse()?),
_ => Err(InputValueError::ExpectedType)
_ => Err(InputValueError::ExpectedType(value))
}
}

View File

@ -28,8 +28,8 @@ impl<T: DeserializeOwned + Serialize + Send + Sync> ScalarType for Json<T> {
"JSON"
}
fn parse(value: &Value) -> InputValueResult<Self> {
Ok(serde_json::from_value(value.clone().into()).map(Json)?)
fn parse(value: Value) -> InputValueResult<Self> {
Ok(serde_json::from_value(value.into()).map(Json)?)
}
fn to_json(&self) -> Result<serde_json::Value> {

View File

@ -18,10 +18,10 @@ impl ScalarType for String {
Some(STRING_DESC)
}
fn parse(value: &Value) -> InputValueResult<Self> {
fn parse(value: Value) -> InputValueResult<Self> {
match value {
Value::String(s) => Ok(s.clone()),
_ => Err(InputValueError::ExpectedType),
Value::String(s) => Ok(s),
_ => Err(InputValueError::ExpectedType(value)),
}
}

View File

@ -8,10 +8,10 @@ impl ScalarType for Url {
"Url"
}
fn parse(value: &Value) -> InputValueResult<Self> {
fn parse(value: Value) -> InputValueResult<Self> {
match value {
Value::String(s) => Ok(Url::parse(s)?),
_ => Err(InputValueError::ExpectedType),
Value::String(s) => Ok(Url::parse(&s)?),
_ => Err(InputValueError::ExpectedType(value)),
}
}

View File

@ -8,10 +8,10 @@ impl ScalarType for Uuid {
"UUID"
}
fn parse(value: &Value) -> InputValueResult<Self> {
fn parse(value: Value) -> InputValueResult<Self> {
match value {
Value::String(s) => Ok(Uuid::parse_str(&s)?),
_ => Err(InputValueError::ExpectedType),
_ => Err(InputValueError::ExpectedType(value)),
}
}

View File

@ -44,10 +44,10 @@ impl ScalarType for Cursor {
"Cursor"
}
fn parse(value: &Value) -> InputValueResult<Self> {
fn parse(value: Value) -> InputValueResult<Self> {
match value {
Value::String(s) => Ok(Cursor(s.into())),
_ => Err(InputValueError::ExpectedType),
Value::String(s) => Ok(Cursor(s)),
_ => Err(InputValueError::ExpectedType(value)),
}
}

View File

@ -11,11 +11,11 @@ pub struct EnumItem<T> {
pub trait EnumType: Type + Sized + Eq + Send + Copy + Sized + 'static {
fn items() -> &'static [EnumItem<Self>];
fn parse_enum(value: &Value) -> InputValueResult<Self> {
fn parse_enum(value: Value) -> InputValueResult<Self> {
let value = match value {
Value::Enum(s) => s.as_str(),
Value::String(s) => s.as_str(),
_ => return Err(InputValueError::ExpectedType),
Value::Enum(s) => s,
Value::String(s) => s,
_ => return Err(InputValueError::ExpectedType(value)),
};
let items = Self::items();

View File

@ -20,7 +20,7 @@ impl<T: Type> Type for Vec<T> {
}
impl<T: InputValueType> InputValueType for Vec<T> {
fn parse(value: &Value) -> InputValueResult<Self> {
fn parse(value: Value) -> InputValueResult<Self> {
match value {
Value::List(values) => {
let mut result = Vec::new();

View File

@ -20,7 +20,7 @@ impl<T: Type> Type for Option<T> {
}
impl<T: InputValueType> InputValueType for Option<T> {
fn parse(value: &Value) -> InputValueResult<Self> {
fn parse(value: Value) -> InputValueResult<Self> {
match value {
Value::Null => Ok(None),
_ => Ok(Some(T::parse(value)?)),

View File

@ -1,9 +1,7 @@
use crate::{registry, InputValueError, InputValueResult, InputValueType, Type, Value};
use async_graphql_parser::UploadValue;
use futures::AsyncRead;
use std::borrow::Cow;
use std::io::Read;
use std::path::PathBuf;
/// Uploaded file
///
@ -26,7 +24,7 @@ use std::path::PathBuf;
/// #[async_graphql::Object]
/// impl MutationRoot {
/// async fn upload(&self, file: Upload) -> bool {
/// println!("upload: filename={}", file.filename);
/// println!("upload: filename={}", file.filename());
/// true
/// }
/// }
@ -57,11 +55,10 @@ impl Upload {
self.0.content_type.as_deref()
}
/// Convert to an asynchronous stream
pub fn into_async_read(self) -> impl AsyncRead {}
/// Convert to a synchronized stream
pub fn into_read(self) -> impl Read {}
/// Convert to a read
pub fn into_read(self) -> impl Read + Sync + Send + 'static {
self.0.content
}
}
impl<'a> Type for Upload {
@ -82,31 +79,11 @@ impl<'a> Type for Upload {
}
impl<'a> InputValueType for Upload {
fn parse(value: &Value) -> InputValueResult<Self> {
if let Value::String(s) = value {
if s.starts_with("file:") {
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 {
return Ok(Self {
filename: name_and_type.to_string(),
content_type: None,
path: PathBuf::from(path),
});
}
}
}
fn parse(value: Value) -> InputValueResult<Self> {
if let Value::Upload(upload) = value {
Ok(Upload(upload))
} else {
Err(InputValueError::ExpectedType(value))
}
Err(InputValueError::ExpectedType)
}
}