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 proc_macro::TokenStream;
use proc_macro2::Span;
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> {
let attrs = &input.attrs;
let vis = &input.vis;
let ident = &input.ident;
let s = match &input.data {
Data::Struct(s) => s,
@ -18,23 +15,44 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
.clone()
.unwrap_or_else(|| ident.to_string());
let mut get_fields = Vec::new();
let mut fields = Vec::new();
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 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! {
#input
impl async_graphql::Type for #ident {
impl async_graphql::GQLType for #ident {
fn type_name() -> 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())
}

View File

@ -91,10 +91,10 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
#[async_graphql::async_trait::async_trait]
impl async_graphql::GQLOutputValue for #ident {
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() {
async_graphql::anyhow::bail!(async_graphql::GQLQueryError::MustHaveSubFields {
async_graphql::anyhow::bail!(async_graphql::QueryError::MustHaveSubFields {
object: #gql_typename,
}.with_position(ctx.span.0));
}
@ -109,7 +109,7 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
continue;
}
#(#resolvers)*
async_graphql::anyhow::bail!(async_graphql::GQLQueryError::FieldNotFound {
async_graphql::anyhow::bail!(async_graphql::QueryError::FieldNotFound {
field_name: field.name.clone(),
object: #gql_typename,
}.with_position(field.position));

View File

@ -6,6 +6,12 @@ enum MyEnum {
B,
}
#[async_graphql::InputObject]
struct MyInputObj {
a: i32,
b: i32,
}
#[async_graphql::Object(name = "haha", desc = "hehe")]
struct MyObj {
#[field(
@ -22,6 +28,9 @@ struct MyObj {
#[field(arg(name = "input", type = "MyEnum"))]
c: MyEnum,
#[field(arg(name = "input", type = "MyInputObj"))]
d: i32,
#[field]
child: ChildObj,
}
@ -46,6 +55,10 @@ impl MyObjFields for MyObj {
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> {
Ok(ChildObj { value: 10.0 })
}
@ -60,9 +73,13 @@ impl ChildObjFields for ChildObj {
#[async_std::main]
async fn main() {
let res = GQLQueryBuilder::new(MyObj { a: 10 }, GQLEmptyMutation, "{ b c(input:B) }")
.execute()
.await
.unwrap();
let res = QueryBuilder::new(
MyObj { a: 10 },
GQLEmptyMutation,
"{ b c(input:B) d(input:{a:10 b:20}) }",
)
.execute()
.await
.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 graphql_parser::query::{Field, SelectionSet, Value};
use std::any::{Any, TypeId};
@ -80,6 +80,26 @@ impl<'a> Context<'a, &'a Field> {
.map(|(_, v)| v)
.cloned()
.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};
impl GQLScalar for DateTime<Utc> {
impl Scalar for DateTime<Utc> {
fn type_name() -> &'static str {
"DateTime"
}
@ -10,7 +10,7 @@ impl GQLScalar for DateTime<Utc> {
match value {
Value::String(s) => Ok(Utc.datetime_from_str(&s, "%+")?),
_ => {
return Err(GQLQueryError::ExpectedType {
return Err(QueryError::ExpectedType {
expect: Self::type_name().to_string(),
actual: value,
}

View File

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

View File

@ -5,10 +5,10 @@ use std::fmt::{Debug, Display, Formatter};
#[derive(Debug, Error)]
#[error("{0}")]
pub struct GQLQueryParseError(pub(crate) String);
pub struct QueryParseError(pub(crate) String);
#[derive(Debug, Error)]
pub enum GQLQueryError {
pub enum QueryError {
#[error("Not supported.")]
NotSupported,
@ -30,20 +30,29 @@ pub enum GQLQueryError {
#[error("Schema is not configured for mutations.")]
NotConfiguredMutations,
#[error("Invalid value for enum \"{enum_type}\"")]
#[error("Invalid value for enum \"{enum_type}\".")]
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;
fn with_position(self, position: Pos) -> GQLPositionError;
fn with_position(self, position: Pos) -> PositionError;
}
impl<T: Into<Error>> GQLErrorWithPosition for T {
type Result = GQLPositionError;
impl<T: Into<Error>> ErrorWithPosition for T {
type Result = PositionError;
fn with_position(self, position: Pos) -> GQLPositionError {
GQLPositionError {
fn with_position(self, position: Pos) -> PositionError {
PositionError {
position,
inner: self.into(),
}
@ -51,12 +60,12 @@ impl<T: Into<Error>> GQLErrorWithPosition for T {
}
#[derive(Debug, Error)]
pub struct GQLPositionError {
pub struct PositionError {
pub position: Pos,
pub inner: Error,
}
impl GQLPositionError {
impl PositionError {
pub fn new(position: Pos, inner: Error) -> Self {
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 {
write!(
f,

View File

@ -22,14 +22,16 @@ pub use serde_json;
pub use async_graphql_derive::{Enum, InputObject, Object};
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 query::GQLQueryBuilder;
pub use query::QueryBuilder;
pub use scalar::Scalar;
// internal types
pub use r#enum::{GQLEnum, GQLEnumItem};
pub use r#type::{
GQLEmptyMutation, GQLInputObject, GQLInputValue, GQLObject, GQLOutputValue, GQLType,
};
pub use scalar::GQLScalar;
pub type Result<T> = anyhow::Result<T>;
pub type Error = anyhow::Error;

View File

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

View File

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

View File

@ -1,4 +1,4 @@
use crate::{ContextSelectionSet, GQLErrorWithPosition, GQLQueryError, Result};
use crate::{ContextSelectionSet, ErrorWithPosition, QueryError, Result};
use graphql_parser::query::Value;
#[doc(hidden)]
@ -34,7 +34,7 @@ impl<T: GQLInputValue> GQLInputValue for Vec<T> {
Ok(result)
}
_ => {
return Err(GQLQueryError::ExpectedType {
return Err(QueryError::ExpectedType {
expect: Self::type_name(),
actual: value,
}
@ -82,7 +82,7 @@ impl<T: GQLOutputValue + Send> GQLOutputValue for Option<T> {
}
#[doc(hidden)]
pub trait GQLObject: GQLType + GQLOutputValue {}
pub trait GQLObject: GQLOutputValue {}
pub struct GQLEmptyMutation;
@ -95,11 +95,11 @@ impl GQLType for GQLEmptyMutation {
#[async_trait::async_trait]
impl GQLOutputValue for GQLEmptyMutation {
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 {}
#[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;
impl GQLScalar for Uuid {
impl Scalar for Uuid {
fn type_name() -> &'static str {
"UUID"
}
@ -10,7 +10,7 @@ impl GQLScalar for Uuid {
match value {
Value::String(s) => Ok(Uuid::parse_str(&s)?),
_ => {
return Err(GQLQueryError::ExpectedType {
return Err(QueryError::ExpectedType {
expect: Self::type_name().to_string(),
actual: value,
}