add InputObject

This commit is contained in:
sunli 2020-03-01 21:35:39 +08:00
parent 5030a28361
commit b62abdcd83
12 changed files with 139 additions and 73 deletions

View File

@ -1,12 +1,9 @@
use crate::args; use crate::args;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote; use quote::quote;
use syn::{Data, DeriveInput, Error, Ident, Result}; use syn::{Data, DeriveInput, Error, Result};
pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<TokenStream> { pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<TokenStream> {
let attrs = &input.attrs;
let vis = &input.vis;
let ident = &input.ident; let ident = &input.ident;
let s = match &input.data { let s = match &input.data {
Data::Struct(s) => s, Data::Struct(s) => s,
@ -18,23 +15,44 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
.clone() .clone()
.unwrap_or_else(|| ident.to_string()); .unwrap_or_else(|| ident.to_string());
let mut get_fields = Vec::new();
let mut fields = Vec::new();
for field in &s.fields { for field in &s.fields {
let field_args = args::Field::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();
let ty = &field.ty;
let name = field_args.name.unwrap_or_else(|| ident.to_string());
get_fields.push(quote! {
let #ident:#ty = async_graphql::GQLInputValue::parse(obj.remove(#name).unwrap_or(async_graphql::Value::Null))?;
});
fields.push(ident);
} }
let expanded = quote! { let expanded = quote! {
#input #input
impl async_graphql::Type for #ident { impl async_graphql::GQLType for #ident {
fn type_name() -> String { fn type_name() -> String {
#gql_typename.to_string() #gql_typename.to_string()
} }
} }
impl async_graphql::GQLInputObject for #ident { impl async_graphql::GQLInputValue for #ident {
fn parse(value: async_graphql::Value) -> async_graphql::Result<Self> {
if let async_graphql::Value::Object(mut obj) = value {
#(#get_fields)*
Ok(Self { #(#fields),* })
} else {
Err(async_graphql::QueryError::ExpectedType {
expect: Self::type_name(),
actual: value,
}.into())
}
}
} }
impl async_graphql::GQLInputObject for #ident {}
}; };
println!("{}", expanded.to_string());
Ok(expanded.into()) Ok(expanded.into())
} }

View File

@ -91,10 +91,10 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
#[async_graphql::async_trait::async_trait] #[async_graphql::async_trait::async_trait]
impl async_graphql::GQLOutputValue for #ident { impl async_graphql::GQLOutputValue for #ident {
async fn resolve(self, ctx: &async_graphql::ContextSelectionSet<'_>) -> async_graphql::Result<async_graphql::serde_json::Value> { async fn resolve(self, ctx: &async_graphql::ContextSelectionSet<'_>) -> async_graphql::Result<async_graphql::serde_json::Value> {
use async_graphql::GQLErrorWithPosition; use async_graphql::ErrorWithPosition;
if ctx.items.is_empty() { if ctx.items.is_empty() {
async_graphql::anyhow::bail!(async_graphql::GQLQueryError::MustHaveSubFields { async_graphql::anyhow::bail!(async_graphql::QueryError::MustHaveSubFields {
object: #gql_typename, object: #gql_typename,
}.with_position(ctx.span.0)); }.with_position(ctx.span.0));
} }
@ -109,7 +109,7 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
continue; continue;
} }
#(#resolvers)* #(#resolvers)*
async_graphql::anyhow::bail!(async_graphql::GQLQueryError::FieldNotFound { async_graphql::anyhow::bail!(async_graphql::QueryError::FieldNotFound {
field_name: field.name.clone(), field_name: field.name.clone(),
object: #gql_typename, object: #gql_typename,
}.with_position(field.position)); }.with_position(field.position));

View File

@ -6,6 +6,12 @@ enum MyEnum {
B, B,
} }
#[async_graphql::InputObject]
struct MyInputObj {
a: i32,
b: i32,
}
#[async_graphql::Object(name = "haha", desc = "hehe")] #[async_graphql::Object(name = "haha", desc = "hehe")]
struct MyObj { struct MyObj {
#[field( #[field(
@ -22,6 +28,9 @@ struct MyObj {
#[field(arg(name = "input", type = "MyEnum"))] #[field(arg(name = "input", type = "MyEnum"))]
c: MyEnum, c: MyEnum,
#[field(arg(name = "input", type = "MyInputObj"))]
d: i32,
#[field] #[field]
child: ChildObj, child: ChildObj,
} }
@ -46,6 +55,10 @@ impl MyObjFields for MyObj {
Ok(input) Ok(input)
} }
async fn d(&self, ctx: &ContextField<'_>, input: MyInputObj) -> Result<i32> {
Ok(input.a + input.b)
}
async fn child(&self, ctx: &ContextField<'_>) -> async_graphql::Result<ChildObj> { async fn child(&self, ctx: &ContextField<'_>) -> async_graphql::Result<ChildObj> {
Ok(ChildObj { value: 10.0 }) Ok(ChildObj { value: 10.0 })
} }
@ -60,9 +73,13 @@ impl ChildObjFields for ChildObj {
#[async_std::main] #[async_std::main]
async fn main() { async fn main() {
let res = GQLQueryBuilder::new(MyObj { a: 10 }, GQLEmptyMutation, "{ b c(input:B) }") let res = QueryBuilder::new(
.execute() MyObj { a: 10 },
.await GQLEmptyMutation,
.unwrap(); "{ b c(input:B) d(input:{a:10 b:20}) }",
)
.execute()
.await
.unwrap();
serde_json::to_writer_pretty(std::io::stdout(), &res).unwrap(); serde_json::to_writer_pretty(std::io::stdout(), &res).unwrap();
} }

View File

@ -1,4 +1,4 @@
use crate::{GQLInputValue, Result}; use crate::{ErrorWithPosition, GQLInputValue, QueryError, Result};
use fnv::FnvHasher; use fnv::FnvHasher;
use graphql_parser::query::{Field, SelectionSet, Value}; use graphql_parser::query::{Field, SelectionSet, Value};
use std::any::{Any, TypeId}; use std::any::{Any, TypeId};
@ -80,6 +80,26 @@ impl<'a> Context<'a, &'a Field> {
.map(|(_, v)| v) .map(|(_, v)| v)
.cloned() .cloned()
.unwrap_or(Value::Null); .unwrap_or(Value::Null);
GQLInputValue::parse(value) let value = match (value, &self.variables) {
(Value::Variable(name), Some(vars)) => match vars.get(&name).cloned() {
Some(value) => value,
None => {
return Err(QueryError::VarNotDefined {
var_name: name.clone(),
}
.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))?;
Ok(res)
} }
} }

View File

@ -1,7 +1,7 @@
use crate::{GQLQueryError, GQLScalar, Result, Value}; use crate::{QueryError, Scalar, Result, Value};
use chrono::{DateTime, TimeZone, Utc}; use chrono::{DateTime, TimeZone, Utc};
impl GQLScalar for DateTime<Utc> { impl Scalar for DateTime<Utc> {
fn type_name() -> &'static str { fn type_name() -> &'static str {
"DateTime" "DateTime"
} }
@ -10,7 +10,7 @@ impl GQLScalar for DateTime<Utc> {
match value { match value {
Value::String(s) => Ok(Utc.datetime_from_str(&s, "%+")?), Value::String(s) => Ok(Utc.datetime_from_str(&s, "%+")?),
_ => { _ => {
return Err(GQLQueryError::ExpectedType { return Err(QueryError::ExpectedType {
expect: Self::type_name().to_string(), expect: Self::type_name().to_string(),
actual: value, actual: value,
} }

View File

@ -1,4 +1,4 @@
use crate::{GQLQueryError, GQLType, Result}; use crate::{QueryError, GQLType, Result};
use graphql_parser::query::Value; use graphql_parser::query::Value;
#[doc(hidden)] #[doc(hidden)]
@ -22,14 +22,14 @@ pub trait GQLEnum: GQLType + Sized + Eq + Send + Copy + Sized + 'static {
return Ok(item.value); return Ok(item.value);
} }
} }
Err(GQLQueryError::InvalidEnumValue { Err(QueryError::InvalidEnumValue {
enum_type: Self::type_name(), enum_type: Self::type_name(),
value: s, value: s,
} }
.into()) .into())
} }
_ => { _ => {
return Err(GQLQueryError::ExpectedType { return Err(QueryError::ExpectedType {
expect: Self::type_name(), expect: Self::type_name(),
actual: value, actual: value,
} }

View File

@ -5,10 +5,10 @@ use std::fmt::{Debug, Display, Formatter};
#[derive(Debug, Error)] #[derive(Debug, Error)]
#[error("{0}")] #[error("{0}")]
pub struct GQLQueryParseError(pub(crate) String); pub struct QueryParseError(pub(crate) String);
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum GQLQueryError { pub enum QueryError {
#[error("Not supported.")] #[error("Not supported.")]
NotSupported, NotSupported,
@ -30,20 +30,29 @@ pub enum GQLQueryError {
#[error("Schema is not configured for mutations.")] #[error("Schema is not configured for mutations.")]
NotConfiguredMutations, NotConfiguredMutations,
#[error("Invalid value for enum \"{enum_type}\"")] #[error("Invalid value for enum \"{enum_type}\".")]
InvalidEnumValue { enum_type: String, value: String }, InvalidEnumValue { enum_type: String, value: String },
#[error("Required field \"{field_name}\" for InputObject \"{object}\" does not exist.")]
RequiredField {
field_name: String,
object: &'static str,
},
#[error("Variable \"${var_name}\" is not defined")]
VarNotDefined { var_name: String },
} }
pub trait GQLErrorWithPosition { pub trait ErrorWithPosition {
type Result; type Result;
fn with_position(self, position: Pos) -> GQLPositionError; fn with_position(self, position: Pos) -> PositionError;
} }
impl<T: Into<Error>> GQLErrorWithPosition for T { impl<T: Into<Error>> ErrorWithPosition for T {
type Result = GQLPositionError; type Result = PositionError;
fn with_position(self, position: Pos) -> GQLPositionError { fn with_position(self, position: Pos) -> PositionError {
GQLPositionError { PositionError {
position, position,
inner: self.into(), inner: self.into(),
} }
@ -51,12 +60,12 @@ impl<T: Into<Error>> GQLErrorWithPosition for T {
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub struct GQLPositionError { pub struct PositionError {
pub position: Pos, pub position: Pos,
pub inner: Error, pub inner: Error,
} }
impl GQLPositionError { impl PositionError {
pub fn new(position: Pos, inner: Error) -> Self { pub fn new(position: Pos, inner: Error) -> Self {
Self { position, inner } Self { position, inner }
} }
@ -66,7 +75,7 @@ impl GQLPositionError {
} }
} }
impl Display for GQLPositionError { impl Display for PositionError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!( write!(
f, f,

View File

@ -22,14 +22,16 @@ pub use serde_json;
pub use async_graphql_derive::{Enum, InputObject, Object}; pub use async_graphql_derive::{Enum, InputObject, Object};
pub use context::{Context, ContextField, ContextSelectionSet, Data, Variables}; pub use context::{Context, ContextField, ContextSelectionSet, Data, Variables};
pub use error::{GQLErrorWithPosition, GQLPositionError, GQLQueryError, GQLQueryParseError}; pub use error::{ErrorWithPosition, PositionError, QueryError, QueryParseError};
pub use graphql_parser::query::Value; pub use graphql_parser::query::Value;
pub use query::GQLQueryBuilder; pub use query::QueryBuilder;
pub use scalar::Scalar;
// internal types
pub use r#enum::{GQLEnum, GQLEnumItem}; pub use r#enum::{GQLEnum, GQLEnumItem};
pub use r#type::{ pub use r#type::{
GQLEmptyMutation, GQLInputObject, GQLInputValue, GQLObject, GQLOutputValue, GQLType, GQLEmptyMutation, GQLInputObject, GQLInputValue, GQLObject, GQLOutputValue, GQLType,
}; };
pub use scalar::GQLScalar;
pub type Result<T> = anyhow::Result<T>; pub type Result<T> = anyhow::Result<T>;
pub type Error = anyhow::Error; pub type Error = anyhow::Error;

View File

@ -1,11 +1,11 @@
use crate::{ use crate::{
Context, Data, GQLErrorWithPosition, GQLObject, GQLQueryError, GQLQueryParseError, Result, Context, Data, ErrorWithPosition, GQLObject, QueryError, QueryParseError, Result,
Variables, Variables,
}; };
use graphql_parser::parse_query; use graphql_parser::parse_query;
use graphql_parser::query::{Definition, OperationDefinition}; use graphql_parser::query::{Definition, OperationDefinition};
pub struct GQLQueryBuilder<'a, Query, Mutation> { pub struct QueryBuilder<'a, Query, Mutation> {
query: Query, query: Query,
mutation: Mutation, mutation: Mutation,
query_source: &'a str, query_source: &'a str,
@ -14,7 +14,7 @@ pub struct GQLQueryBuilder<'a, Query, Mutation> {
data: Option<&'a Data>, data: Option<&'a Data>,
} }
impl<'a, Query, Mutation> GQLQueryBuilder<'a, Query, Mutation> { impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> {
pub fn new(query: Query, mutation: Mutation, query_source: &'a str) -> Self { pub fn new(query: Query, mutation: Mutation, query_source: &'a str) -> Self {
Self { Self {
query, query,
@ -27,21 +27,21 @@ impl<'a, Query, Mutation> GQLQueryBuilder<'a, Query, Mutation> {
} }
pub fn operator_name(self, name: &'a str) -> Self { pub fn operator_name(self, name: &'a str) -> Self {
GQLQueryBuilder { QueryBuilder {
operation_name: Some(name), operation_name: Some(name),
..self ..self
} }
} }
pub fn variables(self, vars: &'a Variables) -> Self { pub fn variables(self, vars: &'a Variables) -> Self {
GQLQueryBuilder { QueryBuilder {
variables: Some(vars), variables: Some(vars),
..self ..self
} }
} }
pub fn data(self, data: &'a Data) -> Self { pub fn data(self, data: &'a Data) -> Self {
GQLQueryBuilder { QueryBuilder {
data: Some(data), data: Some(data),
..self ..self
} }
@ -53,7 +53,7 @@ impl<'a, Query, Mutation> GQLQueryBuilder<'a, Query, Mutation> {
Mutation: GQLObject, Mutation: GQLObject,
{ {
let document = let document =
parse_query(self.query_source).map_err(|err| GQLQueryParseError(err.to_string()))?; parse_query(self.query_source).map_err(|err| QueryParseError(err.to_string()))?;
for definition in &document.definitions { for definition in &document.definitions {
match definition { match definition {
@ -92,16 +92,16 @@ impl<'a, Query, Mutation> GQLQueryBuilder<'a, Query, Mutation> {
} }
} }
Definition::Operation(OperationDefinition::Subscription(subscription)) => { Definition::Operation(OperationDefinition::Subscription(subscription)) => {
anyhow::bail!(GQLQueryError::NotSupported.with_position(subscription.position)); anyhow::bail!(QueryError::NotSupported.with_position(subscription.position));
} }
Definition::Fragment(fragment) => { Definition::Fragment(fragment) => {
anyhow::bail!(GQLQueryError::NotSupported.with_position(fragment.position)); anyhow::bail!(QueryError::NotSupported.with_position(fragment.position));
} }
} }
} }
if let Some(operation_name) = self.operation_name { if let Some(operation_name) = self.operation_name {
anyhow::bail!(GQLQueryError::UnknownOperationNamed { anyhow::bail!(QueryError::UnknownOperationNamed {
name: operation_name.to_string() name: operation_name.to_string()
}); });
} }

View File

@ -1,27 +1,27 @@
use crate::r#type::{GQLInputValue, GQLOutputValue, GQLType}; use crate::r#type::{GQLInputValue, GQLOutputValue, GQLType};
use crate::{ContextSelectionSet, GQLQueryError, Result}; use crate::{ContextSelectionSet, QueryError, Result};
use graphql_parser::query::Value; use graphql_parser::query::Value;
pub trait GQLScalar: 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 into_json(self) -> Result<serde_json::Value>; fn into_json(self) -> Result<serde_json::Value>;
} }
impl<T: GQLScalar> GQLType for T { impl<T: Scalar> GQLType for T {
fn type_name() -> String { fn type_name() -> String {
T::type_name().to_string() T::type_name().to_string()
} }
} }
impl<T: GQLScalar> GQLInputValue for T { impl<T: Scalar> GQLInputValue for T {
fn parse(value: Value) -> Result<Self> { fn parse(value: Value) -> Result<Self> {
T::parse(value) T::parse(value)
} }
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<T: GQLScalar> GQLOutputValue for T { impl<T: Scalar> GQLOutputValue for T {
async fn resolve(self, _: &ContextSelectionSet<'_>) -> Result<serde_json::Value> { async fn resolve(self, _: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
T::into_json(self) T::into_json(self)
} }
@ -30,7 +30,7 @@ impl<T: GQLScalar> GQLOutputValue for T {
macro_rules! impl_integer_scalars { macro_rules! impl_integer_scalars {
($($ty:ty),*) => { ($($ty:ty),*) => {
$( $(
impl GQLScalar for $ty { impl Scalar for $ty {
fn type_name() -> &'static str { fn type_name() -> &'static str {
"Int!" "Int!"
} }
@ -39,8 +39,8 @@ macro_rules! impl_integer_scalars {
match value { match value {
Value::Int(n) => Ok(n.as_i64().unwrap() as Self), Value::Int(n) => Ok(n.as_i64().unwrap() as Self),
_ => { _ => {
return Err(GQLQueryError::ExpectedType { return Err(QueryError::ExpectedType {
expect: <Self as GQLScalar>::type_name().to_string(), expect: <Self as Scalar>::type_name().to_string(),
actual: value, actual: value,
} }
.into()) .into())
@ -61,7 +61,7 @@ impl_integer_scalars!(i8, i16, i32, i64, u8, u16, u32, u64);
macro_rules! impl_float_scalars { macro_rules! impl_float_scalars {
($($ty:ty),*) => { ($($ty:ty),*) => {
$( $(
impl GQLScalar for $ty { impl Scalar for $ty {
fn type_name() -> &'static str { fn type_name() -> &'static str {
"Float!" "Float!"
} }
@ -71,8 +71,8 @@ macro_rules! impl_float_scalars {
Value::Int(n) => Ok(n.as_i64().unwrap() as Self), Value::Int(n) => Ok(n.as_i64().unwrap() as Self),
Value::Float(n) => Ok(n as Self), Value::Float(n) => Ok(n as Self),
_ => { _ => {
return Err(GQLQueryError::ExpectedType { return Err(QueryError::ExpectedType {
expect: <Self as GQLScalar>::type_name().to_string(), expect: <Self as Scalar>::type_name().to_string(),
actual: value, actual: value,
} }
.into()) .into())
@ -90,7 +90,7 @@ macro_rules! impl_float_scalars {
impl_float_scalars!(f32, f64); impl_float_scalars!(f32, f64);
impl GQLScalar for String { impl Scalar for String {
fn type_name() -> &'static str { fn type_name() -> &'static str {
"String!" "String!"
} }
@ -99,8 +99,8 @@ impl GQLScalar for String {
match value { match value {
Value::String(s) => Ok(s), Value::String(s) => Ok(s),
_ => { _ => {
return Err(GQLQueryError::ExpectedType { return Err(QueryError::ExpectedType {
expect: <Self as GQLScalar>::type_name().to_string(), expect: <Self as Scalar>::type_name().to_string(),
actual: value, actual: value,
} }
.into()) .into())
@ -113,7 +113,7 @@ impl GQLScalar for String {
} }
} }
impl GQLScalar for bool { impl Scalar for bool {
fn type_name() -> &'static str { fn type_name() -> &'static str {
"Boolean!" "Boolean!"
} }
@ -122,8 +122,8 @@ impl GQLScalar for bool {
match value { match value {
Value::Boolean(n) => Ok(n), Value::Boolean(n) => Ok(n),
_ => { _ => {
return Err(GQLQueryError::ExpectedType { return Err(QueryError::ExpectedType {
expect: <Self as GQLScalar>::type_name().to_string(), expect: <Self as Scalar>::type_name().to_string(),
actual: value, actual: value,
} }
.into()) .into())

View File

@ -1,4 +1,4 @@
use crate::{ContextSelectionSet, GQLErrorWithPosition, GQLQueryError, Result}; use crate::{ContextSelectionSet, ErrorWithPosition, QueryError, Result};
use graphql_parser::query::Value; use graphql_parser::query::Value;
#[doc(hidden)] #[doc(hidden)]
@ -34,7 +34,7 @@ impl<T: GQLInputValue> GQLInputValue for Vec<T> {
Ok(result) Ok(result)
} }
_ => { _ => {
return Err(GQLQueryError::ExpectedType { return Err(QueryError::ExpectedType {
expect: Self::type_name(), expect: Self::type_name(),
actual: value, actual: value,
} }
@ -82,7 +82,7 @@ impl<T: GQLOutputValue + Send> GQLOutputValue for Option<T> {
} }
#[doc(hidden)] #[doc(hidden)]
pub trait GQLObject: GQLType + GQLOutputValue {} pub trait GQLObject: GQLOutputValue {}
pub struct GQLEmptyMutation; pub struct GQLEmptyMutation;
@ -95,11 +95,11 @@ impl GQLType for GQLEmptyMutation {
#[async_trait::async_trait] #[async_trait::async_trait]
impl GQLOutputValue for GQLEmptyMutation { impl GQLOutputValue for GQLEmptyMutation {
async fn resolve(self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> { async fn resolve(self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
anyhow::bail!(GQLQueryError::NotConfiguredMutations.with_position(ctx.item.span.0)); anyhow::bail!(QueryError::NotConfiguredMutations.with_position(ctx.item.span.0));
} }
} }
impl GQLObject for GQLEmptyMutation {} impl GQLObject for GQLEmptyMutation {}
#[doc(hidden)] #[doc(hidden)]
pub trait GQLInputObject: GQLType + GQLInputValue {} pub trait GQLInputObject: GQLInputValue {}

View File

@ -1,7 +1,7 @@
use crate::{GQLQueryError, GQLScalar, Result, Value}; use crate::{QueryError, Scalar, Result, Value};
use uuid::Uuid; use uuid::Uuid;
impl GQLScalar for Uuid { impl Scalar for Uuid {
fn type_name() -> &'static str { fn type_name() -> &'static str {
"UUID" "UUID"
} }
@ -10,7 +10,7 @@ impl GQLScalar for Uuid {
match value { match value {
Value::String(s) => Ok(Uuid::parse_str(&s)?), Value::String(s) => Ok(Uuid::parse_str(&s)?),
_ => { _ => {
return Err(GQLQueryError::ExpectedType { return Err(QueryError::ExpectedType {
expect: Self::type_name().to_string(), expect: Self::type_name().to_string(),
actual: value, actual: value,
} }