Support default values

This commit is contained in:
sunli 2020-03-04 10:38:07 +08:00
parent d8c7494dec
commit 602f6a656f
21 changed files with 235 additions and 91 deletions

View File

@ -53,23 +53,24 @@
- [X] Non-Null
- [ ] Object
- [ ] Generic Types
- [X] Lifetime cycle
- [X] Lifetime cycle
- [X] Enum
- [X] InputObject
- [ ] Field default value
- [X] Field default value
- [X] Deprecated flag
- [ ] Interface
- [ ] Union
- [ ] Query
- [X] Fields
- [X] Arguments
- [ ] Default value
- [X] Default value
- [X] Deprecated flag
- [X] Alias
- [ ] Fragments
- [ ] Inline fragments
- [X] Operation name
- [X] Variables
- [X] Default value
- [X] Parse value
- [ ] Check type
- [ ] Directives

View File

@ -176,6 +176,12 @@ impl Field {
} else if nv.path.is_ident("default") {
if let syn::Lit::Str(lit) = &nv.lit {
match parse_value(&lit.value()) {
Ok(Value::Variable(_)) => {
return Err(Error::new_spanned(
&nv.lit,
"The default cannot be a variable",
))
}
Ok(value) => default = Some(value),
Err(err) => {
return Err(Error::new_spanned(
@ -406,6 +412,12 @@ impl InputField {
} else if nv.path.is_ident("default") {
if let syn::Lit::Str(lit) = nv.lit {
match parse_value(&lit.value()) {
Ok(Value::Variable(_)) => {
return Err(Error::new_spanned(
&lit,
"The default cannot be a variable",
))
}
Ok(value) => default = Some(value),
Err(err) => {
return Err(Error::new_spanned(

View File

@ -98,11 +98,11 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
}
impl #crate_name::GQLInputValue for #ident {
fn parse(value: #crate_name::Value) -> Option<Self> {
fn parse(value: &#crate_name::Value) -> Option<Self> {
#crate_name::GQLEnum::parse_enum(value)
}
fn parse_from_json(value: #crate_name::serde_json::Value) -> Option<Self> {
fn parse_from_json(value: &#crate_name::serde_json::Value) -> Option<Self> {
#crate_name::GQLEnum::parse_json_enum(value)
}
}

View File

@ -1,5 +1,5 @@
use crate::args;
use crate::utils::get_crate_name;
use crate::utils::{build_value_repr, get_crate_name};
use proc_macro::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, Error, Result};
@ -63,12 +63,45 @@ pub fn generate(object_args: &args::InputObject, input: &DeriveInput) -> Result<
quote! {Some(#s)}
})
.unwrap_or_else(|| quote! {None});
get_fields.push(quote! {
let #ident:#ty = #crate_name::GQLInputValue::parse(obj.remove(#name).unwrap_or(#crate_name::Value::Null))?;
});
get_json_fields.push(quote! {
let #ident:#ty = #crate_name::GQLInputValue::parse_from_json(obj.remove(#name).unwrap_or(#crate_name::serde_json::Value::Null))?;
});
if let Some(default) = &field_args.default {
let default_repr = build_value_repr(&crate_name, default);
get_fields.push(quote! {
let #ident:#ty = {
match obj.get(#name) {
Some(value) => #crate_name::GQLInputValue::parse(value)?,
None => {
let default = #default_repr;
#crate_name::GQLInputValue::parse(&value)?
}
}
};
});
} else {
get_fields.push(quote! {
let #ident:#ty = #crate_name::GQLInputValue::parse(obj.get(#name).unwrap_or(&#crate_name::Value::Null))?;
});
}
if let Some(default) = &field_args.default {
let default_repr = build_value_repr(&crate_name, default);
get_json_fields.push(quote! {
let #ident:#ty = match obj.get(#name) {
None => {
let default_value = #default_repr;
#crate_name::GQLInputValue::parse(&default_value)?
}
Some(value) => {
#crate_name::GQLInputValue::parse_from_json(&value)?
}
};
});
} else {
get_json_fields.push(quote! {
let #ident:#ty = #crate_name::GQLInputValue::parse_from_json(&obj.get(#name).unwrap_or(&#crate_name::serde_json::Value::Null))?;
});
}
fields.push(ident);
schema_fields.push(quote! {
#crate_name::registry::InputValue {
@ -98,10 +131,10 @@ pub fn generate(object_args: &args::InputObject, input: &DeriveInput) -> Result<
}
impl #crate_name::GQLInputValue for #ident {
fn parse(value: #crate_name::Value) -> Option<Self> {
fn parse(value: &#crate_name::Value) -> Option<Self> {
use #crate_name::GQLType;
if let #crate_name::Value::Object(mut obj) = value {
if let #crate_name::Value::Object(obj) = value {
#(#get_fields)*
Some(Self { #(#fields),* })
} else {
@ -109,9 +142,9 @@ pub fn generate(object_args: &args::InputObject, input: &DeriveInput) -> Result<
}
}
fn parse_from_json(value: #crate_name::serde_json::Value) -> Option<Self> {
fn parse_from_json(value: &#crate_name::serde_json::Value) -> Option<Self> {
use #crate_name::GQLType;
if let #crate_name::serde_json::Value::Object(mut obj) = value {
if let #crate_name::serde_json::Value::Object(obj) = value {
#(#get_json_fields)*
Some(Self { #(#fields),* })
} else {

View File

@ -1,5 +1,5 @@
use crate::args;
use crate::utils::get_crate_name;
use crate::utils::{build_value_repr, get_crate_name};
use inflector::Inflector;
use proc_macro::TokenStream;
use proc_macro2::Span;
@ -59,7 +59,7 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
.as_ref()
.map(|s| quote! { Some(#s) })
.unwrap_or_else(|| quote! {None});
let default = arg
let schema_default = arg
.default
.as_ref()
.map(|v| {
@ -69,16 +69,26 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
.unwrap_or_else(|| quote! {None});
decl_params.push(quote! { #snake_case_name: #ty });
let default = match &arg.default {
Some(default) => {
let repr = build_value_repr(&crate_name, &default);
quote! {Some(|| #repr)}
}
None => quote! { None },
};
get_params.push(quote! {
let #snake_case_name: #ty = ctx_field.param_value(#name_str)?;
let #snake_case_name: #ty = ctx_field.param_value(#name_str, #default)?;
});
use_params.push(quote! { #snake_case_name });
schema_args.push(quote! {
#crate_name::registry::InputValue {
name: #name_str,
description: #desc,
ty: <#ty as #crate_name::GQLType>::create_type_info(registry),
default_value: #default,
default_value: #schema_default,
}
});
}

View File

@ -1,3 +1,5 @@
use graphql_parser::parse_query;
use graphql_parser::query::{Definition, OperationDefinition, ParseError, Query, Value};
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::Ident;
@ -12,18 +14,13 @@ pub fn get_crate_name(internal: bool) -> TokenStream {
}
}
pub fn parse_value(
s: &str,
) -> Result<graphql_parser::query::Value, graphql_parser::query::ParseError> {
let mut doc =
graphql_parser::query::parse_query(&format!("query ($a:Int!={}) {{ dummy }}", s))?;
pub fn parse_value(s: &str) -> Result<Value, ParseError> {
let mut doc = parse_query(&format!("query ($a:Int!={}) {{ dummy }}", s))?;
let definition = doc.definitions.remove(0);
if let graphql_parser::query::Definition::Operation(
graphql_parser::query::OperationDefinition::Query(graphql_parser::query::Query {
mut variable_definitions,
..
}),
) = definition
if let Definition::Operation(OperationDefinition::Query(Query {
mut variable_definitions,
..
})) = definition
{
let var = variable_definitions.remove(0);
Ok(var.default_value.unwrap())
@ -31,3 +28,53 @@ pub fn parse_value(
unreachable!()
}
}
pub fn build_value_repr(crate_name: &TokenStream, value: &Value) -> TokenStream {
match value {
Value::Variable(_) => unreachable!(),
Value::Int(n) => {
let n = n.as_i64().unwrap();
quote! { #crate_name::Value::Int((#n as i32).into()) }
}
Value::Float(n) => {
quote! { #crate_name::Value::Float(#n) }
}
Value::String(s) => {
quote! { #crate_name::Value::String(#s.to_string()) }
}
Value::Boolean(n) => {
quote! { #crate_name::Value::Boolean(#n) }
}
Value::Null => {
quote! { #crate_name::Value::Null }
}
Value::Enum(n) => {
quote! { #crate_name::Value::Enum(#n.to_string()) }
}
Value::List(ls) => {
let members = ls
.iter()
.map(|v| build_value_repr(crate_name, v))
.collect::<Vec<_>>();
quote! { #crate_name::Value::List(vec![#(#members),*]) }
}
Value::Object(obj) => {
let members = obj
.iter()
.map(|(n, v)| {
let value = build_value_repr(crate_name, v);
quote! {
obj.insert(#n.to_string(), #value);
}
})
.collect::<Vec<_>>();
quote! {
{
let mut obj = std::collections::BTreeMap::new();
#(#members)*
obj
}
}
}
}
}

View File

@ -10,9 +10,15 @@ struct MyInputObj {
a: i32,
b: i32,
}
#[async_graphql::Object(
field(name = "a", type = "i32"),
field(owned, name = "b", type = "i32"),
field(
owned,
name = "b",
type = "i32",
arg(name = "v", type = "i32", default = "123")
),
field(owned, name = "c", type = "Option<String>")
)]
struct MyObj {
@ -25,8 +31,8 @@ impl MyObjFields for MyObj {
Ok(&self.value)
}
async fn b(&self, ctx: &async_graphql::Context<'_>) -> async_graphql::Result<i32> {
Ok(999)
async fn b(&self, ctx: &async_graphql::Context<'_>, v: i32) -> async_graphql::Result<i32> {
Ok(v)
}
async fn c(&self, ctx: &async_graphql::Context<'_>) -> async_graphql::Result<Option<String>> {
@ -37,14 +43,16 @@ impl MyObjFields for MyObj {
#[async_std::main]
async fn main() {
let schema = async_graphql::Schema::<MyObj, async_graphql::GQLEmptyMutation>::new();
let res = schema
.query(
MyObj { value: 100 },
async_graphql::GQLEmptyMutation,
"{ a b c __schema { types { kind name description } } }",
)
.execute()
.await
.unwrap();
serde_json::to_writer_pretty(std::io::stdout(), &res).unwrap();
for _ in 0..1000 {
let res = schema
.query(
MyObj { value: 100 },
async_graphql::GQLEmptyMutation,
"{ a b c __schema { types { kind name description fields(includeDeprecated: false) { name args { name defaultValue } } } } }",
)
.execute()
.await
.unwrap();
}
// serde_json::to_writer_pretty(std::io::stdout(), &res).unwrap();
}

View File

@ -15,8 +15,8 @@ pub trait GQLType {
#[doc(hidden)]
pub trait GQLInputValue: GQLType + Sized {
fn parse(value: Value) -> Option<Self>;
fn parse_from_json(value: serde_json::Value) -> Option<Self>;
fn parse(value: &Value) -> Option<Self>;
fn parse_from_json(value: &serde_json::Value) -> Option<Self>;
}
#[doc(hidden)]
@ -36,8 +36,8 @@ pub trait Scalar: Sized + Send {
fn description() -> Option<&'static str> {
None
}
fn parse(value: Value) -> Option<Self>;
fn parse_from_json(value: serde_json::Value) -> Option<Self>;
fn parse(value: &Value) -> Option<Self>;
fn parse_from_json(value: &serde_json::Value) -> Option<Self>;
fn to_json(&self) -> Result<serde_json::Value>;
}
@ -55,11 +55,11 @@ impl<T: Scalar> GQLType for T {
}
impl<T: Scalar> GQLInputValue for T {
fn parse(value: Value) -> Option<Self> {
fn parse(value: &Value) -> Option<Self> {
T::parse(value)
}
fn parse_from_json(value: serde_json::Value) -> Option<Self> {
fn parse_from_json(value: &serde_json::Value) -> Option<Self> {
T::parse_from_json(value)
}
}

View File

@ -77,7 +77,11 @@ impl<'a, T> ContextBase<'a, T> {
impl<'a> ContextBase<'a, &'a Field> {
#[doc(hidden)]
pub fn param_value<T: GQLInputValue>(&self, name: &str) -> Result<T> {
pub fn param_value<T: GQLInputValue, F: FnOnce() -> Value>(
&self,
name: &str,
default: Option<F>,
) -> Result<T> {
let value = self
.arguments
.iter()
@ -88,26 +92,42 @@ impl<'a> ContextBase<'a, &'a Field> {
if let Some(Value::Variable(var_name)) = &value {
if let Some(vars) = &self.variables {
if let Some(var_value) = vars.get(&*var_name).cloned() {
let res =
GQLInputValue::parse_from_json(var_value.clone()).ok_or_else(|| {
QueryError::ExpectedJsonType {
expect: T::qualified_type_name(),
actual: var_value,
}
.with_position(self.item.position)
})?;
let res = GQLInputValue::parse_from_json(&var_value).ok_or_else(|| {
QueryError::ExpectedJsonType {
expect: T::qualified_type_name(),
actual: var_value,
}
.with_position(self.item.position)
})?;
return Ok(res);
}
}
if let Some(default) = default {
let value = default();
let res = GQLInputValue::parse(&value).ok_or_else(|| {
QueryError::ExpectedType {
expect: T::qualified_type_name(),
actual: value,
}
.with_position(self.item.position)
})?;
return Ok(res);
}
return Err(QueryError::VarNotDefined {
var_name: var_name.clone(),
}
.into());
};
let value = value.unwrap_or(Value::Null);
let res = GQLInputValue::parse(value.clone()).ok_or_else(|| {
let value = if let (Some(default), None) = (default, &value) {
default()
} else {
value.unwrap_or(Value::Null)
};
let res = GQLInputValue::parse(&value).ok_or_else(|| {
QueryError::ExpectedType {
expect: T::qualified_type_name(),
actual: value,

View File

@ -1,4 +1,4 @@
use crate::model::__Type;
use crate::model::{__InputValue, __Type};
use crate::{registry, Context, Result};
use async_graphql_derive::Object;
@ -7,6 +7,7 @@ use async_graphql_derive::Object;
desc = "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.",
field(name = "name", type = "String", owned),
field(name = "description", type = "Option<String>", owned),
field(name = "args", type = "Vec<__InputValue>", owned),
field(name = "type", resolver = "ty", type = "__Type", owned),
field(name = "isDeprecated", type = "bool", owned),
field(name = "deprecationReason", type = "Option<String>", owned)
@ -27,6 +28,18 @@ impl<'a> __FieldFields for __Field<'a> {
Ok(self.field.description.map(|s| s.to_string()))
}
async fn args<'b>(&'b self, _: &Context<'_>) -> Result<Vec<__InputValue<'b>>> {
Ok(self
.field
.args
.iter()
.map(|input_value| __InputValue {
registry: self.registry,
input_value,
})
.collect())
}
async fn ty<'b>(&'b self, _: &Context<'_>) -> Result<__Type<'b>> {
Ok(__Type {
registry: self.registry,

View File

@ -17,7 +17,7 @@ Depending on the kind of a type, certain fields describe information about that
name = "fields",
type = "Option<Vec<__Field>>",
owned,
arg(name = "includeDeprecated", type = "bool")
arg(name = "includeDeprecated", type = "bool", default="false")
),
field(name = "interfaces", type = "Option<Vec<__Type>>", owned),
field(name = "possibleTypes", type = "Option<Vec<__Type>>", owned),
@ -25,7 +25,7 @@ Depending on the kind of a type, certain fields describe information about that
name = "enumValues",
type = "Option<Vec<__EnumValue>>",
owned,
arg(name = "includeDeprecated", type = "bool")
arg(name = "includeDeprecated", type = "bool", default="false")
),
field(name = "inputFields", type = "Option<Vec<__InputValue>>", owned),
field(name = "ofType", type = "Option<__Type>", owned)

View File

@ -9,16 +9,16 @@ impl Scalar for bool {
Some("The `Boolean` scalar type represents `true` or `false`.")
}
fn parse(value: Value) -> Option<Self> {
fn parse(value: &Value) -> Option<Self> {
match value {
Value::Boolean(n) => Some(n),
Value::Boolean(n) => Some(*n),
_ => None,
}
}
fn parse_from_json(value: serde_json::Value) -> Option<Self> {
fn parse_from_json(value: &serde_json::Value) -> Option<Self> {
match value {
serde_json::Value::Bool(n) => Some(n),
serde_json::Value::Bool(n) => Some(*n),
_ => None,
}
}

View File

@ -6,14 +6,14 @@ impl Scalar for DateTime<Utc> {
"DateTime"
}
fn parse(value: Value) -> Option<Self> {
fn parse(value: &Value) -> Option<Self> {
match value {
Value::String(s) => Some(Utc.datetime_from_str(&s, "%+").ok()?),
_ => None,
}
}
fn parse_from_json(value: serde_json::Value) -> Option<Self> {
fn parse_from_json(value: &serde_json::Value) -> Option<Self> {
match value {
serde_json::Value::String(s) => Some(Utc.datetime_from_str(&s, "%+").ok()?),
_ => None,

View File

@ -12,15 +12,15 @@ 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) -> Option<Self> {
fn parse(value: &Value) -> Option<Self> {
match value {
Value::Int(n) => Some(n.as_i64().unwrap() as Self),
Value::Float(n) => Some(n as Self),
Value::Float(n) => Some(*n as Self),
_ => None
}
}
fn parse_from_json(value: serde_json::Value) -> Option<Self> {
fn parse_from_json(value: &serde_json::Value) -> Option<Self> {
match value {
serde_json::Value::Number(n) => Some(n.as_f64().unwrap() as Self),
_ => None

View File

@ -23,18 +23,18 @@ impl Scalar for ID {
"ID"
}
fn parse(value: Value) -> Option<Self> {
fn parse(value: &Value) -> Option<Self> {
match value {
Value::Int(n) => Some(ID(n.as_i64().unwrap().to_string())),
Value::String(s) => Some(ID(s)),
Value::String(s) => Some(ID(s.clone())),
_ => None,
}
}
fn parse_from_json(value: serde_json::Value) -> Option<Self> {
fn parse_from_json(value: &serde_json::Value) -> Option<Self> {
match value {
serde_json::Value::Number(n) if n.is_i64() => Some(ID(n.as_i64().unwrap().to_string())),
serde_json::Value::String(s) => Some(ID(s)),
serde_json::Value::String(s) => Some(ID(s.clone())),
_ => None,
}
}

View File

@ -12,14 +12,14 @@ 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) -> Option<Self> {
fn parse(value: &Value) -> Option<Self> {
match value {
Value::Int(n) => Some(n.as_i64().unwrap() as Self),
_ => None
}
}
fn parse_from_json(value: serde_json::Value) -> Option<Self> {
fn parse_from_json(value: &serde_json::Value) -> Option<Self> {
match value {
serde_json::Value::Number(n) if n.is_i64() => Some(n.as_i64().unwrap() as Self),
serde_json::Value::Number(n) => Some(n.as_f64().unwrap() as Self),

View File

@ -13,16 +13,16 @@ impl Scalar for String {
Some(STRING_DESC)
}
fn parse(value: Value) -> Option<Self> {
fn parse(value: &Value) -> Option<Self> {
match value {
Value::String(s) => Some(s),
Value::String(s) => Some(s.clone()),
_ => None,
}
}
fn parse_from_json(value: serde_json::Value) -> Option<Self> {
fn parse_from_json(value: &serde_json::Value) -> Option<Self> {
match value {
serde_json::Value::String(s) => Some(s),
serde_json::Value::String(s) => Some(s.clone()),
_ => None,
}
}

View File

@ -6,14 +6,14 @@ impl Scalar for Uuid {
"UUID"
}
fn parse(value: Value) -> Option<Self> {
fn parse(value: &Value) -> Option<Self> {
match value {
Value::String(s) => Some(Uuid::parse_str(&s).ok()?),
_ => None,
}
}
fn parse_from_json(value: serde_json::Value) -> Option<Self> {
fn parse_from_json(value: &serde_json::Value) -> Option<Self> {
match value {
serde_json::Value::String(s) => Some(Uuid::parse_str(&s).ok()?),
_ => None,

View File

@ -12,7 +12,7 @@ pub struct GQLEnumItem<T> {
pub trait GQLEnum: GQLType + Sized + Eq + Send + Copy + Sized + 'static {
fn items() -> &'static [GQLEnumItem<Self>];
fn parse_enum(value: Value) -> Option<Self> {
fn parse_enum(value: &Value) -> Option<Self> {
match value {
Value::Enum(s) => {
let items = Self::items();
@ -27,7 +27,7 @@ pub trait GQLEnum: GQLType + Sized + Eq + Send + Copy + Sized + 'static {
None
}
fn parse_json_enum(value: serde_json::Value) -> Option<Self> {
fn parse_json_enum(value: &serde_json::Value) -> Option<Self> {
match value {
serde_json::Value::String(s) => {
let items = Self::items();

View File

@ -12,7 +12,7 @@ impl<T: GQLType> GQLType for Vec<T> {
}
impl<T: GQLInputValue> GQLInputValue for Vec<T> {
fn parse(value: Value) -> Option<Self> {
fn parse(value: &Value) -> Option<Self> {
match value {
Value::List(values) => {
let mut result = Vec::new();
@ -25,7 +25,7 @@ impl<T: GQLInputValue> GQLInputValue for Vec<T> {
}
}
fn parse_from_json(value: serde_json::Value) -> Option<Self> {
fn parse_from_json(value: &serde_json::Value) -> Option<Self> {
match value {
serde_json::Value::Array(values) => {
let mut result = Vec::new();

View File

@ -16,14 +16,14 @@ impl<T: GQLType> GQLType for Option<T> {
}
impl<T: GQLInputValue> GQLInputValue for Option<T> {
fn parse(value: Value) -> Option<Self> {
fn parse(value: &Value) -> Option<Self> {
match value {
Value::Null => Some(None),
_ => Some(GQLInputValue::parse(value)?),
}
}
fn parse_from_json(value: serde_json::Value) -> Option<Self> {
fn parse_from_json(value: &serde_json::Value) -> Option<Self> {
match value {
serde_json::Value::Null => Some(None),
_ => Some(GQLInputValue::parse_from_json(value)?),