variables supported

This commit is contained in:
sunli 2020-03-02 00:52:05 +08:00
parent fdf3c1d360
commit 781aabdff0
12 changed files with 195 additions and 31 deletions

View File

@ -51,7 +51,7 @@
- [ ] Fragments - [ ] Fragments
- [ ] Inline fragments - [ ] Inline fragments
- [X] Operation name - [X] Operation name
- [ ] Variables - [X] Variables
- [ ] Directives - [ ] Directives
- [ ] Schema - [ ] Schema

View File

@ -67,9 +67,13 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
} }
impl async_graphql::GQLInputValue for #ident { impl async_graphql::GQLInputValue for #ident {
fn parse(value: graphql_parser::query::Value) -> Result<Self> { fn parse(value: async_graphql::Value) -> Result<Self> {
Self::parse_enum(value) Self::parse_enum(value)
} }
fn parse_from_json(value: async_graphql::serde_json::Value) -> Result<Self> {
Self::parse_json_enum(value)
}
} }
#[async_graphql::async_trait::async_trait] #[async_graphql::async_trait::async_trait]

View File

@ -16,7 +16,9 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
.unwrap_or_else(|| ident.to_string()); .unwrap_or_else(|| ident.to_string());
let mut get_fields = Vec::new(); let mut get_fields = Vec::new();
let mut get_json_fields = Vec::new();
let mut fields = Vec::new(); let mut fields = Vec::new();
for field in &s.fields { for field in &s.fields {
let field_args = args::InputField::parse(&field.attrs)?; let field_args = args::InputField::parse(&field.attrs)?;
let ident = field.ident.as_ref().unwrap(); let ident = field.ident.as_ref().unwrap();
@ -25,6 +27,9 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
get_fields.push(quote! { get_fields.push(quote! {
let #ident:#ty = async_graphql::GQLInputValue::parse(obj.remove(#name).unwrap_or(async_graphql::Value::Null))?; let #ident:#ty = async_graphql::GQLInputValue::parse(obj.remove(#name).unwrap_or(async_graphql::Value::Null))?;
}); });
get_json_fields.push(quote! {
let #ident:#ty = async_graphql::GQLInputValue::parse_from_json(obj.remove(#name).unwrap_or(async_graphql::serde_json::Value::Null))?;
});
fields.push(ident); fields.push(ident);
} }
@ -49,6 +54,18 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
}.into()) }.into())
} }
} }
fn parse_from_json(value: async_graphql::serde_json::Value) -> async_graphql::Result<Self> {
if let async_graphql::serde_json::Value::Object(mut obj) = value {
#(#get_json_fields)*
Ok(Self { #(#fields),* })
} else {
Err(async_graphql::QueryError::ExpectedJsonType {
expect: Self::type_name(),
actual: value,
}.into())
}
}
} }
impl async_graphql::GQLInputObject for #ident {} impl async_graphql::GQLInputObject for #ident {}

View File

@ -62,7 +62,8 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
let ctx_obj = ctx_field.with_item(&field.selection_set); let ctx_obj = ctx_field.with_item(&field.selection_set);
let value = obj.resolve(&ctx_obj).await. let value = obj.resolve(&ctx_obj).await.
map_err(|err| err.with_position(field.position))?; map_err(|err| err.with_position(field.position))?;
result.insert(#field_name.to_string(), value.into()); let name = field.alias.clone().unwrap_or_else(|| field.name.clone());
result.insert(name, value.into());
continue; continue;
} }
}); });
@ -105,7 +106,8 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
async_graphql::graphql_parser::query::Selection::Field(field) => { async_graphql::graphql_parser::query::Selection::Field(field) => {
let ctx_field = ctx.with_item(field); let ctx_field = ctx.with_item(field);
if field.name.as_str() == "__typename" { if field.name.as_str() == "__typename" {
result.insert("__typename".to_string(), #gql_typename.into()); let name = field.alias.clone().unwrap_or_else(|| field.name.clone());
result.insert(name, #gql_typename.into());
continue; continue;
} }
#(#resolvers)* #(#resolvers)*

View File

@ -6,7 +6,7 @@ use std::collections::HashMap;
use std::hash::BuildHasherDefault; use std::hash::BuildHasherDefault;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
#[derive(Serialize, Deserialize, Default)] #[derive(Default)]
pub struct Variables(HashMap<String, serde_json::Value>); pub struct Variables(HashMap<String, serde_json::Value>);
impl Deref for Variables { impl Deref for Variables {
@ -78,28 +78,25 @@ impl<'a> Context<'a, &'a Field> {
.iter() .iter()
.find(|(n, _)| n == name) .find(|(n, _)| n == name)
.map(|(_, v)| v) .map(|(_, v)| v)
.cloned() .cloned();
.unwrap_or(Value::Null);
let value = match (value, &self.variables) { if let Some(Value::Variable(var_name)) = &value {
(Value::Variable(name), Some(vars)) => match vars.get(&name).cloned() { if let Some(vars) = &self.variables {
Some(value) => value, if let Some(var_value) = vars.get(&*var_name).cloned() {
None => { let res = GQLInputValue::parse_from_json(var_value)
.map_err(|err| err.with_position(self.item.position))?;
return Ok(res);
}
}
return Err(QueryError::VarNotDefined { return Err(QueryError::VarNotDefined {
var_name: name.clone(), var_name: var_name.clone(),
} }
.into()); .into());
}
},
(Value::Variable(name), None) => {
return Err(QueryError::VarNotDefined {
var_name: name.clone(),
}
.into());
}
(value, _) => value,
}; };
let res =
GQLInputValue::parse(value).map_err(|err| err.with_position(self.item.position))?; let res = GQLInputValue::parse(value.unwrap_or(Value::Null))
.map_err(|err| err.with_position(self.item.position))?;
Ok(res) Ok(res)
} }
} }

View File

@ -1,4 +1,5 @@
use crate::{QueryError, Scalar, Result, Value}; use crate::{QueryError, Result, Scalar, Value};
use anyhow::Error;
use chrono::{DateTime, TimeZone, Utc}; use chrono::{DateTime, TimeZone, Utc};
impl Scalar for DateTime<Utc> { impl Scalar for DateTime<Utc> {
@ -19,6 +20,19 @@ impl Scalar for DateTime<Utc> {
} }
} }
fn parse_from_json(value: serde_json::Value) -> Result<Self> {
match value {
serde_json::Value::String(s) => Ok(Utc.datetime_from_str(&s, "%+")?),
_ => {
return Err(QueryError::ExpectedJsonType {
expect: Self::type_name().to_string(),
actual: value,
}
.into())
}
}
}
fn into_json(self) -> Result<serde_json::Value> { fn into_json(self) -> Result<serde_json::Value> {
Ok(self.to_rfc3339().into()) Ok(self.to_rfc3339().into())
} }

View File

@ -1,4 +1,4 @@
use crate::{QueryError, GQLType, Result}; use crate::{GQLType, QueryError, Result};
use graphql_parser::query::Value; use graphql_parser::query::Value;
#[doc(hidden)] #[doc(hidden)]
@ -38,6 +38,31 @@ pub trait GQLEnum: GQLType + Sized + Eq + Send + Copy + Sized + 'static {
} }
} }
fn parse_json_enum(value: serde_json::Value) -> Result<Self> {
match value {
serde_json::Value::String(s) => {
let items = Self::items();
for item in items {
if item.name == s {
return Ok(item.value);
}
}
Err(QueryError::InvalidEnumValue {
enum_type: Self::type_name(),
value: s,
}
.into())
}
_ => {
return Err(QueryError::ExpectedJsonType {
expect: Self::type_name(),
actual: value,
}
.into())
}
}
}
fn resolve_enum(self) -> Result<serde_json::Value> { fn resolve_enum(self) -> Result<serde_json::Value> {
let items = Self::items(); let items = Self::items();
for item in items { for item in items {

View File

@ -15,6 +15,12 @@ pub enum QueryError {
#[error("Expected type \"{expect}\", found {actual}.")] #[error("Expected type \"{expect}\", found {actual}.")]
ExpectedType { expect: String, actual: Value }, ExpectedType { expect: String, actual: Value },
#[error("Expected type \"{expect}\", found {actual}.")]
ExpectedJsonType {
expect: String,
actual: serde_json::Value,
},
#[error("Cannot query field \"{field_name}\" on type \"{object}\".")] #[error("Cannot query field \"{field_name}\" on type \"{object}\".")]
FieldNotFound { FieldNotFound {
field_name: String, field_name: String,

View File

@ -32,8 +32,6 @@
#[macro_use] #[macro_use]
extern crate thiserror; extern crate thiserror;
#[macro_use]
extern crate serde_derive;
mod context; mod context;
mod r#enum; mod r#enum;

View File

@ -1,10 +1,12 @@
use crate::r#type::{GQLInputValue, GQLOutputValue, GQLType}; use crate::r#type::{GQLInputValue, GQLOutputValue, GQLType};
use crate::{ContextSelectionSet, QueryError, Result}; use crate::{ContextSelectionSet, QueryError, Result};
use anyhow::Error;
use graphql_parser::query::Value; use graphql_parser::query::Value;
pub trait Scalar: Sized + Send { pub trait Scalar: Sized + Send {
fn type_name() -> &'static str; fn type_name() -> &'static str;
fn parse(value: Value) -> Result<Self>; fn parse(value: Value) -> Result<Self>;
fn parse_from_json(value: serde_json::Value) -> Result<Self>;
fn into_json(self) -> Result<serde_json::Value>; fn into_json(self) -> Result<serde_json::Value>;
} }
@ -18,6 +20,10 @@ impl<T: Scalar> GQLInputValue for T {
fn parse(value: Value) -> Result<Self> { fn parse(value: Value) -> Result<Self> {
T::parse(value) T::parse(value)
} }
fn parse_from_json(value: serde_json::Value) -> Result<Self> {
T::parse_from_json(value)
}
} }
#[async_trait::async_trait] #[async_trait::async_trait]
@ -48,6 +54,20 @@ macro_rules! impl_integer_scalars {
} }
} }
fn parse_from_json(value: serde_json::Value) -> Result<Self> {
match value {
serde_json::Value::Number(n) if n.is_i64() => Ok(n.as_i64().unwrap() as Self),
serde_json::Value::Number(n) => Ok(n.as_f64().unwrap() as Self),
_ => {
return Err(QueryError::ExpectedJsonType {
expect: <Self as GQLType>::type_name().to_string(),
actual: value,
}
.into())
}
}
}
fn into_json(self) -> Result<serde_json::Value> { fn into_json(self) -> Result<serde_json::Value> {
Ok(self.into()) Ok(self.into())
} }
@ -80,6 +100,19 @@ macro_rules! impl_float_scalars {
} }
} }
fn parse_from_json(value: serde_json::Value) -> Result<Self> {
match value {
serde_json::Value::Number(n) => Ok(n.as_f64().unwrap() as Self),
_ => {
return Err(QueryError::ExpectedJsonType {
expect: <Self as GQLType>::type_name().to_string(),
actual: value,
}
.into())
}
}
}
fn into_json(self) -> Result<serde_json::Value> { fn into_json(self) -> Result<serde_json::Value> {
Ok(self.into()) Ok(self.into())
} }
@ -100,7 +133,20 @@ impl Scalar for String {
Value::String(s) => Ok(s), Value::String(s) => Ok(s),
_ => { _ => {
return Err(QueryError::ExpectedType { return Err(QueryError::ExpectedType {
expect: <Self as Scalar>::type_name().to_string(), expect: <Self as GQLType>::type_name().to_string(),
actual: value,
}
.into())
}
}
}
fn parse_from_json(value: serde_json::Value) -> Result<Self> {
match value {
serde_json::Value::String(s) => Ok(s),
_ => {
return Err(QueryError::ExpectedJsonType {
expect: <Self as GQLType>::type_name().to_string(),
actual: value, actual: value,
} }
.into()) .into())
@ -123,7 +169,20 @@ impl Scalar for bool {
Value::Boolean(n) => Ok(n), Value::Boolean(n) => Ok(n),
_ => { _ => {
return Err(QueryError::ExpectedType { return Err(QueryError::ExpectedType {
expect: <Self as Scalar>::type_name().to_string(), expect: <Self as GQLType>::type_name().to_string(),
actual: value,
}
.into())
}
}
}
fn parse_from_json(value: serde_json::Value) -> Result<Self> {
match value {
serde_json::Value::Bool(n) => Ok(n),
_ => {
return Err(QueryError::ExpectedJsonType {
expect: <Self as GQLType>::type_name().to_string(),
actual: value, actual: value,
} }
.into()) .into())

View File

@ -1,4 +1,5 @@
use crate::{ContextSelectionSet, ErrorWithPosition, QueryError, Result}; use crate::{ContextSelectionSet, ErrorWithPosition, QueryError, Result};
use anyhow::Error;
use graphql_parser::query::Value; use graphql_parser::query::Value;
#[doc(hidden)] #[doc(hidden)]
@ -9,6 +10,7 @@ pub trait GQLType {
#[doc(hidden)] #[doc(hidden)]
pub trait GQLInputValue: GQLType + Sized { pub trait GQLInputValue: GQLType + Sized {
fn parse(value: Value) -> Result<Self>; fn parse(value: Value) -> Result<Self>;
fn parse_from_json(value: serde_json::Value) -> Result<Self>;
} }
#[doc(hidden)] #[doc(hidden)]
@ -42,6 +44,25 @@ impl<T: GQLInputValue> GQLInputValue for Vec<T> {
} }
} }
} }
fn parse_from_json(value: serde_json::Value) -> Result<Self> {
match value {
serde_json::Value::Array(values) => {
let mut result = Vec::new();
for value in values {
result.push(GQLInputValue::parse_from_json(value)?);
}
Ok(result)
}
_ => {
return Err(QueryError::ExpectedJsonType {
expect: Self::type_name(),
actual: value,
}
.into())
}
}
}
} }
#[async_trait::async_trait] #[async_trait::async_trait]
@ -68,6 +89,13 @@ impl<T: GQLInputValue> GQLInputValue for Option<T> {
_ => Ok(Some(GQLInputValue::parse(value)?)), _ => Ok(Some(GQLInputValue::parse(value)?)),
} }
} }
fn parse_from_json(value: serde_json::Value) -> Result<Self> {
match value {
serde_json::Value::Null => Ok(None),
_ => Ok(Some(GQLInputValue::parse_from_json(value)?)),
}
}
} }
#[async_trait::async_trait] #[async_trait::async_trait]

View File

@ -1,4 +1,5 @@
use crate::{QueryError, Scalar, Result, Value}; use crate::{QueryError, Result, Scalar, Value};
use anyhow::Error;
use uuid::Uuid; use uuid::Uuid;
impl Scalar for Uuid { impl Scalar for Uuid {
@ -19,6 +20,19 @@ impl Scalar for Uuid {
} }
} }
fn parse_from_json(value: serde_json::Value) -> Result<Self> {
match value {
serde_json::Value::String(s) => Ok(Uuid::parse_str(&s)?),
_ => {
return Err(QueryError::ExpectedJsonType {
expect: Self::type_name().to_string(),
actual: value,
}
.into())
}
}
}
fn into_json(self) -> Result<serde_json::Value> { fn into_json(self) -> Result<serde_json::Value> {
Ok(self.to_string().into()) Ok(self.to_string().into())
} }