Rework async-graphql-derive. #288

This commit is contained in:
Sunli 2020-09-28 17:44:00 +08:00
parent 97ffe25f9f
commit e60864a18d
62 changed files with 1344 additions and 1873 deletions

View File

@ -99,7 +99,7 @@ impl Chat {
User
}
#[field(name = "created_at")]
#[graphql(name = "created_at")]
pub async fn created_at(&self) -> &String {
&CHAT.created_at
}
@ -151,14 +151,14 @@ impl User {
Some(UserProfile)
}
#[field(name = "is_operator")]
#[graphql(name = "is_operator")]
pub async fn is_operator(&self) -> bool {
USER.is_operator
}
pub async fn phone(&self) -> String {
USER.phone.to_string()
}
#[field(name = "join_date")]
#[graphql(name = "join_date")]
pub async fn join_date(&self) -> &String {
&USER.join_date
}
@ -174,15 +174,15 @@ impl UserProfile {
pub async fn email(&self) -> &String {
&PROFILE.email
}
#[field(name = "first_name")]
#[graphql(name = "first_name")]
pub async fn first_name(&self) -> &String {
&PROFILE.first_name
}
#[field(name = "last_name")]
#[graphql(name = "last_name")]
pub async fn last_name(&self) -> &String {
&PROFILE.last_name
}
#[field(name = "job_title")]
#[graphql(name = "job_title")]
pub async fn job_title(&self) -> &Option<String> {
&PROFILE.job_title
}

View File

@ -4,12 +4,10 @@ pub struct QueryRoot;
#[Object]
impl QueryRoot {
#[field]
async fn value_i32(&self) -> i32 {
999
}
#[field]
async fn obj(&self) -> MyObj {
MyObj
}
@ -19,17 +17,14 @@ pub struct MyObj;
#[Object]
impl MyObj {
#[field]
async fn value_i32(&self) -> i32 {
999
}
#[field]
async fn value_list(&self) -> &[i32] {
&[1, 2, 3, 4, 5, 6, 7, 8, 9]
}
#[field]
async fn obj(&self) -> MyObj {
MyObj
}

View File

@ -23,3 +23,5 @@ quote = "1.0.3"
Inflector = "0.11.4"
proc-macro-crate = "0.1.4"
itertools = "0.9.0"
darling = "0.10"
thiserror = "1.0"

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +1,23 @@
use crate::args;
use crate::utils::{get_crate_name, get_rustdoc};
use crate::utils::{get_crate_name, get_rustdoc, GeneratorResult};
use darling::ast::Data;
use inflector::Inflector;
use proc_macro::TokenStream;
use quote::quote;
use syn::ext::IdentExt;
use syn::{Data, DeriveInput, Error, Result};
use syn::Error;
pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStream> {
pub fn generate(enum_args: &args::Enum) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(enum_args.internal);
let ident = &input.ident;
let e = match &input.data {
let ident = &enum_args.ident;
let e = match &enum_args.data {
Data::Enum(e) => e,
_ => return Err(Error::new_spanned(input, "It should be a enum")),
_ => return Err(Error::new_spanned(ident, "Enum can only be applied to an enum.").into()),
};
let gql_typename = enum_args.name.clone().unwrap_or_else(|| ident.to_string());
let desc = enum_args
.desc
.clone()
.or_else(|| get_rustdoc(&input.attrs).ok().flatten())
let desc = get_rustdoc(&enum_args.attrs)?
.map(|s| quote! { Some(#s) })
.unwrap_or_else(|| quote! {None});
@ -27,33 +25,33 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
let mut items = Vec::new();
let mut schema_enum_items = Vec::new();
for variant in &e.variants {
for variant in e {
if !variant.fields.is_empty() {
return Err(Error::new_spanned(
&variant,
&variant.ident,
format!(
"Invalid enum variant {}.\nGraphQL enums may only contain unit variants.",
variant.ident
),
));
)
.into());
}
let item_ident = &variant.ident;
let mut item_args = args::EnumItem::parse(&variant.attrs)?;
let gql_item_name = item_args
let gql_item_name = variant
.name
.clone()
.take()
.unwrap_or_else(|| variant.ident.unraw().to_string().to_screaming_snake_case());
let item_deprecation = item_args
let item_deprecation = variant
.deprecation
.as_ref()
.map(|s| quote! { Some(#s) })
.unwrap_or_else(|| quote! {None});
let item_desc = item_args
.desc
.as_ref()
let item_desc = get_rustdoc(&variant.attrs)?
.map(|s| quote! { Some(#s) })
.unwrap_or_else(|| quote! {None});
enum_items.push(item_ident);
items.push(quote! {
#crate_name::resolver_utils::EnumItem {
@ -74,10 +72,9 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
let remote_ty = if let Ok(ty) = syn::parse_str::<syn::Type>(remote) {
ty
} else {
return Err(Error::new_spanned(
remote,
format!("Invalid remote type: '{}'", remote),
));
return Err(
Error::new_spanned(remote, format!("Invalid remote type: '{}'", remote)).into(),
);
};
let local_to_remote_items = enum_items.iter().map(|item| {

View File

@ -1,17 +1,24 @@
use crate::args;
use crate::utils::{get_crate_name, get_rustdoc};
use crate::utils::{
generate_default, generate_validator, get_crate_name, get_rustdoc, GeneratorResult,
};
use darling::ast::Data;
use inflector::Inflector;
use proc_macro::TokenStream;
use quote::quote;
use syn::ext::IdentExt;
use syn::{Data, DeriveInput, Error, Result};
use syn::Error;
pub fn generate(object_args: &args::InputObject, input: &DeriveInput) -> Result<TokenStream> {
pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(object_args.internal);
let ident = &input.ident;
let s = match &input.data {
let ident = &object_args.ident;
let s = match &object_args.data {
Data::Struct(s) => s,
_ => return Err(Error::new_spanned(input, "It should be a struct.")),
_ => {
return Err(
Error::new_spanned(ident, "InputObject can only be applied to an struct.").into(),
)
}
};
let mut struct_fields = Vec::new();
@ -35,10 +42,7 @@ pub fn generate(object_args: &args::InputObject, input: &DeriveInput) -> Result<
.clone()
.unwrap_or_else(|| ident.to_string());
let desc = object_args
.desc
.clone()
.or_else(|| get_rustdoc(&input.attrs).ok().flatten())
let desc = get_rustdoc(&object_args.attrs)?
.map(|s| quote! { Some(#s) })
.unwrap_or_else(|| quote! {None});
@ -49,14 +53,14 @@ pub fn generate(object_args: &args::InputObject, input: &DeriveInput) -> Result<
let mut flatten_fields = Vec::new();
for field in &s.fields {
let field_args = args::InputField::parse(&crate_name, &field.attrs)?;
let ident = field.ident.as_ref().unwrap();
let ty = &field.ty;
let name = field_args
let name = field
.name
.clone()
.unwrap_or_else(|| ident.unraw().to_string().to_camel_case());
if field_args.flatten {
if field.flatten {
flatten_fields.push((ident, ty));
schema_fields.push(quote! {
@ -82,21 +86,25 @@ pub fn generate(object_args: &args::InputObject, input: &DeriveInput) -> Result<
continue;
}
let validator = &field_args.validator;
let desc = field_args
.desc
.as_ref()
.map(|s| quote! {Some(#s)})
let validator = match &field.validator {
Some(meta) => {
let stream = generate_validator(&crate_name, meta)?;
quote!(Some(#stream))
}
None => quote!(None),
};
let desc = get_rustdoc(&field.attrs)?
.map(|s| quote! { Some(#s) })
.unwrap_or_else(|| quote! {None});
let schema_default = field_args
.default
let default = generate_default(&field.default, &field.default_with)?;
let schema_default = default
.as_ref()
.map(|value| {
quote! {Some( <#ty as #crate_name::InputValueType>::to_value(&#value).to_string() )}
})
.unwrap_or_else(|| quote! {None});
if let Some(default) = &field_args.default {
if let Some(default) = default {
get_fields.push(quote! {
let #ident: #ty = {
match obj.get(#name) {

View File

@ -1,25 +1,25 @@
use crate::args;
use crate::args::{InterfaceField, InterfaceFieldArgument};
use crate::output_type::OutputType;
use crate::utils::{get_crate_name, get_rustdoc};
use crate::utils::{generate_default, get_crate_name, get_rustdoc, GeneratorResult};
use darling::ast::{Data, Style};
use inflector::Inflector;
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::quote;
use std::collections::HashSet;
use syn::{Data, DeriveInput, Error, Fields, Result, Type};
use syn::{Error, Type};
pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result<TokenStream> {
pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(interface_args.internal);
let ident = &input.ident;
let generics = &input.generics;
let s = match &input.data {
let ident = &interface_args.ident;
let generics = &interface_args.generics;
let s = match &interface_args.data {
Data::Enum(s) => s,
_ => {
return Err(Error::new_spanned(
input,
"Interfaces can only be applied to an enum.",
))
return Err(
Error::new_spanned(ident, "Interface can only be applied to an enum.").into(),
)
}
};
let extends = interface_args.extends;
@ -31,10 +31,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
.clone()
.unwrap_or_else(|| ident.to_string());
let desc = interface_args
.desc
.clone()
.or_else(|| get_rustdoc(&input.attrs).ok().flatten())
let desc = get_rustdoc(&interface_args.attrs)?
.map(|s| quote! { Some(#s) })
.unwrap_or_else(|| quote! {None});
@ -43,36 +40,37 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
let mut get_introspection_typename = Vec::new();
let mut collect_all_fields = Vec::new();
for variant in s.variants.iter() {
for variant in s {
let enum_name = &variant.ident;
let field = match &variant.fields {
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => fields.unnamed.first().unwrap(),
Fields::Unnamed(_) => {
let ty = match variant.fields.style {
Style::Tuple if variant.fields.fields.len() == 1 => &variant.fields.fields[0],
Style::Tuple => {
return Err(Error::new_spanned(
variant,
enum_name,
"Only single value variants are supported",
))
)
.into())
}
Fields::Unit => {
return Err(Error::new_spanned(
variant,
"Empty variants are not supported",
))
Style::Unit => {
return Err(
Error::new_spanned(enum_name, "Empty variants are not supported").into(),
)
}
Fields::Named(_) => {
Style::Struct => {
return Err(Error::new_spanned(
variant,
enum_name,
"Variants with named fields are not supported",
))
)
.into())
}
};
if let Type::Path(p) = &field.ty {
if let Type::Path(p) = ty {
// This validates that the field type wasn't already used
if !enum_items.insert(p) {
return Err(Error::new_spanned(
field,
"This type already used in another variant",
));
return Err(
Error::new_spanned(ty, "This type already used in another variant").into(),
);
}
type_into_impls.push(quote! {
@ -102,7 +100,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
#ident::#enum_name(obj) => obj.collect_all_fields(ctx, fields)
});
} else {
return Err(Error::new_spanned(field, "Invalid type"));
return Err(Error::new_spanned(ty, "Invalid type").into());
}
}
@ -128,6 +126,10 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
let method_name = Ident::new(&name, Span::call_site());
(name.to_camel_case(), method_name)
};
let ty = match syn::parse_str::<syn::Type>(&ty.value()) {
Ok(ty) => ty,
Err(_) => return Err(Error::new_spanned(&ty, "Expect type").into()),
};
let mut calls = Vec::new();
let mut use_params = Vec::new();
let mut decl_params = Vec::new();
@ -150,14 +152,20 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
desc,
ty,
default,
default_with,
} in args
{
let ident = Ident::new(name, Span::call_site());
let name = name.to_camel_case();
let ty = match syn::parse_str::<syn::Type>(&ty.value()) {
Ok(ty) => ty,
Err(_) => return Err(Error::new_spanned(&ty, "Expect type").into()),
};
decl_params.push(quote! { #ident: #ty });
use_params.push(quote! { #ident });
let get_default = match default {
let default = generate_default(&default, &default_with)?;
let get_default = match &default {
Some(default) => quote! { Some(|| -> #ty { #default }) },
None => quote! { None },
};
@ -201,7 +209,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
.map(|s| quote! {Some(#s)})
.unwrap_or_else(|| quote! {None});
let oty = OutputType::parse(ty)?;
let oty = OutputType::parse(&ty)?;
let ty = match oty {
OutputType::Value(ty) => ty,
OutputType::Result(_, ty) => ty,

View File

@ -17,161 +17,141 @@ mod subscription;
mod union;
mod utils;
use crate::utils::parse_derive;
use darling::{FromDeriveInput, FromMeta};
use proc_macro::TokenStream;
use syn::parse_macro_input;
use syn::{AttributeArgs, ItemImpl};
use syn::{AttributeArgs, DeriveInput, ItemImpl};
#[proc_macro_attribute]
#[allow(non_snake_case)]
pub fn Object(args: TokenStream, input: TokenStream) -> TokenStream {
let object_args = match args::Object::parse(parse_macro_input!(args as AttributeArgs)) {
let object_args = match args::Object::from_list(&parse_macro_input!(args as AttributeArgs)) {
Ok(object_args) => object_args,
Err(err) => return err.to_compile_error().into(),
Err(err) => return TokenStream::from(err.write_errors()),
};
let mut item_impl = parse_macro_input!(input as ItemImpl);
match object::generate(&object_args, &mut item_impl) {
Ok(expanded) => expanded,
Err(err) => err.to_compile_error().into(),
Err(err) => err.write_errors().into(),
}
}
#[proc_macro_derive(SimpleObject, attributes(field, graphql))]
pub fn derive_simple_object(input: TokenStream) -> TokenStream {
let (args, input) = match parse_derive(input.into()) {
Ok(r) => r,
Err(err) => return err.to_compile_error().into(),
};
let object_args = match args::Object::parse(parse_macro_input!(args as AttributeArgs)) {
Ok(object_args) => object_args,
Err(err) => return err.to_compile_error().into(),
};
match simple_object::generate(&object_args, &input) {
let object_args =
match args::SimpleObject::from_derive_input(&parse_macro_input!(input as DeriveInput)) {
Ok(object_args) => object_args,
Err(err) => return TokenStream::from(err.write_errors()),
};
match simple_object::generate(&object_args) {
Ok(expanded) => expanded,
Err(err) => err.to_compile_error().into(),
Err(err) => err.write_errors().into(),
}
}
#[proc_macro_derive(Enum, attributes(item, graphql))]
pub fn derive_enum(input: TokenStream) -> TokenStream {
let (args, input) = match parse_derive(input.into()) {
Ok(r) => r,
Err(err) => return err.to_compile_error().into(),
};
let enum_args = match args::Enum::parse(parse_macro_input!(args as AttributeArgs)) {
let enum_args = match args::Enum::from_derive_input(&parse_macro_input!(input as DeriveInput)) {
Ok(enum_args) => enum_args,
Err(err) => return err.to_compile_error().into(),
Err(err) => return TokenStream::from(err.write_errors()),
};
match r#enum::generate(&enum_args, &input) {
match r#enum::generate(&enum_args) {
Ok(expanded) => expanded,
Err(err) => err.to_compile_error().into(),
Err(err) => err.write_errors().into(),
}
}
#[proc_macro_derive(InputObject, attributes(field, graphql))]
pub fn derive_input_object(input: TokenStream) -> TokenStream {
let (args, input) = match parse_derive(input.into()) {
Ok(r) => r,
Err(err) => return err.to_compile_error().into(),
};
let object_args = match args::InputObject::parse(parse_macro_input!(args as AttributeArgs)) {
Ok(object_args) => object_args,
Err(err) => return err.to_compile_error().into(),
};
match input_object::generate(&object_args, &input) {
let object_args =
match args::InputObject::from_derive_input(&parse_macro_input!(input as DeriveInput)) {
Ok(object_args) => object_args,
Err(err) => return TokenStream::from(err.write_errors()),
};
match input_object::generate(&object_args) {
Ok(expanded) => expanded,
Err(err) => err.to_compile_error().into(),
Err(err) => err.write_errors().into(),
}
}
#[proc_macro_derive(Interface, attributes(graphql))]
pub fn derive_interface(input: TokenStream) -> TokenStream {
let (args, input) = match parse_derive(input.into()) {
Ok(r) => r,
Err(err) => return err.to_compile_error().into(),
};
let interface_args = match args::Interface::parse(parse_macro_input!(args as AttributeArgs)) {
Ok(interface_args) => interface_args,
Err(err) => return err.to_compile_error().into(),
};
match interface::generate(&interface_args, &input) {
let interface_args =
match args::Interface::from_derive_input(&parse_macro_input!(input as DeriveInput)) {
Ok(interface_args) => interface_args,
Err(err) => return TokenStream::from(err.write_errors()),
};
match interface::generate(&interface_args) {
Ok(expanded) => expanded,
Err(err) => err.to_compile_error().into(),
Err(err) => err.write_errors().into(),
}
}
#[proc_macro_derive(Union, attributes(graphql, item))]
pub fn derive_union(input: TokenStream) -> TokenStream {
let (args, input) = match parse_derive(input.into()) {
Ok(r) => r,
Err(err) => return err.to_compile_error().into(),
};
let union_args = match args::Interface::parse(parse_macro_input!(args as AttributeArgs)) {
let union_args = match args::Union::from_derive_input(&parse_macro_input!(input as DeriveInput))
{
Ok(union_args) => union_args,
Err(err) => return err.to_compile_error().into(),
Err(err) => return TokenStream::from(err.write_errors()),
};
match union::generate(&union_args, &input) {
match union::generate(&union_args) {
Ok(expanded) => expanded,
Err(err) => err.to_compile_error().into(),
Err(err) => err.write_errors().into(),
}
}
#[proc_macro_attribute]
#[allow(non_snake_case)]
pub fn Subscription(args: TokenStream, input: TokenStream) -> TokenStream {
let object_args = match args::Object::parse(parse_macro_input!(args as AttributeArgs)) {
Ok(object_args) => object_args,
Err(err) => return err.to_compile_error().into(),
};
let object_args =
match args::Subscription::from_list(&parse_macro_input!(args as AttributeArgs)) {
Ok(object_args) => object_args,
Err(err) => return TokenStream::from(err.write_errors()),
};
let mut item_impl = parse_macro_input!(input as ItemImpl);
match subscription::generate(&object_args, &mut item_impl) {
Ok(expanded) => expanded,
Err(err) => err.to_compile_error().into(),
Err(err) => err.write_errors().into(),
}
}
#[proc_macro_attribute]
#[allow(non_snake_case)]
pub fn Scalar(args: TokenStream, input: TokenStream) -> TokenStream {
let scalar_args = match args::Scalar::parse(parse_macro_input!(args as AttributeArgs)) {
let scalar_args = match args::Scalar::from_list(&parse_macro_input!(args as AttributeArgs)) {
Ok(scalar_args) => scalar_args,
Err(err) => return err.to_compile_error().into(),
Err(err) => return TokenStream::from(err.write_errors()),
};
let mut item_impl = parse_macro_input!(input as ItemImpl);
match scalar::generate(&scalar_args, &mut item_impl) {
Ok(expanded) => expanded,
Err(err) => err.to_compile_error().into(),
Err(err) => err.write_errors().into(),
}
}
#[proc_macro_derive(MergedObject, attributes(item, graphql))]
pub fn derive_merged_object(input: TokenStream) -> TokenStream {
let (args, input) = match parse_derive(input.into()) {
Ok(r) => r,
Err(err) => return err.to_compile_error().into(),
};
let object_args = match args::Object::parse(parse_macro_input!(args as AttributeArgs)) {
Ok(object_args) => object_args,
Err(err) => return err.to_compile_error().into(),
};
match merged_object::generate(&object_args, &input) {
let object_args =
match args::MergedObject::from_derive_input(&parse_macro_input!(input as DeriveInput)) {
Ok(object_args) => object_args,
Err(err) => return TokenStream::from(err.write_errors()),
};
match merged_object::generate(&object_args) {
Ok(expanded) => expanded,
Err(err) => err.to_compile_error().into(),
Err(err) => err.write_errors().into(),
}
}
#[proc_macro_derive(MergedSubscription, attributes(item, graphql))]
pub fn derive_merged_subscription(input: TokenStream) -> TokenStream {
let (args, input) = match parse_derive(input.into()) {
Ok(r) => r,
Err(err) => return err.to_compile_error().into(),
};
let object_args = match args::Object::parse(parse_macro_input!(args as AttributeArgs)) {
let object_args = match args::MergedSubscription::from_derive_input(&parse_macro_input!(
input as DeriveInput
)) {
Ok(object_args) => object_args,
Err(err) => return err.to_compile_error().into(),
Err(err) => return TokenStream::from(err.write_errors()),
};
match merged_subscription::generate(&object_args, &input) {
match merged_subscription::generate(&object_args) {
Ok(expanded) => expanded,
Err(err) => err.to_compile_error().into(),
Err(err) => err.write_errors().into(),
}
}

View File

@ -1,29 +1,31 @@
use crate::args;
use crate::utils::{get_crate_name, get_rustdoc};
use crate::utils::{get_crate_name, get_rustdoc, GeneratorResult};
use darling::ast::Data;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{Data, DeriveInput, Error, LitInt, Result};
use syn::{Error, LitInt};
pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<TokenStream> {
pub fn generate(object_args: &args::MergedObject) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(object_args.internal);
let ident = &input.ident;
let ident = &object_args.ident;
let extends = object_args.extends;
let gql_typename = object_args
.name
.clone()
.unwrap_or_else(|| ident.to_string());
let desc = object_args
.desc
.clone()
.or_else(|| get_rustdoc(&input.attrs).ok().flatten())
let desc = get_rustdoc(&object_args.attrs)?
.map(|s| quote! { Some(#s) })
.unwrap_or_else(|| quote! {None});
let s = match &input.data {
let s = match &object_args.data {
Data::Struct(e) => e,
_ => return Err(Error::new_spanned(input, "It should be a struct")),
_ => {
return Err(
Error::new_spanned(ident, "MergedObject can only be applied to an struct.").into(),
)
}
};
let mut types = Vec::new();

View File

@ -1,28 +1,32 @@
use crate::args;
use crate::utils::{get_crate_name, get_rustdoc};
use crate::utils::{get_crate_name, get_rustdoc, GeneratorResult};
use darling::ast::Data;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{Data, DeriveInput, Error, LitInt, Result};
use syn::{Error, LitInt};
pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<TokenStream> {
pub fn generate(object_args: &args::MergedSubscription) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(object_args.internal);
let ident = &input.ident;
let ident = &object_args.ident;
let gql_typename = object_args
.name
.clone()
.unwrap_or_else(|| ident.to_string());
let desc = object_args
.desc
.clone()
.or_else(|| get_rustdoc(&input.attrs).ok().flatten())
let desc = get_rustdoc(&object_args.attrs)?
.map(|s| quote! { Some(#s) })
.unwrap_or_else(|| quote! {None});
let s = match &input.data {
let s = match &object_args.data {
Data::Struct(e) => e,
_ => return Err(Error::new_spanned(input, "It should be a struct")),
_ => {
return Err(Error::new_spanned(
&ident,
"MergedSubscription can only be applied to an struct.",
)
.into())
}
};
let mut types = Vec::new();

View File

@ -1,13 +1,20 @@
use crate::args;
use crate::output_type::OutputType;
use crate::utils::{get_cfg_attrs, get_crate_name, get_param_getter_ident, get_rustdoc};
use crate::utils::{
generate_default, generate_guards, generate_post_guards, generate_validator, get_cfg_attrs,
get_crate_name, get_param_getter_ident, get_rustdoc, parse_graphql_attrs, remove_graphql_attrs,
GeneratorResult,
};
use inflector::Inflector;
use proc_macro::TokenStream;
use quote::quote;
use syn::ext::IdentExt;
use syn::{Block, Error, FnArg, ImplItem, ItemImpl, Pat, Result, ReturnType, Type, TypeReference};
use syn::{Block, Error, FnArg, ImplItem, ItemImpl, Pat, ReturnType, Type, TypeReference};
pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<TokenStream> {
pub fn generate(
object_args: &args::Object,
item_impl: &mut ItemImpl,
) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(object_args.internal);
let (self_ty, self_name) = match item_impl.self_ty.as_ref() {
Type::Path(path) => (
@ -18,21 +25,17 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
.map(|s| s.ident.to_string())
.unwrap(),
),
_ => return Err(Error::new_spanned(&item_impl.self_ty, "Invalid type")),
_ => return Err(Error::new_spanned(&item_impl.self_ty, "Invalid type").into()),
};
let generics = &item_impl.generics;
let where_clause = &item_impl.generics.where_clause;
let extends = object_args.extends;
let gql_typename = object_args
.name
.clone()
.unwrap_or_else(|| self_name.clone());
let desc = object_args
.desc
.clone()
.or_else(|| get_rustdoc(&item_impl.attrs).ok().flatten())
let desc = get_rustdoc(&item_impl.attrs)?
.map(|s| quote! { Some(#s) })
.unwrap_or_else(|| quote! {None});
@ -44,17 +47,20 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
for item in &mut item_impl.items {
if let ImplItem::Method(method) = item {
if args::Entity::parse(&crate_name, &method.attrs)?.is_some() {
let method_args: args::ObjectField =
parse_graphql_attrs(&method.attrs)?.unwrap_or_default();
if method_args.entity {
let cfg_attrs = get_cfg_attrs(&method.attrs);
if method.sig.asyncness.is_none() {
return Err(Error::new_spanned(&method, "Must be asynchronous"));
return Err(Error::new_spanned(&method, "Must be asynchronous").into());
}
let ty = match &method.sig.output {
ReturnType::Type(_, ty) => OutputType::parse(ty)?,
ReturnType::Default => {
return Err(Error::new_spanned(&method.sig.output, "Missing type"))
return Err(Error::new_spanned(&method.sig.output, "Missing type").into())
}
};
let mut create_ctx = true;
@ -66,14 +72,16 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
return Err(Error::new_spanned(
receiver,
"The self receiver must be the first parameter.",
));
)
.into());
}
} else if let FnArg::Typed(pat) = arg {
if idx == 0 {
return Err(Error::new_spanned(
pat,
"The self receiver must be the first parameter.",
));
)
.into());
}
match (&*pat.pat, &*pat.ty) {
@ -81,7 +89,8 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
args.push((
arg_ident.clone(),
arg_ty.clone(),
args::Argument::parse(&crate_name, &pat.attrs)?,
parse_graphql_attrs::<args::Argument>(&pat.attrs)?
.unwrap_or_default(),
));
pat.attrs.clear();
}
@ -93,13 +102,16 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
return Err(Error::new_spanned(
arg,
"The Context must be the second argument.",
));
)
.into());
} else {
create_ctx = false;
}
}
}
_ => return Err(Error::new_spanned(arg, "Invalid argument type.")),
_ => {
return Err(Error::new_spanned(arg, "Invalid argument type.").into())
}
}
}
}
@ -123,7 +135,8 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
return Err(Error::new_spanned(
method,
"Entity need to have at least one key.",
));
)
.into());
}
for (ident, ty, args::Argument { name, key, .. }) in &args {
@ -194,53 +207,41 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
}
},
));
method.attrs.remove(
method
.attrs
.iter()
.enumerate()
.find(|(_, a)| a.path.is_ident("entity"))
.map(|(idx, _)| idx)
.unwrap(),
);
} else if let Some(field) = args::Field::parse(&crate_name, &method.attrs)? {
} else if !method_args.skip {
if method.sig.asyncness.is_none() {
return Err(Error::new_spanned(&method, "Must be asynchronous"));
return Err(Error::new_spanned(&method, "Must be asynchronous").into());
}
let field_name = field
let field_name = method_args
.name
.clone()
.unwrap_or_else(|| method.sig.ident.unraw().to_string().to_camel_case());
let field_desc = field
.desc
.as_ref()
.map(|s| quote! {Some(#s)})
let field_desc = get_rustdoc(&method.attrs)?
.map(|s| quote! { Some(#s) })
.unwrap_or_else(|| quote! {None});
let field_deprecation = field
let field_deprecation = method_args
.deprecation
.as_ref()
.map(|s| quote! {Some(#s)})
.unwrap_or_else(|| quote! {None});
let external = field.external;
let requires = match &field.requires {
let external = method_args.external;
let requires = match &method_args.requires {
Some(requires) => quote! { Some(#requires) },
None => quote! { None },
};
let provides = match &field.provides {
let provides = match &method_args.provides {
Some(provides) => quote! { Some(#provides) },
None => quote! { None },
};
let ty = match &method.sig.output {
ReturnType::Type(_, ty) => OutputType::parse(ty)?,
ReturnType::Default => {
return Err(Error::new_spanned(&method.sig.output, "Missing type"))
return Err(Error::new_spanned(&method.sig.output, "Missing type").into())
}
};
let cache_control = {
let public = field.cache_control.public;
let max_age = field.cache_control.max_age;
let public = method_args.cache_control.is_public();
let max_age = method_args.cache_control.max_age;
quote! {
#crate_name::CacheControl {
public: #public,
@ -259,14 +260,16 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
return Err(Error::new_spanned(
receiver,
"The self receiver must be the first parameter.",
));
)
.into());
}
} else if let FnArg::Typed(pat) = arg {
if idx == 0 {
return Err(Error::new_spanned(
pat,
"The self receiver must be the first parameter.",
));
)
.into());
}
match (&*pat.pat, &*pat.ty) {
@ -274,7 +277,8 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
args.push((
arg_ident.clone(),
arg_ty.clone(),
args::Argument::parse(&crate_name, &pat.attrs)?,
parse_graphql_attrs::<args::Argument>(&pat.attrs)?
.unwrap_or_default(),
));
pat.attrs.clear();
}
@ -286,13 +290,16 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
return Err(Error::new_spanned(
arg,
"The Context must be the second argument.",
));
)
.into());
}
create_ctx = false;
}
}
_ => return Err(Error::new_spanned(arg, "Invalid argument type.")),
_ => {
return Err(Error::new_spanned(arg, "Invalid argument type.").into())
}
}
}
}
@ -314,6 +321,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
name,
desc,
default,
default_with,
validator,
..
},
@ -326,6 +334,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
.as_ref()
.map(|s| quote! {Some(#s)})
.unwrap_or_else(|| quote! {None});
let default = generate_default(&default, &default_with)?;
let schema_default = default
.as_ref()
.map(|value| {
@ -333,6 +342,14 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
})
.unwrap_or_else(|| quote! {None});
let validator = match &validator {
Some(meta) => {
let stream = generate_validator(&crate_name, meta)?;
quote!(Some(#stream))
}
None => quote!(None),
};
schema_args.push(quote! {
args.insert(#name, #crate_name::registry::MetaInputValue {
name: #name,
@ -401,14 +418,23 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
}
};
let guard = field
.guard
let guard = match &method_args.guard {
Some(meta_list) => generate_guards(&crate_name, meta_list)?,
None => None,
};
let guard = guard
.map(|guard| quote! {
#guard.check(ctx).await
.map_err(|err| err.into_error_with_path(ctx.item.pos, ctx.path_node.as_ref()))?;
});
let post_guard = field
.post_guard
let post_guard = match &method_args.post_guard {
Some(meta_list) => generate_post_guards(&crate_name, meta_list)?,
None => None,
};
let post_guard = post_guard
.map(|guard| quote! {
#guard.check(ctx, &res).await
.map_err(|err| err.into_error_with_path(ctx.item.pos, ctx.path_node.as_ref()))?;
@ -425,28 +451,14 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
return #crate_name::OutputValueType::resolve(&res, &ctx_obj, ctx.item).await;
}
});
if let Some((idx, _)) = method
.attrs
.iter()
.enumerate()
.find(|(_, a)| a.path.is_ident("field"))
{
method.attrs.remove(idx);
}
} else if let Some((idx, _)) = method
.attrs
.iter()
.enumerate()
.find(|(_, a)| a.path.is_ident("field"))
{
method.attrs.remove(idx);
}
remove_graphql_attrs(&mut method.attrs);
}
}
let cache_control = {
let public = object_args.cache_control.public;
let public = object_args.cache_control.is_public();
let max_age = object_args.cache_control.max_age;
quote! {
#crate_name::CacheControl {

View File

@ -1,10 +1,13 @@
use crate::args;
use crate::utils::{get_crate_name, get_rustdoc};
use crate::utils::{get_crate_name, get_rustdoc, GeneratorResult};
use proc_macro::TokenStream;
use quote::quote;
use syn::{Error, ItemImpl, Result, Type};
use syn::{Error, ItemImpl, Type};
pub fn generate(scalar_args: &args::Scalar, item_impl: &mut ItemImpl) -> Result<TokenStream> {
pub fn generate(
scalar_args: &args::Scalar,
item_impl: &mut ItemImpl,
) -> GeneratorResult<TokenStream> {
let self_name = match item_impl.self_ty.as_ref() {
Type::Path(path) => path
.path
@ -12,16 +15,13 @@ pub fn generate(scalar_args: &args::Scalar, item_impl: &mut ItemImpl) -> Result<
.last()
.map(|s| s.ident.to_string())
.unwrap(),
_ => return Err(Error::new_spanned(&item_impl.self_ty, "Invalid type")),
_ => return Err(Error::new_spanned(&item_impl.self_ty, "Invalid type").into()),
};
let gql_typename = scalar_args
.name
.clone()
.unwrap_or_else(|| self_name.clone());
let desc = scalar_args
.desc
.clone()
.or_else(|| get_rustdoc(&item_impl.attrs).ok().flatten())
let desc = get_rustdoc(&item_impl.attrs)?
.map(|s| quote! { Some(#s) })
.unwrap_or_else(|| quote! {None});
let self_ty = &item_impl.self_ty;

View File

@ -1,15 +1,18 @@
use crate::args;
use crate::utils::{get_crate_name, get_rustdoc};
use crate::utils::{
generate_guards, generate_post_guards, get_crate_name, get_rustdoc, GeneratorResult,
};
use darling::ast::Data;
use inflector::Inflector;
use proc_macro::TokenStream;
use quote::quote;
use syn::ext::IdentExt;
use syn::{Data, DeriveInput, Error, Fields, Result};
use syn::Error;
pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<TokenStream> {
pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(object_args.internal);
let ident = &input.ident;
let generics = &input.generics;
let ident = &object_args.ident;
let generics = &object_args.generics;
let where_clause = &generics.where_clause;
let extends = object_args.extends;
let gql_typename = object_args
@ -17,125 +20,125 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
.clone()
.unwrap_or_else(|| ident.to_string());
let desc = object_args
.desc
.clone()
.or_else(|| get_rustdoc(&input.attrs).ok().flatten())
let desc = get_rustdoc(&object_args.attrs)?
.map(|s| quote! { Some(#s) })
.unwrap_or_else(|| quote! {None});
let s = match &input.data {
let s = match &object_args.data {
Data::Struct(e) => e,
_ => return Err(Error::new_spanned(input, "It should be a struct")),
_ => {
return Err(Error::new_spanned(
&ident,
"SimpleObject can only be applied to an struct.",
)
.into())
}
};
let mut getters = Vec::new();
let mut resolvers = Vec::new();
let mut schema_fields = Vec::new();
let fields = match &s.fields {
Fields::Named(fields) => Some(fields),
Fields::Unit => None,
_ => return Err(Error::new_spanned(input, "All fields must be named.")),
};
if let Some(fields) = fields {
for item in &fields.named {
if let Some(field) = args::Field::parse(&crate_name, &item.attrs)? {
let field_name = field.name.clone().unwrap_or_else(|| {
item.ident
.as_ref()
.unwrap()
.unraw()
.to_string()
.to_camel_case()
});
let field_desc = field
.desc
.as_ref()
.map(|s| quote! {Some(#s)})
.unwrap_or_else(|| quote! {None});
let field_deprecation = field
.deprecation
.as_ref()
.map(|s| quote! {Some(#s)})
.unwrap_or_else(|| quote! {None});
let external = field.external;
let requires = match &field.requires {
Some(requires) => quote! { Some(#requires) },
None => quote! { None },
};
let provides = match &field.provides {
Some(provides) => quote! { Some(#provides) },
None => quote! { None },
};
let vis = &item.vis;
let ty = &item.ty;
let cache_control = {
let public = field.cache_control.public;
let max_age = field.cache_control.max_age;
quote! {
#crate_name::CacheControl {
public: #public,
max_age: #max_age,
}
}
};
schema_fields.push(quote! {
fields.insert(#field_name.to_string(), #crate_name::registry::MetaField {
name: #field_name.to_string(),
description: #field_desc,
args: Default::default(),
ty: <#ty as #crate_name::Type>::create_type_info(registry),
deprecation: #field_deprecation,
cache_control: #cache_control,
external: #external,
provides: #provides,
requires: #requires,
});
});
let ident = &item.ident;
let guard = field
.guard
.map(|guard| quote! { #guard.check(ctx).await.map_err(|err| err.into_error_with_path(ctx.item.pos, ctx.path_node.as_ref()))?; });
let post_guard = field
.post_guard
.map(|guard| quote! { #guard.check(ctx, &res).await.map_err(|err| err.into_error_with_path(ctx.item.pos, ctx.path_node.as_ref()))?; });
getters.push(if !field.owned {
quote! {
#[inline]
#[allow(missing_docs)]
#vis async fn #ident(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::FieldResult<&#ty> {
Ok(&self.#ident)
}
}
} else {
quote! {
#[inline]
#[allow(missing_docs)]
#vis async fn #ident(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::FieldResult<#ty> {
Ok(self.#ident.clone())
}
}
});
resolvers.push(quote! {
if ctx.item.node.name.node == #field_name {
#guard
let res = self.#ident(ctx).await.map_err(|err| err.into_error_with_path(ctx.item.pos, ctx.path_node.as_ref()))?;
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
#post_guard
return #crate_name::OutputValueType::resolve(&res, &ctx_obj, ctx.item).await;
}
});
}
for field in &s.fields {
if field.skip {
continue;
}
let ident = match &field.ident {
Some(ident) => ident,
None => return Err(Error::new_spanned(&ident, "All fields must be named.").into()),
};
let field_name = field
.name
.clone()
.unwrap_or_else(|| ident.unraw().to_string().to_camel_case());
let field_desc = get_rustdoc(&field.attrs)?
.map(|s| quote! {Some(#s)})
.unwrap_or_else(|| quote! {None});
let field_deprecation = field
.deprecation
.as_ref()
.map(|s| quote! {Some(#s)})
.unwrap_or_else(|| quote! {None});
let external = field.external;
let requires = match &field.requires {
Some(requires) => quote! { Some(#requires) },
None => quote! { None },
};
let provides = match &field.provides {
Some(provides) => quote! { Some(#provides) },
None => quote! { None },
};
let vis = &field.vis;
let ty = &field.ty;
let cache_control = {
let public = field.cache_control.is_public();
let max_age = field.cache_control.max_age;
quote! {
#crate_name::CacheControl {
public: #public,
max_age: #max_age,
}
}
};
schema_fields.push(quote! {
fields.insert(#field_name.to_string(), #crate_name::registry::MetaField {
name: #field_name.to_string(),
description: #field_desc,
args: Default::default(),
ty: <#ty as #crate_name::Type>::create_type_info(registry),
deprecation: #field_deprecation,
cache_control: #cache_control,
external: #external,
provides: #provides,
requires: #requires,
});
});
let guard = match &field.guard {
Some(meta) => generate_guards(&crate_name, &meta)?,
None => None,
};
let guard = guard.map(|guard| quote! { #guard.check(ctx).await.map_err(|err| err.into_error_with_path(ctx.item.pos, ctx.path_node.as_ref()))?; });
let post_guard = match &field.post_guard {
Some(meta) => generate_post_guards(&crate_name, &meta)?,
None => None,
};
let post_guard = post_guard.map(|guard| quote! { #guard.check(ctx, &res).await.map_err(|err| err.into_error_with_path(ctx.item.pos, ctx.path_node.as_ref()))?; });
getters.push(if !field.owned {
quote! {
#[inline]
#[allow(missing_docs)]
#vis async fn #ident(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::FieldResult<&#ty> {
Ok(&self.#ident)
}
}
} else {
quote! {
#[inline]
#[allow(missing_docs)]
#vis async fn #ident(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::FieldResult<#ty> {
Ok(self.#ident.clone())
}
}
});
resolvers.push(quote! {
if ctx.item.node.name.node == #field_name {
#guard
let res = self.#ident(ctx).await.map_err(|err| err.into_error_with_path(ctx.item.pos, ctx.path_node.as_ref()))?;
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
#post_guard
return #crate_name::OutputValueType::resolve(&res, &ctx_obj, ctx.item).await;
}
});
}
let cache_control = {
let public = object_args.cache_control.public;
let public = object_args.cache_control.is_public();
let max_age = object_args.cache_control.max_age;
quote! {
#crate_name::CacheControl {

View File

@ -1,17 +1,24 @@
use crate::args;
use crate::args::SubscriptionField;
use crate::output_type::OutputType;
use crate::utils::{get_cfg_attrs, get_crate_name, get_param_getter_ident, get_rustdoc};
use crate::utils::{
generate_default, generate_guards, generate_validator, get_cfg_attrs, get_crate_name,
get_param_getter_ident, get_rustdoc, parse_graphql_attrs, remove_graphql_attrs,
GeneratorResult,
};
use inflector::Inflector;
use proc_macro::TokenStream;
use quote::quote;
use syn::ext::IdentExt;
use syn::{
Block, Error, FnArg, ImplItem, ItemImpl, Pat, Result, ReturnType, Type, TypeImplTrait,
TypeReference,
Block, Error, FnArg, ImplItem, ItemImpl, Pat, ReturnType, Type, TypeImplTrait, TypeReference,
};
pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<TokenStream> {
let crate_name = get_crate_name(object_args.internal);
pub fn generate(
subscription_args: &args::Subscription,
item_impl: &mut ItemImpl,
) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(subscription_args.internal);
let (self_ty, self_name) = match item_impl.self_ty.as_ref() {
Type::Path(path) => (
path,
@ -21,20 +28,17 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
.map(|s| s.ident.to_string())
.unwrap(),
),
_ => return Err(Error::new_spanned(&item_impl.self_ty, "Invalid type")),
_ => return Err(Error::new_spanned(&item_impl.self_ty, "Invalid type").into()),
};
let generics = &item_impl.generics;
let where_clause = &generics.where_clause;
let gql_typename = object_args
let gql_typename = subscription_args
.name
.clone()
.unwrap_or_else(|| self_name.clone());
let desc = object_args
.desc
.clone()
.or_else(|| get_rustdoc(&item_impl.attrs).ok().flatten())
let desc = get_rustdoc(&item_impl.attrs)?
.map(|s| quote! { Some(#s) })
.unwrap_or_else(|| quote! {None});
@ -43,303 +47,315 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
for item in &mut item_impl.items {
if let ImplItem::Method(method) = item {
if let Some(field) = args::Field::parse(&crate_name, &method.attrs)? {
let ident = &method.sig.ident;
let field_name = field
.name
.clone()
.unwrap_or_else(|| method.sig.ident.unraw().to_string().to_camel_case());
let field_desc = field
.desc
.as_ref()
.map(|s| quote! {Some(#s)})
.unwrap_or_else(|| quote! {None});
let field_deprecation = field
.deprecation
.as_ref()
.map(|s| quote! {Some(#s)})
.unwrap_or_else(|| quote! {None});
let cfg_attrs = get_cfg_attrs(&method.attrs);
let field: SubscriptionField = parse_graphql_attrs(&method.attrs)?.unwrap_or_default();
if field.skip {
remove_graphql_attrs(&mut method.attrs);
continue;
}
if method.sig.asyncness.is_none() {
return Err(Error::new_spanned(
&method,
"The subscription stream function must be asynchronous",
));
let ident = &method.sig.ident;
let field_name = field
.name
.clone()
.unwrap_or_else(|| method.sig.ident.unraw().to_string().to_camel_case());
let field_desc = get_rustdoc(&method.attrs)?
.map(|s| quote! {Some(#s)})
.unwrap_or_else(|| quote! {None});
let field_deprecation = field
.deprecation
.as_ref()
.map(|s| quote! {Some(#s)})
.unwrap_or_else(|| quote! {None});
let cfg_attrs = get_cfg_attrs(&method.attrs);
if method.sig.asyncness.is_none() {
return Err(Error::new_spanned(
&method,
"The subscription stream function must be asynchronous",
)
.into());
}
let ty = match &method.sig.output {
ReturnType::Type(_, ty) => OutputType::parse(ty)?,
ReturnType::Default => {
return Err(Error::new_spanned(&method.sig.output, "Missing type").into())
}
};
let ty = match &method.sig.output {
ReturnType::Type(_, ty) => OutputType::parse(ty)?,
ReturnType::Default => {
return Err(Error::new_spanned(&method.sig.output, "Missing type"))
let mut create_ctx = true;
let mut args = Vec::new();
for (idx, arg) in method.sig.inputs.iter_mut().enumerate() {
if let FnArg::Receiver(receiver) = arg {
if idx != 0 {
return Err(Error::new_spanned(
receiver,
"The self receiver must be the first parameter.",
)
.into());
}
} else if let FnArg::Typed(pat) = arg {
if idx == 0 {
return Err(Error::new_spanned(
pat,
"The self receiver must be the first parameter.",
)
.into());
}
};
let mut create_ctx = true;
let mut args = Vec::new();
for (idx, arg) in method.sig.inputs.iter_mut().enumerate() {
if let FnArg::Receiver(receiver) = arg {
if idx != 0 {
return Err(Error::new_spanned(
receiver,
"The self receiver must be the first parameter.",
match (&*pat.pat, &*pat.ty) {
(Pat::Ident(arg_ident), Type::Path(arg_ty)) => {
args.push((
arg_ident.clone(),
arg_ty.clone(),
parse_graphql_attrs::<args::SubscriptionFieldArgument>(&pat.attrs)?
.unwrap_or_default(),
));
pat.attrs.clear();
}
} else if let FnArg::Typed(pat) = arg {
if idx == 0 {
return Err(Error::new_spanned(
pat,
"The self receiver must be the first parameter.",
));
}
match (&*pat.pat, &*pat.ty) {
(Pat::Ident(arg_ident), Type::Path(arg_ty)) => {
args.push((
arg_ident.clone(),
arg_ty.clone(),
args::Argument::parse(&crate_name, &pat.attrs)?,
));
pat.attrs.clear();
}
(arg, Type::Reference(TypeReference { elem, .. })) => {
if let Type::Path(path) = elem.as_ref() {
if idx != 1
|| path.path.segments.last().unwrap().ident != "Context"
{
return Err(Error::new_spanned(
arg,
"The Context must be the second argument.",
));
} else {
create_ctx = false;
}
(arg, Type::Reference(TypeReference { elem, .. })) => {
if let Type::Path(path) = elem.as_ref() {
if idx != 1 || path.path.segments.last().unwrap().ident != "Context"
{
return Err(Error::new_spanned(
arg,
"The Context must be the second argument.",
)
.into());
} else {
create_ctx = false;
}
}
_ => {
return Err(Error::new_spanned(arg, "Incorrect argument type"));
}
}
} else {
return Err(Error::new_spanned(arg, "Incorrect argument type"));
_ => {
return Err(Error::new_spanned(arg, "Incorrect argument type").into());
}
}
}
if create_ctx {
let arg =
syn::parse2::<FnArg>(quote! { _: &#crate_name::Context<'_> }).unwrap();
method.sig.inputs.insert(1, arg);
}
let mut schema_args = Vec::new();
let mut use_params = Vec::new();
let mut get_params = Vec::new();
for (
ident,
ty,
args::Argument {
name,
desc,
default,
validator,
..
},
) in args
{
let name = name
.clone()
.unwrap_or_else(|| ident.ident.unraw().to_string().to_camel_case());
let desc = desc
.as_ref()
.map(|s| quote! {Some(#s)})
.unwrap_or_else(|| quote! {None});
let schema_default = default
.as_ref()
.map(|value| {
quote! {Some( <#ty as #crate_name::InputValueType>::to_value(&#value).to_string() )}
})
.unwrap_or_else(|| quote! {None});
schema_args.push(quote! {
args.insert(#name, #crate_name::registry::MetaInputValue {
name: #name,
description: #desc,
ty: <#ty as #crate_name::Type>::create_type_info(registry),
default_value: #schema_default,
validator: #validator,
});
});
use_params.push(quote! { #ident });
let default = match default {
Some(default) => quote! { Some(|| -> #ty { #default }) },
None => quote! { None },
};
let param_getter_name = get_param_getter_ident(&ident.ident.to_string());
get_params.push(quote! {
let #param_getter_name = || -> #crate_name::Result<#ty> { ctx.param_value(#name, #default) };
let #ident: #ty = ctx.param_value(#name, #default)?;
});
}
let res_ty = ty.value_type();
let stream_ty = if let Type::ImplTrait(TypeImplTrait { bounds, .. }) = &res_ty {
quote! { #bounds }
} else {
quote! { #res_ty }
return Err(Error::new_spanned(arg, "Incorrect argument type").into());
}
}
if create_ctx {
let arg = syn::parse2::<FnArg>(quote! { _: &#crate_name::Context<'_> }).unwrap();
method.sig.inputs.insert(1, arg);
}
let mut schema_args = Vec::new();
let mut use_params = Vec::new();
let mut get_params = Vec::new();
for (
ident,
ty,
args::SubscriptionFieldArgument {
name,
desc,
default,
default_with,
validator,
},
) in args
{
let name = name
.clone()
.unwrap_or_else(|| ident.ident.unraw().to_string().to_camel_case());
let desc = desc
.as_ref()
.map(|s| quote! {Some(#s)})
.unwrap_or_else(|| quote! {None});
let default = generate_default(&default, &default_with)?;
let validator = match &validator {
Some(meta) => {
let stream = generate_validator(&crate_name, meta)?;
quote!(Some(#stream))
}
None => quote!(None),
};
if let OutputType::Value(inner_ty) = &ty {
let block = &method.block;
let new_block = quote!({
{
let value = (move || { async move #block })().await;
Ok(value)
}
});
method.block = syn::parse2::<Block>(new_block).expect("invalid block");
method.sig.output = syn::parse2::<ReturnType>(
quote! { -> #crate_name::FieldResult<#inner_ty> },
)
.expect("invalid result type");
}
let schema_default = default
.as_ref()
.map(|value| {
quote! {Some( <#ty as #crate_name::InputValueType>::to_value(&#value).to_string() )}
})
.unwrap_or_else(|| quote! {None});
schema_fields.push(quote! {
#(#cfg_attrs)*
fields.insert(#field_name.to_string(), #crate_name::registry::MetaField {
name: #field_name.to_string(),
description: #field_desc,
args: {
let mut args = #crate_name::indexmap::IndexMap::new();
#(#schema_args)*
args
},
ty: <<#stream_ty as #crate_name::futures::stream::Stream>::Item as #crate_name::Type>::create_type_info(registry),
deprecation: #field_deprecation,
cache_control: Default::default(),
external: false,
requires: None,
provides: None,
schema_args.push(quote! {
args.insert(#name, #crate_name::registry::MetaInputValue {
name: #name,
description: #desc,
ty: <#ty as #crate_name::Type>::create_type_info(registry),
default_value: #schema_default,
validator: #validator,
});
});
let create_field_stream = quote! {
self.#ident(ctx, #(#use_params),*)
.await
.map_err(|err| {
err.into_error_with_path(ctx.item.pos, ctx.path_node.as_ref())
})?
};
use_params.push(quote! { #ident });
let guard = field.guard.map(|guard| quote! {
#guard.check(ctx).await.map_err(|err| err.into_error_with_path(ctx.item.pos, ctx.path_node.as_ref()))?;
let default = match default {
Some(default) => quote! { Some(|| -> #ty { #default }) },
None => quote! { None },
};
let param_getter_name = get_param_getter_ident(&ident.ident.to_string());
get_params.push(quote! {
let #param_getter_name = || -> #crate_name::Result<#ty> { ctx.param_value(#name, #default) };
let #ident: #ty = ctx.param_value(#name, #default)?;
});
if field.post_guard.is_some() {
return Err(Error::new_spanned(
method,
"The subscription field does not support post guard",
));
}
}
let stream_fn = quote! {
#(#get_params)*
#guard
let field_name = ::std::sync::Arc::new(ctx.item.node.response_key().node.clone());
let field = ::std::sync::Arc::new(ctx.item.clone());
let res_ty = ty.value_type();
let stream_ty = if let Type::ImplTrait(TypeImplTrait { bounds, .. }) = &res_ty {
quote! { #bounds }
} else {
quote! { #res_ty }
};
let pos = ctx.item.pos;
let schema_env = ctx.schema_env.clone();
let query_env = ctx.query_env.clone();
let stream = #crate_name::futures::StreamExt::then(#create_field_stream, {
let field_name = field_name.clone();
move |msg| {
let schema_env = schema_env.clone();
let query_env = query_env.clone();
let field = field.clone();
let field_name = field_name.clone();
async move {
let resolve_id = #crate_name::ResolveId {
parent: Some(0),
current: 1,
};
let inc_resolve_id = ::std::sync::atomic::AtomicUsize::new(1);
let ctx_selection_set = query_env.create_context(
&schema_env,
Some(#crate_name::QueryPathNode {
parent: None,
segment: #crate_name::QueryPathSegment::Name(&field_name),
}),
&field.node.selection_set,
resolve_id,
&inc_resolve_id,
);
#crate_name::extensions::Extension::execution_start(&mut *query_env.extensions.lock());
#[allow(bare_trait_objects)]
let ri = #crate_name::extensions::ResolveInfo {
resolve_id,
path_node: ctx_selection_set.path_node.as_ref().unwrap(),
parent_type: #gql_typename,
return_type: &<<#stream_ty as #crate_name::futures::stream::Stream>::Item as #crate_name::Type>::qualified_type_name(),
schema_env: &schema_env,
query_env: &query_env,
};
#crate_name::extensions::Extension::resolve_start(&mut *query_env.extensions.lock(), &ri);
let res = #crate_name::OutputValueType::resolve(&msg, &ctx_selection_set, &*field)
.await
.map(|value| {
#crate_name::serde_json::json!({
field_name.as_str(): value
})
});
#crate_name::extensions::Extension::resolve_end(&mut *query_env.extensions.lock(), &ri);
#crate_name::extensions::Extension::execution_end(&mut *query_env.extensions.lock());
res
}
}
});
#crate_name::Result::Ok(#crate_name::futures::StreamExt::scan(
stream,
false,
|errored, item| {
if *errored {
return #crate_name::futures::future::ready(None);
}
if item.is_err() {
*errored = true;
}
#crate_name::futures::future::ready(Some(item))
},
))
};
create_stream.push(quote! {
#(#cfg_attrs)*
if ctx.item.node.name.node == #field_name {
return ::std::boxed::Box::pin(
#crate_name::futures::TryStreamExt::try_flatten(
#crate_name::futures::stream::once((move || async move { #stream_fn })())
)
);
if let OutputType::Value(inner_ty) = &ty {
let block = &method.block;
let new_block = quote!({
{
let value = (move || { async move #block })().await;
Ok(value)
}
});
method.block = syn::parse2::<Block>(new_block).expect("invalid block");
method.sig.output =
syn::parse2::<ReturnType>(quote! { -> #crate_name::FieldResult<#inner_ty> })
.expect("invalid result type");
}
if let Some((idx, _)) = method
.attrs
.iter()
.enumerate()
.find(|(_, a)| a.path.is_ident("field"))
{
method.attrs.remove(idx);
schema_fields.push(quote! {
#(#cfg_attrs)*
fields.insert(#field_name.to_string(), #crate_name::registry::MetaField {
name: #field_name.to_string(),
description: #field_desc,
args: {
let mut args = #crate_name::indexmap::IndexMap::new();
#(#schema_args)*
args
},
ty: <<#stream_ty as #crate_name::futures::stream::Stream>::Item as #crate_name::Type>::create_type_info(registry),
deprecation: #field_deprecation,
cache_control: Default::default(),
external: false,
requires: None,
provides: None,
});
});
let create_field_stream = quote! {
self.#ident(ctx, #(#use_params),*)
.await
.map_err(|err| {
err.into_error_with_path(ctx.item.pos, ctx.path_node.as_ref())
})?
};
let guard = match &field.guard {
Some(meta_list) => generate_guards(&crate_name, meta_list)?,
None => None,
};
let guard = guard.map(|guard| quote! {
#guard.check(ctx).await.map_err(|err| err.into_error_with_path(ctx.item.pos, ctx.path_node.as_ref()))?;
});
if field.post_guard.is_some() {
return Err(Error::new_spanned(
method,
"The subscription field does not support post guard",
)
.into());
}
let stream_fn = quote! {
#(#get_params)*
#guard
let field_name = ::std::sync::Arc::new(ctx.item.node.response_key().node.clone());
let field = ::std::sync::Arc::new(ctx.item.clone());
let pos = ctx.item.pos;
let schema_env = ctx.schema_env.clone();
let query_env = ctx.query_env.clone();
let stream = #crate_name::futures::StreamExt::then(#create_field_stream, {
let field_name = field_name.clone();
move |msg| {
let schema_env = schema_env.clone();
let query_env = query_env.clone();
let field = field.clone();
let field_name = field_name.clone();
async move {
let resolve_id = #crate_name::ResolveId {
parent: Some(0),
current: 1,
};
let inc_resolve_id = ::std::sync::atomic::AtomicUsize::new(1);
let ctx_selection_set = query_env.create_context(
&schema_env,
Some(#crate_name::QueryPathNode {
parent: None,
segment: #crate_name::QueryPathSegment::Name(&field_name),
}),
&field.node.selection_set,
resolve_id,
&inc_resolve_id,
);
#crate_name::extensions::Extension::execution_start(&mut *query_env.extensions.lock());
#[allow(bare_trait_objects)]
let ri = #crate_name::extensions::ResolveInfo {
resolve_id,
path_node: ctx_selection_set.path_node.as_ref().unwrap(),
parent_type: #gql_typename,
return_type: &<<#stream_ty as #crate_name::futures::stream::Stream>::Item as #crate_name::Type>::qualified_type_name(),
schema_env: &schema_env,
query_env: &query_env,
};
#crate_name::extensions::Extension::resolve_start(&mut *query_env.extensions.lock(), &ri);
let res = #crate_name::OutputValueType::resolve(&msg, &ctx_selection_set, &*field)
.await
.map(|value| {
#crate_name::serde_json::json!({
field_name.as_str(): value
})
});
#crate_name::extensions::Extension::resolve_end(&mut *query_env.extensions.lock(), &ri);
#crate_name::extensions::Extension::execution_end(&mut *query_env.extensions.lock());
res
}
}
});
#crate_name::Result::Ok(#crate_name::futures::StreamExt::scan(
stream,
false,
|errored, item| {
if *errored {
return #crate_name::futures::future::ready(None);
}
if item.is_err() {
*errored = true;
}
#crate_name::futures::future::ready(Some(item))
},
))
};
create_stream.push(quote! {
#(#cfg_attrs)*
if ctx.item.node.name.node == #field_name {
return ::std::boxed::Box::pin(
#crate_name::futures::TryStreamExt::try_flatten(
#crate_name::futures::stream::once((move || async move { #stream_fn })())
)
);
}
});
remove_graphql_attrs(&mut method.attrs);
}
}

View File

@ -1,21 +1,19 @@
use crate::args;
use crate::utils::{get_crate_name, get_rustdoc};
use crate::utils::{get_crate_name, get_rustdoc, GeneratorResult};
use darling::ast::{Data, Style};
use proc_macro::TokenStream;
use quote::quote;
use std::collections::HashSet;
use syn::{Data, DeriveInput, Error, Fields, Result, Type};
use syn::{Error, Type};
pub fn generate(union_args: &args::Interface, input: &DeriveInput) -> Result<TokenStream> {
pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(union_args.internal);
let ident = &input.ident;
let generics = &input.generics;
let s = match &input.data {
let ident = &union_args.ident;
let generics = &union_args.generics;
let s = match &union_args.data {
Data::Enum(s) => s,
_ => {
return Err(Error::new_spanned(
input,
"Unions can only be applied to an enum.",
))
return Err(Error::new_spanned(&ident, "Union can only be applied to an enum.").into())
}
};
let mut enum_names = Vec::new();
@ -23,10 +21,7 @@ pub fn generate(union_args: &args::Interface, input: &DeriveInput) -> Result<Tok
let mut type_into_impls = Vec::new();
let gql_typename = union_args.name.clone().unwrap_or_else(|| ident.to_string());
let desc = union_args
.desc
.clone()
.or_else(|| get_rustdoc(&input.attrs).ok().flatten())
let desc = get_rustdoc(&union_args.attrs)?
.map(|s| quote! { Some(#s) })
.unwrap_or_else(|| quote! {None});
@ -35,44 +30,42 @@ pub fn generate(union_args: &args::Interface, input: &DeriveInput) -> Result<Tok
let mut get_introspection_typename = Vec::new();
let mut collect_all_fields = Vec::new();
for variant in s.variants.iter() {
for variant in s {
let enum_name = &variant.ident;
let field = match &variant.fields {
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => fields.unnamed.first().unwrap(),
Fields::Unnamed(_) => {
let ty = match variant.fields.style {
Style::Tuple if variant.fields.fields.len() == 1 => &variant.fields.fields[0],
Style::Tuple => {
return Err(Error::new_spanned(
variant,
enum_name,
"Only single value variants are supported",
))
)
.into())
}
Fields::Unit => {
return Err(Error::new_spanned(
variant,
"Empty variants are not supported",
))
Style::Unit => {
return Err(
Error::new_spanned(enum_name, "Empty variants are not supported").into(),
)
}
Fields::Named(_) => {
Style::Struct => {
return Err(Error::new_spanned(
variant,
enum_name,
"Variants with named fields are not supported",
))
)
.into())
}
};
if let Type::Path(p) = &field.ty {
if let Type::Path(p) = &ty {
// This validates that the field type wasn't already used
let item_args = args::UnionItem::parse(&variant.attrs)?;
if !enum_items.insert(p) {
return Err(Error::new_spanned(
field,
"This type already used in another variant",
));
return Err(
Error::new_spanned(&ty, "This type already used in another variant").into(),
);
}
enum_names.push(enum_name);
if !item_args.flatten {
if !variant.flatten {
type_into_impls.push(quote! {
#crate_name::static_assertions::assert_impl_one!(#p: #crate_name::type_mark::TypeMarkObject);
@ -100,7 +93,7 @@ pub fn generate(union_args: &args::Interface, input: &DeriveInput) -> Result<Tok
<#p as #crate_name::Type>::create_type_info(registry);
});
if !item_args.flatten {
if !variant.flatten {
possible_types.push(quote! {
possible_types.insert(<#p as #crate_name::Type>::type_name().to_string());
});
@ -113,7 +106,7 @@ pub fn generate(union_args: &args::Interface, input: &DeriveInput) -> Result<Tok
});
}
if !item_args.flatten {
if !variant.flatten {
get_introspection_typename.push(quote! {
#ident::#enum_name(obj) => <#p as #crate_name::Type>::type_name()
});
@ -127,7 +120,7 @@ pub fn generate(union_args: &args::Interface, input: &DeriveInput) -> Result<Tok
#ident::#enum_name(obj) => obj.collect_all_fields(ctx, fields)
});
} else {
return Err(Error::new_spanned(field, "Invalid type"));
return Err(Error::new_spanned(ty, "Invalid type").into());
}
}

View File

@ -1,8 +1,30 @@
use itertools::Itertools;
use crate::args;
use darling::FromMeta;
use proc_macro2::{Span, TokenStream, TokenTree};
use proc_macro_crate::crate_name;
use quote::quote;
use syn::{Attribute, DeriveInput, Error, Expr, Ident, Lit, Meta, MetaList, NestedMeta, Result};
use syn::{Attribute, Error, Expr, Ident, Lit, LitStr, Meta, NestedMeta};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum GeneratorError {
#[error("{0}")]
Syn(#[from] syn::Error),
#[error("{0}")]
Darling(#[from] darling::Error),
}
impl GeneratorError {
pub fn write_errors(self) -> TokenStream {
match self {
GeneratorError::Syn(err) => err.to_compile_error(),
GeneratorError::Darling(err) => err.write_errors(),
}
}
}
pub type GeneratorResult<T> = std::result::Result<T, GeneratorError>;
pub fn get_crate_name(internal: bool) -> TokenStream {
if internal {
@ -13,33 +35,17 @@ pub fn get_crate_name(internal: bool) -> TokenStream {
}
}
pub fn parse_derive(input: TokenStream) -> Result<(proc_macro::TokenStream, DeriveInput)> {
let mut input: DeriveInput = syn::parse2(input)?;
let attrs = &mut input.attrs;
let graphql_attr = attrs
.iter()
.find_position(|attr| attr.path.is_ident("graphql"));
if let Some((pos, _attr)) = graphql_attr {
let attribute = attrs.remove(pos);
let args = attribute.parse_args::<TokenStream>()?;
Ok((args.into(), input))
} else {
Ok((TokenStream::new().into(), input))
}
}
fn parse_nested_validator(
fn generate_nested_validator(
crate_name: &TokenStream,
nested_meta: &NestedMeta,
) -> Result<TokenStream> {
) -> GeneratorResult<TokenStream> {
let mut params = Vec::new();
match nested_meta {
NestedMeta::Meta(Meta::List(ls)) => {
if ls.path.is_ident("and") {
let mut validators = Vec::new();
for nested_meta in &ls.nested {
validators.push(parse_nested_validator(crate_name, nested_meta)?);
validators.push(generate_nested_validator(crate_name, nested_meta)?);
}
Ok(validators
.into_iter()
@ -51,7 +57,7 @@ fn parse_nested_validator(
} else if ls.path.is_ident("or") {
let mut validators = Vec::new();
for nested_meta in &ls.nested {
validators.push(parse_nested_validator(crate_name, nested_meta)?);
validators.push(generate_nested_validator(crate_name, nested_meta)?);
}
Ok(validators
.into_iter()
@ -72,13 +78,15 @@ fn parse_nested_validator(
return Err(Error::new_spanned(
&nv.lit,
"Value must be string literal",
));
)
.into());
}
} else {
return Err(Error::new_spanned(
nested_meta,
"Invalid property for validator",
));
)
.into());
}
}
Ok(quote! { #ty { #(#params),* } })
@ -86,133 +94,136 @@ fn parse_nested_validator(
}
NestedMeta::Meta(Meta::Path(ty)) => Ok(quote! { #ty {} }),
NestedMeta::Meta(Meta::NameValue(_)) | NestedMeta::Lit(_) => {
Err(Error::new_spanned(nested_meta, "Invalid validator"))
Err(Error::new_spanned(nested_meta, "Invalid validator").into())
}
}
}
pub fn parse_validator(crate_name: &TokenStream, args: &MetaList) -> Result<TokenStream> {
for arg in &args.nested {
if let NestedMeta::Meta(Meta::List(ls)) = arg {
if ls.path.is_ident("validator") {
if ls.nested.len() > 1 {
return Err(Error::new_spanned(ls,
"Only one validator can be defined. You can connect combine validators with `and` or `or`"));
}
if ls.nested.is_empty() {
return Err(Error::new_spanned(
ls,
"At least one validator must be defined",
));
}
let validator = parse_nested_validator(crate_name, &ls.nested[0])?;
return Ok(quote! { Some(::std::sync::Arc::new(#validator)) });
pub fn generate_validator(crate_name: &TokenStream, args: &Meta) -> GeneratorResult<TokenStream> {
match args {
Meta::List(args) => {
if args.nested.len() > 1 {
return Err(Error::new_spanned(args, "Only one validator can be defined. You can connect combine validators with `and` or `or`").into());
}
if args.nested.is_empty() {
return Err(
Error::new_spanned(args, "At least one validator must be defined").into(),
);
}
let validator = generate_nested_validator(crate_name, &args.nested[0])?;
Ok(quote! { ::std::sync::Arc::new(#validator) })
}
_ => Err(Error::new_spanned(args, "Invalid validator").into()),
}
Ok(quote! {None})
}
pub fn parse_guards(crate_name: &TokenStream, args: &MetaList) -> Result<Option<TokenStream>> {
for arg in &args.nested {
if let NestedMeta::Meta(Meta::List(ls)) = arg {
if ls.path.is_ident("guard") {
let mut guards = None;
for item in &ls.nested {
if let NestedMeta::Meta(Meta::List(ls)) = item {
let ty = &ls.path;
let mut params = Vec::new();
for attr in &ls.nested {
if let NestedMeta::Meta(Meta::NameValue(nv)) = attr {
let name = &nv.path;
if let Lit::Str(value) = &nv.lit {
let value_str = value.value();
if value_str.starts_with('@') {
let getter_name = get_param_getter_ident(&value_str[1..]);
params.push(quote! { #name: #getter_name()? });
} else {
let expr = syn::parse_str::<Expr>(&value_str)?;
params.push(quote! { #name: (#expr).into() });
}
pub fn generate_guards(
crate_name: &TokenStream,
args: &Meta,
) -> GeneratorResult<Option<TokenStream>> {
match args {
Meta::List(args) => {
let mut guards = None;
for item in &args.nested {
if let NestedMeta::Meta(Meta::List(ls)) = item {
let ty = &ls.path;
let mut params = Vec::new();
for attr in &ls.nested {
if let NestedMeta::Meta(Meta::NameValue(nv)) = attr {
let name = &nv.path;
if let Lit::Str(value) = &nv.lit {
let value_str = value.value();
if value_str.starts_with('@') {
let getter_name = get_param_getter_ident(&value_str[1..]);
params.push(quote! { #name: #getter_name()? });
} else {
return Err(Error::new_spanned(
&nv.lit,
"Value must be string literal",
));
let expr = syn::parse_str::<Expr>(&value_str)?;
params.push(quote! { #name: (#expr).into() });
}
} else {
return Err(Error::new_spanned(attr, "Invalid property for guard"));
return Err(Error::new_spanned(
&nv.lit,
"Value must be string literal",
)
.into());
}
}
let guard = quote! { #ty { #(#params),* } };
if guards.is_none() {
guards = Some(guard);
} else {
guards =
Some(quote! { #crate_name::guard::GuardExt::and(#guard, #guards) });
}
} else {
return Err(Error::new_spanned(item, "Invalid guard"));
}
}
return Ok(guards);
}
}
}
Ok(None)
}
pub fn parse_post_guards(crate_name: &TokenStream, args: &MetaList) -> Result<Option<TokenStream>> {
for arg in &args.nested {
if let NestedMeta::Meta(Meta::List(ls)) = arg {
if ls.path.is_ident("post_guard") {
let mut guards = None;
for item in &ls.nested {
if let NestedMeta::Meta(Meta::List(ls)) = item {
let ty = &ls.path;
let mut params = Vec::new();
for attr in &ls.nested {
if let NestedMeta::Meta(Meta::NameValue(nv)) = attr {
let name = &nv.path;
if let Lit::Str(value) = &nv.lit {
let value_str = value.value();
if value_str.starts_with('@') {
let getter_name = get_param_getter_ident(&value_str[1..]);
params.push(quote! { #name: #getter_name()? });
} else {
let expr = syn::parse_str::<Expr>(&value_str)?;
params.push(quote! { #name: (#expr).into() });
}
} else {
return Err(Error::new_spanned(
&nv.lit,
"Value must be string literal",
));
}
} else {
return Err(Error::new_spanned(attr, "Invalid property for guard"));
}
}
let guard = quote! { #ty { #(#params),* } };
if guards.is_none() {
guards = Some(guard);
} else {
guards = Some(
quote! { #crate_name::guard::PostGuardExt::and(#guard, #guards) },
return Err(
Error::new_spanned(attr, "Invalid property for guard").into()
);
}
} else {
return Err(Error::new_spanned(item, "Invalid guard"));
}
let guard = quote! { #ty { #(#params),* } };
if guards.is_none() {
guards = Some(guard);
} else {
guards =
Some(quote! { #crate_name::guard::GuardExt::and(#guard, #guards) });
}
} else {
return Err(Error::new_spanned(item, "Invalid guard").into());
}
return Ok(guards);
}
Ok(guards)
}
_ => Err(Error::new_spanned(args, "Invalid guards").into()),
}
Ok(None)
}
pub fn get_rustdoc(attrs: &[Attribute]) -> Result<Option<String>> {
pub fn generate_post_guards(
crate_name: &TokenStream,
args: &Meta,
) -> GeneratorResult<Option<TokenStream>> {
match args {
Meta::List(args) => {
let mut guards = None;
for item in &args.nested {
if let NestedMeta::Meta(Meta::List(ls)) = item {
let ty = &ls.path;
let mut params = Vec::new();
for attr in &ls.nested {
if let NestedMeta::Meta(Meta::NameValue(nv)) = attr {
let name = &nv.path;
if let Lit::Str(value) = &nv.lit {
let value_str = value.value();
if value_str.starts_with('@') {
let getter_name = get_param_getter_ident(&value_str[1..]);
params.push(quote! { #name: #getter_name()? });
} else {
let expr = syn::parse_str::<Expr>(&value_str)?;
params.push(quote! { #name: (#expr).into() });
}
} else {
return Err(Error::new_spanned(
&nv.lit,
"Value must be string literal",
)
.into());
}
} else {
return Err(
Error::new_spanned(attr, "Invalid property for guard").into()
);
}
}
let guard = quote! { #ty { #(#params),* } };
if guards.is_none() {
guards = Some(guard);
} else {
guards =
Some(quote! { #crate_name::guard::PostGuardExt::and(#guard, #guards) });
}
} else {
return Err(Error::new_spanned(item, "Invalid guard").into());
}
}
Ok(guards)
}
_ => Err(Error::new_spanned(args, "Invalid guards").into()),
}
}
pub fn get_rustdoc(attrs: &[Attribute]) -> GeneratorResult<Option<String>> {
let mut full_docs = String::new();
for attr in attrs {
match attr.parse_meta()? {
@ -236,7 +247,7 @@ pub fn get_rustdoc(attrs: &[Attribute]) -> Result<Option<String>> {
})
}
pub fn parse_default(lit: &Lit) -> Result<TokenStream> {
fn generate_default_value(lit: &Lit) -> GeneratorResult<TokenStream> {
match lit {
Lit::Str(value) =>{
let value = value.value();
@ -257,20 +268,27 @@ pub fn parse_default(lit: &Lit) -> Result<TokenStream> {
_ => Err(Error::new_spanned(
lit,
"The default value type only be string, integer, float and boolean, other types should use default_with",
)),
).into()),
}
}
pub fn parse_default_with(lit: &Lit) -> Result<TokenStream> {
if let Lit::Str(str) = lit {
let str = str.value();
let tokens: TokenStream = str.parse()?;
Ok(quote! { (#tokens) })
} else {
Err(Error::new_spanned(
&lit,
"Attribute 'default' should be a string.",
))
fn generate_default_with(lit: &LitStr) -> GeneratorResult<TokenStream> {
let str = lit.value();
let tokens: TokenStream = str
.parse()
.map_err(|err| GeneratorError::Syn(syn::Error::from(err)))?;
Ok(quote! { (#tokens) })
}
pub fn generate_default(
default: &Option<args::DefaultValue>,
default_with: &Option<LitStr>,
) -> GeneratorResult<Option<TokenStream>> {
match (default, default_with) {
(Some(args::DefaultValue::Default), _) => Ok(Some(quote! { Default::default() })),
(Some(args::DefaultValue::Value(lit)), _) => Ok(Some(generate_default_value(lit)?)),
(None, Some(lit)) => Ok(Some(generate_default_with(lit)?)),
(None, None) => Ok(None),
}
}
@ -285,3 +303,23 @@ pub fn get_cfg_attrs(attrs: &[Attribute]) -> Vec<Attribute> {
.cloned()
.collect()
}
pub fn parse_graphql_attrs<T: FromMeta>(attrs: &[Attribute]) -> GeneratorResult<Option<T>> {
for attr in attrs {
if attr.path.is_ident("graphql") {
let meta = attr.parse_meta()?;
return Ok(Some(T::from_meta(&meta)?));
}
}
Ok(None)
}
pub fn remove_graphql_attrs(attrs: &mut Vec<Attribute>) {
if let Some((idx, _)) = attrs
.iter()
.enumerate()
.find(|(_, a)| a.path.is_ident("graphql"))
{
attrs.remove(idx);
}
}

View File

@ -23,7 +23,7 @@ impl Query {
}
#[entity]
async fn find_user_by_id_with_username(&self, #[arg(key)] id: ID, username: String) -> User {
async fn find_user_by_id_with_username(&self, #[graphql(key)] id: ID, username: String) -> User {
User { ... }
}

View File

@ -17,11 +17,11 @@ We can use `QueryResponse` to get a merged cache control result from a query res
```rust
#[Object(cache_control(max_age = 60))]
impl Query {
#[field(cache_control(max_age = 30))]
#[graphql(cache_control(max_age = 30))]
async fn value1(&self) -> i32 {
}
#[field(cache_control(private))]
#[graphql(cache_control(private))]
async fn value2(&self) -> i32 {
}

View File

@ -12,7 +12,6 @@ struct Query;
#[Object]
impl Query {
#[field]
async fn numbers(&self,
after: Option<String>,
before: Option<String>,

View File

@ -17,13 +17,13 @@ fn my_default() -> i32 {
#[Object]
impl Query {
// The default value of the value parameter is 0, it will call i32::default()
fn test1(&self, #[arg(default)] value: i32) {}
fn test1(&self, #[graphql(default)] value: i32) {}
// The default value of the value parameter is 10
fn test2(&self, #[arg(default = 10)] value: i32) {}
fn test2(&self, #[graphql(default = 10)] value: i32) {}
// The default value of the value parameter uses the return result of the my_default function, the value is 30.
fn test3(&self, #[arg(default_with = "my_default()")] value: i32) {}
fn test3(&self, #[graphql(default_with = "my_default()")] value: i32) {}
}
```
@ -50,13 +50,13 @@ use async_graphql::*;
#derive(InputObject)
struct MyInputObject {
#[field(default)]
#[graphql(default)]
value1: i32,
#[field(default = 10)]
#[graphql(default = 10)]
value2: i32,
#[field(default = "my_default()")]
#[graphql(default = "my_default()")]
value3: i32,
}
```

View File

@ -26,7 +26,7 @@ impl MyObject {
async fn value_from_db(
&self,
ctx: &Context<'_>,
#[arg(desc = "Id of object")] id: i64
#[graphql(desc = "Id of object")] id: i64
) -> FieldResult<String> {
let conn = ctx.data::<DbPool>()?.take();
Ok(conn.query_something(id)?.name)

View File

@ -17,7 +17,7 @@ pub enum Episode {
Empire,
/// Released in 1983.
#[item(name="AAA")]
#[graphql(name="AAA")]
Jedi,
}
```

View File

@ -5,8 +5,7 @@ You can use an `Object` as an argument, and GraphQL calls it an `InputObject`.
The definition of `InputObject` is similar to [SimpleObject](define_simple_object.md), but
`SimpleObject` can only be used as output and `InputObject` can only be used as input.
`InputObject` doesn't need a `#[field]` for each field, every field is an `InputValue`.
You can add optional `#[field]` attributes to add descriptions or rename the field.
You can add optional `#[graphql]` attributes to add descriptions or rename the field.
```rust
use async_graphql::*;
@ -14,8 +13,6 @@ use async_graphql::*;
#[derive(InputObject)]
struct Coordinate {
latitude: f64,
#[field(desc = "...")]
longitude: f64
}

View File

@ -30,7 +30,7 @@ impl Circle {
Circle { radius: self.radius * s }.into()
}
#[field(name = "short_description")]
#[graphql(name = "short_description")]
async fn short_description(&self) -> String {
"Circle".to_string()
}
@ -50,7 +50,7 @@ impl Square {
Square { width: self.width * s }.into()
}
#[field(name = "short_description")]
#[graphql(name = "short_description")]
async fn short_description(&self) -> String {
"Square".to_string()
}

View File

@ -2,7 +2,7 @@
`SimpleObject` directly maps all the fields of a struct to GraphQL object. You cannot define a resolver function on it - for that, see [Object](define_complex_object.html).
The example below defines an object `MyObject` which includes the fields `a` and `b`. `c` will be not mapped to GraphQL as it is labelled as `#[field(skip)]`
The example below defines an object `MyObject` which includes the fields `a` and `b`. `c` will be not mapped to GraphQL as it is labelled as `#[graphql(skip)]`
```rust
use async_graphql::*;
@ -12,10 +12,10 @@ struct MyObject {
/// Value a
a: i32,
#[field(desc = "Value b")]
/// Value b
b: i32,
#[field(skip)]
#[graphql(skip)]
c: i32,
}
```

View File

@ -18,7 +18,6 @@ struct Query;
#[Object]
impl Query {
#[field]
async fn parse_with_extensions(&self, input: String) -> FieldResult<i32> {
Ok("234a"
.parse()

View File

@ -16,7 +16,7 @@ struct Query;
#[Object]
impl Query {
async fn input(#[arg(validator(or(Email, MAC(colon = "false"))))] a: String) {
async fn input(#[graphql(validator(or(Email, MAC(colon = "false"))))] a: String) {
}
}
```

View File

@ -12,7 +12,7 @@ async-graphql-tide = "1.18.0" # If you need to integrate into tide
## Write a Schema
The Schema of a GraphQL contains a required Query, an optional Mutation, and an optional Subscription. These object types are described using the structure of the Rust language. The field of the structure corresponds to the field of the GraphQL object, but you need to mark it with `#[field]` so that the procedure macro provided by `Async-graphql` can correctly recognize it.
The Schema of a GraphQL contains a required Query, an optional Mutation, and an optional Subscription. These object types are described using the structure of the Rust language. The field of the structure corresponds to the field of the GraphQL object.
`Async-graphql` implements the mapping of common data types to GraphQL types, such as `i32`, `f64`, `Option<T>`, `Vec<T>`, etc. Also, you can [extend these base types](custom_scalars.md), which are called scalars in the GraphQL.

View File

@ -11,7 +11,7 @@ struct Subscription;
#[Subscription]
impl Subscription {
async fn integers(&self, #[arg(default = "1")] step: i32) -> impl Stream<Item = i32> {
async fn integers(&self, #[graphql(default = "1")] step: i32) -> impl Stream<Item = i32> {
let mut value = 0;
tokio::time::interval(Duration::from_secs(1)).map(move |_| {
value += step;

View File

@ -25,7 +25,7 @@ impl Query {
}
#[entity]
async fn find_user_by_id_with_username(&self, #[arg(key)] id: ID, username: String) -> User {
async fn find_user_by_id_with_username(&self, #[graphql(key)] id: ID, username: String) -> User {
User { ... }
}

View File

@ -17,11 +17,11 @@
```rust
#[Object(cache_control(max_age = 60))]
impl Query {
#[field(cache_control(max_age = 30))]
#[graphql(cache_control(max_age = 30))]
async fn value1(&self) -> i32 {
}
#[field(cache_control(private))]
#[graphql(cache_control(private))]
async fn value2(&self) -> i32 {
}

View File

@ -14,7 +14,6 @@ struct Query;
#[Object]
impl Query {
#[field]
async fn numbers(&self,
after: Option<String>,
before: Option<String>,

View File

@ -16,13 +16,13 @@ fn my_default() -> i32 {
#[Object]
impl Query {
// value参数的默认值为0它会调用i32::default()
fn test1(&self, #[arg(default)] value: i32) {}
fn test1(&self, #[graphql(default)] value: i32) {}
// value参数的默认值为10
fn test2(&self, #[arg(default = 10)] value: i32) {}
fn test2(&self, #[graphql(default = 10)] value: i32) {}
// value参数的默认值使用my_default函数的返回结果值为30
fn test3(&self, #[arg(default_with = "my_default()")] value: i32) {}
fn test3(&self, #[graphql(default_with = "my_default()")] value: i32) {}
}
```
@ -49,13 +49,13 @@ use async_graphql::*;
#derive(InputObject)
struct MyInputObject {
#[field(default)]
#[graphql(default)]
value1: i32,
#[field(default = 10)]
#[graphql(default = 10)]
value2: i32,
#[field(default = "my_default()")]
#[graphql(default = "my_default()")]
value3: i32,
}
```

View File

@ -24,7 +24,7 @@ impl MyObject {
async fn value_from_db(
&self,
ctx: &Context<'_>,
#[arg(desc = "Id of object")] id: i64
#[graphql(desc = "Id of object")] id: i64
) -> FieldResult<String> {
let conn = ctx.data::<DbPool>()?.take();
Ok(conn.query_something(id)?.name)

View File

@ -17,7 +17,7 @@ pub enum Episode {
Empire,
/// Released in 1983.
#[item(name="AAA")]
#[graphql(name="AAA")]
Jedi,
}
```

View File

@ -2,7 +2,7 @@
你可以定义一个对象作为参数类型GraphQL称之为`Input Object`,输入对象的定义方式和[简单对象](define_simple_object.md)很像,不同的是,简单对象只能用于输出,而输入对象只能用于输入。
输入对象不需要为每个字段指定`#[field]`标记,它每个字段都是`Input Value`。但你也可以通过可选的`#[field]`标记来给字段添加描述,重命名。
你也通过可选的`#[graphql]`属性来给字段添加描述,重命名。
```rust
use async_graphql::*;
@ -10,8 +10,6 @@ use async_graphql::*;
#[derive(InputObject)]
struct Coordinate {
latitude: f64,
#[field(desc = "...")]
longitude: f64,
}

View File

@ -28,7 +28,7 @@ impl Circle {
Circle { radius: self.radius * s }.into()
}
#[field(name = "short_description")]
#[graphql(name = "short_description")]
async fn short_description(&self) -> String {
"Circle".to_string()
}
@ -48,7 +48,7 @@ impl Square {
Square { width: self.width * s }.into()
}
#[field(name = "short_description")]
#[graphql(name = "short_description")]
async fn short_description(&self) -> String {
"Square".to_string()
}

View File

@ -2,7 +2,7 @@
简单对象是把Rust结构的所有字段都直接映射到GraphQL对象不支持定义单独的Resolver函数。
下面的例子定义了一个名称为MyObject的对象包含字段`a`和`b``c`由于标记为`#[field(skip)]`所以不会映射到GraphQL。
下面的例子定义了一个名称为MyObject的对象包含字段`a`和`b``c`由于标记为`#[graphql(skip)]`所以不会映射到GraphQL。
**`a`和`b`字段都有描述信息这会反映在GraphQL的内省信息中它们采用了rustdoc和属性两种不同的写法这两种写法`async-graphql`都支持。当两种写法同时存在时,属性的优先级更高。**
@ -14,10 +14,10 @@ struct MyObject {
/// Value a
a: i32,
#[field(desc = "Value b")]
/// Value b
b: i32,
#[field(skip)]
#[graphql(skip)]
c: i32,
}
```

View File

@ -17,7 +17,6 @@ struct Query;
#[Object]
impl Query {
#[field]
async fn parse_with_extensions(&self, input: String) -> FieldResult<i32> {
Ok("234a"
.parse()

View File

@ -14,7 +14,7 @@ struct Query;
#[Object]
impl Query {
async fn input(#[arg(validator(or(Email, MAC(colon = "false"))))] a: String) {
async fn input(#[graphql(validator(or(Email, MAC(colon = "false"))))] a: String) {
}
}
```

View File

@ -12,7 +12,7 @@ async-graphql-tide = "1.18.0" # 如果你需要集成到Tide
## 写一个Schema
一个GraphQL的Schema包含一个必须的查询(Query)根对象,可选的变更(Mutation)根对象和可选的订阅(Subscription)根对象这些对象类型都是用Rust语言的结构来描述它们结构的字段对应GraphQL对象的字段,但你需要用`#[field]`来修饰它,这样`Async-graphql`提供的过程宏才能够正确的识别它
一个GraphQL的Schema包含一个必须的查询(Query)根对象,可选的变更(Mutation)根对象和可选的订阅(Subscription)根对象这些对象类型都是用Rust语言的结构来描述它们结构的字段对应GraphQL对象的字段。
Async-graphql实现了常用数据类型到GraphQL类型的映射例如`i32`, `f64`, `Option<T>`, `Vec<T>`等。同时,你也能够[扩展这些基础类型](custom_scalars.md)基础数据类型在GraphQL里面称为标量。

View File

@ -11,7 +11,7 @@ struct Subscription;
#[Subscription]
impl Subscription {
async fn integers(&self, #[arg(default = "1")] step: i32) -> impl Stream<Item = i32> {
async fn integers(&self, #[graphql(default = "1")] step: i32) -> impl Stream<Item = i32> {
let mut value = 0;
tokio::time::interval(Duration::from_secs(1)).map(move |_| {
value += step;

View File

@ -36,7 +36,7 @@ use yansi::Paint;
///
/// #[Object]
/// impl QueryRoot {
/// #[field(desc = "Returns the sum of a and b")]
/// /// Returns the sum of a and b
/// async fn add(&self, a: i32, b: i32) -> i32 {
/// a + b
/// }

View File

@ -17,7 +17,7 @@ fn quickstart() -> Result<()> {
struct QueryRoot;
#[Object]
impl QueryRoot {
#[field(desc = "Returns the sum of a and b")]
/// Returns the sum of a and b
async fn add(&self, a: i32, b: i32) -> i32 {
a + b
}
@ -90,7 +90,7 @@ fn hello() -> Result<()> {
struct QueryRoot;
#[Object]
impl QueryRoot {
#[field(desc = "Returns hello")]
/// Returns hello
async fn hello<'a>(&self, ctx: &'a Context<'_>) -> String {
let name = ctx.data_opt::<Hello>().map(|hello| hello.0.as_str());
format!("Hello, {}!", name.unwrap_or("world"))

View File

@ -28,7 +28,6 @@ use warp::{Buf, Filter, Rejection, Reply};
///
/// #[Object]
/// impl QueryRoot {
/// #[field]
/// async fn value(&self, ctx: &Context<'_>) -> i32 {
/// unimplemented!()
/// }

View File

@ -23,7 +23,6 @@ use warp::{Filter, Rejection, Reply};
///
/// #[Subscription]
/// impl SubscriptionRoot {
/// #[field]
/// async fn tick(&self) -> impl Stream<Item = String> {
/// tokio::time::interval(Duration::from_secs(1)).map(|n| format!("{}", n.elapsed().as_secs_f32()))
/// }

View File

@ -181,8 +181,6 @@ pub use types::*;
/// Result type
pub type Result<T> = std::result::Result<T, Error>;
// internal types
/// Define a GraphQL object with methods
///
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/define_complex_object.html).*
@ -194,7 +192,6 @@ pub type Result<T> = std::result::Result<T, Error>;
/// | Attribute | description | Type | Optional |
/// |---------------|---------------------------|----------|----------|
/// | name | Object name | string | Y |
/// | desc | Object description | string | Y |
/// | cache_control | Object cache control | [`CacheControl`](struct.CacheControl.html) | Y |
/// | extends | Add fields to an entity that's defined in another service | bool | Y |
///
@ -202,6 +199,7 @@ pub type Result<T> = std::result::Result<T, Error>;
///
/// | Attribute | description | Type | Optional |
/// |---------------|---------------------------|----------|----------|
/// | skip | Skip this field | bool | Y |
/// | name | Field name | string | Y |
/// | desc | Field description | string | Y |
/// | deprecation | Field deprecation reason | string | Y |
@ -210,6 +208,7 @@ pub type Result<T> = std::result::Result<T, Error>;
/// | provides | Annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway. | string | Y |
/// | requires | Annotate the required input fieldset from a base type for a resolver. It is used to develop a query plan where the required fields may not be needed by the client, but the service may need additional information from other services. | string | Y |
/// | guard | Field of guard | [`Guard`](guard/trait.Guard.html) | Y |
/// | post_guard | Field of post guard | [`PostGuard`](guard/trait.PostGuard.html) | Y |
///
/// # Field argument parameters
///
@ -221,6 +220,7 @@ pub type Result<T> = std::result::Result<T, Error>;
/// | default | Argument default value | literal | Y |
/// | default_with | Expression to generate default value | code string | Y |
/// | validator | Input value validator | [`InputValueValidator`](validators/trait.InputValueValidator.html) | Y |
/// | key | Is entity key | bool | Y |
///
/// # Valid field return types
///
@ -229,6 +229,7 @@ pub type Result<T> = std::result::Result<T, Error>;
/// - `Vec<T>`, such as `Vec<i32>`
/// - Slices, such as `&[i32]`
/// - `Option<T>`, such as `Option<i32>`
/// - `BTree<T>`, `HashMap<T>`, `HashSet<T>`, `BTreeSet<T>`, `LinkedList<T>`, `VecDeque<T>`
/// - GraphQL objects.
/// - GraphQL enums.
/// - References to any of the above types, such as `&i32` or `&Option<String>`.
@ -256,22 +257,22 @@ pub type Result<T> = std::result::Result<T, Error>;
///
/// #[Object]
/// impl QueryRoot {
/// #[field(desc = "value")]
/// /// value
/// async fn value(&self) -> i32 {
/// self.value
/// }
///
/// #[field(desc = "reference value")]
/// /// reference value
/// async fn value_ref(&self) -> &i32 {
/// &self.value
/// }
///
/// #[field(desc = "value with error")]
/// /// value with error
/// async fn value_with_error(&self) -> FieldResult<i32> {
/// Ok(self.value)
/// }
///
/// async fn value_with_arg(&self, #[arg(default = 1)] a: i32) -> i32 {
/// async fn value_with_arg(&self, #[graphql(default = 1)] a: i32) -> i32 {
/// a
/// }
/// }
@ -307,15 +308,15 @@ pub use async_graphql_derive::Object;
/// | Attribute | description | Type | Optional |
/// |---------------|---------------------------|----------|----------|
/// | name | Object name | string | Y |
/// | desc | Object description | string | Y |
/// | cache_control | Object cache control | [`CacheControl`](struct.CacheControl.html) | Y |
/// | extends | Add fields to an entity that's defined in another service | bool | Y |
///
/// # Field parameters
///
/// | Attribute | description | Type | Optional |
/// |---------------|---------------------------|----------|----------|
/// | skip | Skip this field | bool | Y |
/// | name | Field name | string | Y |
/// | desc | Field description | string | Y |
/// | deprecation | Field deprecation reason | string | Y |
/// | owned | Field resolver return a ownedship value | bool | Y |
/// | cache_control | Field cache control | [`CacheControl`](struct.CacheControl.html) | Y |
@ -323,6 +324,7 @@ pub use async_graphql_derive::Object;
/// | provides | Annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway. | string | Y |
/// | requires | Annotate the required input fieldset from a base type for a resolver. It is used to develop a query plan where the required fields may not be needed by the client, but the service may need additional information from other services. | string | Y |
/// | guard | Field of guard | [`Guard`](guard/trait.Guard.html) | Y |
/// | post_guard | Field of post guard | [`PostGuard`](guard/trait.PostGuard.html) | Y |
///
/// # Examples
///
@ -353,7 +355,6 @@ pub use async_graphql_derive::SimpleObject;
/// | Attribute | description | Type | Optional |
/// |-------------|---------------------------|----------|----------|
/// | name | Enum name | string | Y |
/// | desc | Enum description | string | Y |
/// | remote | Derive a remote enum | string | Y |
///
/// # Item parameters
@ -361,7 +362,6 @@ pub use async_graphql_derive::SimpleObject;
/// | Attribute | description | Type | Optional |
/// |-------------|---------------------------|----------|----------|
/// | name | Item name | string | Y |
/// | desc | Item description | string | Y |
/// | deprecation | Item deprecation reason | string | Y |
///
/// # Examples
@ -372,7 +372,7 @@ pub use async_graphql_derive::SimpleObject;
/// #[derive(Enum, Copy, Clone, Eq, PartialEq)]
/// enum MyEnum {
/// A,
/// #[item(name = "b")] B,
/// #[graphql(name = "b")] B,
/// }
///
/// struct QueryRoot {
@ -382,12 +382,12 @@ pub use async_graphql_derive::SimpleObject;
///
/// #[Object]
/// impl QueryRoot {
/// #[field(desc = "value")]
/// /// value1
/// async fn value1(&self) -> MyEnum {
/// self.value1
/// }
///
/// #[field(desc = "value")]
/// /// value2
/// async fn value2(&self) -> MyEnum {
/// self.value2
/// }
@ -410,19 +410,17 @@ pub use async_graphql_derive::Enum;
/// | Attribute | description | Type | Optional |
/// |-------------|---------------------------|----------|----------|
/// | name | Object name | string | Y |
/// | desc | Object description | string | Y |
///
/// # Field parameters
///
/// | Attribute | description | Type | Optional |
/// |--------------|------------------------------------------|----------|----------|
/// | name | Field name | string | Y |
/// | desc | Field description | string | Y |
/// | Attribute | description | Type | Optional |
/// |--------------|------------------------------------------|-------------|----------|
/// | name | Field name | string | Y |
/// | default | Use `Default::default` for default value | none | Y |
/// | default | Argument default value | literal | Y |
/// | default_with | Expression to generate default value | code string | Y |
/// | validator | Input value validator | [`InputValueValidator`](validators/trait.InputValueValidator.html) | Y |
/// | flatten | Similar to serde (flatten) | boolean | Y |
/// | flatten | Similar to serde (flatten) | boolean | Y |
///
/// # Examples
///
@ -432,7 +430,7 @@ pub use async_graphql_derive::Enum;
/// #[derive(InputObject)]
/// struct MyInputObject {
/// a: i32,
/// #[field(default = 10)]
/// #[graphql(default = 10)]
/// b: i32,
/// }
///
@ -440,7 +438,7 @@ pub use async_graphql_derive::Enum;
///
/// #[Object]
/// impl QueryRoot {
/// #[field(desc = "value")]
/// /// value
/// async fn value(&self, input: MyInputObject) -> i32 {
/// input.a * input.b
/// }
@ -467,18 +465,22 @@ pub use async_graphql_derive::InputObject;
/// | Attribute | description | Type | Optional |
/// |-------------|---------------------------|----------|----------|
/// | name | Object name | string | Y |
/// | desc | Object description | string | Y |
/// | field | Fields of this Interface | [InterfaceField] | N |
/// | extends | Add fields to an entity that's defined in another service | bool | Y |
///
/// # Field parameters
///
/// | Attribute | description | Type | Optional |
/// |-------------|---------------------------|----------|----------|
/// | name | Field name | string | N |
/// | method | Rust resolver method name. If specified, `name` will not be camelCased in schema definition | string | Y |
/// | type | Field type | string | N |
/// | method | Rust resolver method name. If specified, `name` will not be camelCased in schema definition | string | Y |
/// | desc | Field description | string | Y |
/// | deprecation | Field deprecation reason | string | Y |
/// | args | Field arguments | | Y |
/// | arg | Field arguments | [InterfaceFieldArgument] | Y |
/// | external | Mark a field as owned by another service. This allows service A to use fields from service B while also knowing at runtime the types of that field. | bool | Y |
/// | provides | Annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway. | string | Y |
/// | requires | Annotate the required input fieldset from a base type for a resolver. It is used to develop a query plan where the required fields may not be needed by the client, but the service may need additional information from other services. | string | Y |
///
/// # Field argument parameters
///
@ -535,7 +537,7 @@ pub use async_graphql_derive::InputObject;
/// }
///
/// /// Disabled name transformation, don't forget "method" argument in interface!
/// #[field(name = "value_d")]
/// #[graphql(name = "value_d")]
/// async fn value_d(&self) -> i32 {
/// &self.value + 1
/// }
@ -595,7 +597,6 @@ pub use async_graphql_derive::Interface;
/// | Attribute | description | Type | Optional |
/// |-------------|---------------------------|----------|----------|
/// | name | Object name | string | Y |
/// | desc | Object description | string | Y |
///
/// # Item parameters
///
@ -672,21 +673,20 @@ pub use async_graphql_derive::Union;
/// | Attribute | description | Type | Optional |
/// |-------------|---------------------------|----------|----------|
/// | name | Object name | string | Y |
/// | desc | Object description | string | Y |
///
/// # Field parameters
///
/// | Attribute | description | Type | Optional |
/// |-------------|---------------------------|----------|----------|
/// | name | Field name | string | Y |
/// | desc | Field description | string | Y |
/// | deprecation | Field deprecation reason | string | Y |
/// | guard | Field of guard | [`Guard`](guard/trait.Guard.html) | Y |
/// | post_guard | Field of post guard | [`PostGuard`](guard/trait.PostGuard.html) | Y |
///
/// # Field argument parameters
///
/// | Attribute | description | Type | Optional |
/// |--------------|------------------------------------------|-------------|----------|
/// |--------------|------------------------------------------|------------ |----------|
/// | name | Argument name | string | Y |
/// | desc | Argument description | string | Y |
/// | default | Use `Default::default` for default value | none | Y |
@ -719,7 +719,6 @@ pub use async_graphql_derive::Subscription;
/// | Attribute | description | Type | Optional |
/// |-------------|---------------------------|----------|----------|
/// | name | Scalar name | string | Y |
/// | desc | Scalar description | string | Y |
///
pub use async_graphql_derive::Scalar;
@ -732,7 +731,6 @@ pub use async_graphql_derive::Scalar;
/// | Attribute | description | Type | Optional |
/// |---------------|---------------------------|----------|----------|
/// | name | Object name | string | Y |
/// | desc | Object description | string | Y |
/// | cache_control | Object cache control | [`CacheControl`](struct.CacheControl.html) | Y |
/// | extends | Add fields to an entity that's defined in another service | bool | Y |
///
@ -772,7 +770,6 @@ pub use async_graphql_derive::MergedObject;
/// | Attribute | description | Type | Optional |
/// |---------------|---------------------------|----------|----------|
/// | name | Object name | string | Y |
/// | desc | Object description | string | Y |
///
/// # Examples
///

View File

@ -29,7 +29,7 @@ impl<'a> __Field<'a> {
.collect_vec()
}
#[field(name = "type")]
#[graphql(name = "type")]
async fn ty(&self) -> __Type<'a> {
__Type::new(self.registry, &self.field.ty)
}

View File

@ -17,7 +17,7 @@ impl<'a> __InputValue<'a> {
self.input_value.description.map(|s| s.to_string())
}
#[field(name = "type")]
#[graphql(name = "type")]
async fn ty(&self) -> __Type<'a> {
__Type::new(self.registry, &self.input_value.ty)
}

View File

@ -92,7 +92,7 @@ impl<'a> __Type<'a> {
async fn fields(
&self,
#[arg(default = false)] include_deprecated: bool,
#[graphql(default = false)] include_deprecated: bool,
) -> Option<Vec<__Field<'a>>> {
if let TypeDetail::Named(ty) = &self.detail {
ty.fields().map(|fields| {
@ -155,7 +155,7 @@ impl<'a> __Type<'a> {
async fn enum_values(
&self,
#[arg(default = false)] include_deprecated: bool,
#[graphql(default = false)] include_deprecated: bool,
) -> Option<Vec<__EnumValue<'a>>> {
if let TypeDetail::Named(registry::MetaType::Enum { enum_values, .. }) = &self.detail {
Some(

View File

@ -9,12 +9,12 @@
///
/// #[Object(cache_control(max_age = 60))]
/// impl QueryRoot {
/// #[field(cache_control(max_age = 30))]
/// #[graphql(cache_control(max_age = 30))]
/// async fn value1(&self) -> i32 {
/// 0
/// }
///
/// #[field(cache_control(private))]
/// #[graphql(cache_control(private))]
/// async fn value2(&self) -> i32 {
/// 0
/// }

View File

@ -55,7 +55,10 @@ impl Dog {
unimplemented!()
}
async fn is_housetrained(&self, #[arg(default = true)] at_other_homes: bool) -> Option<bool> {
async fn is_housetrained(
&self,
#[graphql(default = true)] at_other_homes: bool,
) -> Option<bool> {
unimplemented!()
}
@ -266,8 +269,8 @@ impl ComplicatedArgs {
async fn multiple_opts(
&self,
#[arg(default)] opt1: i32,
#[arg(default)] opt2: i32,
#[graphql(default)] opt1: i32,
#[graphql(default)] opt2: i32,
) -> Option<String> {
unimplemented!()
}
@ -276,8 +279,8 @@ impl ComplicatedArgs {
&self,
req1: i32,
req2: i32,
#[arg(default)] opt1: i32,
#[arg(default)] opt2: i32,
#[graphql(default)] opt1: i32,
#[graphql(default)] opt2: i32,
) -> Option<String> {
unimplemented!()
}
@ -336,7 +339,7 @@ pub struct MutationRoot;
#[Object(internal)]
impl MutationRoot {
async fn test_input(&self, #[arg(default)] input: TestInput) -> i32 {
async fn test_input(&self, #[graphql(default)] input: TestInput) -> i32 {
unimplemented!()
}
}

View File

@ -25,17 +25,17 @@ pub use string_validators::{Email, StringMaxLength, StringMinLength, MAC};
/// #[Object]
/// impl QueryRoot {
/// // Input is email address
/// async fn value1(&self, #[arg(validator(Email))] email: String) -> i32 {
/// async fn value1(&self, #[graphql(validator(Email))] email: String) -> i32 {
/// unimplemented!()
/// }
///
/// // Input is email or MAC address
/// async fn value2(&self, #[arg(validator(or(Email, MAC(colon = "false"))))] email_or_mac: String) -> i32 {
/// async fn value2(&self, #[graphql(validator(or(Email, MAC(colon = "false"))))] email_or_mac: String) -> i32 {
/// unimplemented!()
/// }
///
/// // Input is integer between 100 and 200
/// async fn value3(&self, #[arg(validator(IntRange(min = "100", max = "200")))] value: i32) -> i32 {
/// async fn value3(&self, #[graphql(validator(IntRange(min = "100", max = "200")))] value: i32) -> i32 {
/// unimplemented!()
/// }
/// }

View File

@ -6,15 +6,15 @@ pub async fn test_default_value_arg() {
#[Object]
impl Query {
async fn value1(&self, #[arg(default = 100)] input: i32) -> i32 {
async fn value1(&self, #[graphql(default = 100)] input: i32) -> i32 {
input
}
async fn value2(&self, #[arg(default)] input: i32) -> i32 {
async fn value2(&self, #[graphql(default)] input: i32) -> i32 {
input
}
async fn value3(&self, #[arg(default_with = "1 + 2 + 3")] input: i32) -> i32 {
async fn value3(&self, #[graphql(default_with = "1 + 2 + 3")] input: i32) -> i32 {
input
}
}
@ -46,13 +46,13 @@ pub async fn test_default_value_arg() {
pub async fn test_default_value_inputobject() {
#[derive(InputObject)]
struct MyInput {
#[field(default = 100)]
#[graphql(default = 100)]
value1: i32,
#[field(default)]
#[graphql(default)]
value2: i32,
#[field(default_with = "1 + 2 + 3")]
#[graphql(default_with = "1 + 2 + 3")]
value3: i32,
}

View File

@ -8,7 +8,7 @@ struct User {
#[Object(extends)]
impl User {
#[field(external)]
#[graphql(external)]
async fn id(&self) -> &ID {
&self.id
}
@ -26,7 +26,7 @@ impl Review {
todo!()
}
#[field(provides = "username")]
#[graphql(provides = "username")]
async fn author(&self) -> User {
todo!()
}
@ -42,7 +42,7 @@ struct Product {
#[Object(extends)]
impl Product {
#[field(external)]
#[graphql(external)]
async fn upc(&self) -> &str {
&self.upc
}
@ -56,12 +56,12 @@ struct QueryRoot;
#[Object]
impl QueryRoot {
#[entity]
#[graphql(entity)]
async fn find_user_by_id(&self, id: ID) -> User {
User { id }
}
#[entity]
#[graphql(entity)]
async fn find_product_by_upc(&self, upc: String) -> Product {
Product { upc }
}

View File

@ -44,7 +44,7 @@ impl Guard for UserGuard {
pub async fn test_guard() {
#[derive(SimpleObject)]
struct MyObj {
#[field(guard(RoleGuard(role = "Role::Admin")))]
#[graphql(guard(RoleGuard(role = "Role::Admin")))]
value: i32,
}
@ -52,7 +52,7 @@ pub async fn test_guard() {
#[Object]
impl Query {
#[field(guard(RoleGuard(role = "Role::Admin")))]
#[graphql(guard(RoleGuard(role = "Role::Admin")))]
async fn value(&self) -> i32 {
1
}
@ -66,7 +66,7 @@ pub async fn test_guard() {
#[Subscription]
impl Subscription {
#[field(guard(RoleGuard(role = "Role::Admin")))]
#[graphql(guard(RoleGuard(role = "Role::Admin")))]
async fn values(&self) -> impl Stream<Item = i32> {
futures::stream::iter(vec![1, 2, 3])
}
@ -170,7 +170,7 @@ pub async fn test_guard() {
pub async fn test_multiple_guards() {
#[derive(SimpleObject)]
struct Query {
#[field(guard(RoleGuard(role = "Role::Admin"), UserGuard(username = r#""test""#)))]
#[graphql(guard(RoleGuard(role = "Role::Admin"), UserGuard(username = r#""test""#)))]
value: i32,
}
@ -274,7 +274,7 @@ pub async fn test_guard_forward_arguments() {
#[Object]
impl QueryRoot {
#[field(guard(UserGuard(id = "@id")))]
#[graphql(guard(UserGuard(id = "@id")))]
async fn user(&self, id: ID) -> ID {
id
}

View File

@ -4,19 +4,19 @@ use async_graphql::*;
pub async fn test_input_object_default_value() {
#[derive(InputObject)]
struct MyInput {
#[field(default = 999)]
#[graphql(default = 999)]
a: i32,
#[field(default_with = "vec![1, 2, 3]")]
#[graphql(default_with = "vec![1, 2, 3]")]
b: Vec<i32>,
#[field(default = "abc")]
#[graphql(default = "abc")]
c: String,
#[field(default = 999)]
#[graphql(default = 999)]
d: i32,
#[field(default = 999)]
#[graphql(default = 999)]
e: i32,
}
@ -112,15 +112,15 @@ pub async fn test_inputobject_flatten_recursive() {
#[derive(InputObject, Debug, Eq, PartialEq)]
struct B {
#[field(default = 70)]
#[graphql(default = 70)]
b: i32,
#[field(flatten)]
#[graphql(flatten)]
a_obj: A,
}
#[derive(InputObject, Debug, Eq, PartialEq)]
struct MyInputObject {
#[field(flatten)]
#[graphql(flatten)]
b_obj: B,
c: i32,
}
@ -171,7 +171,7 @@ pub async fn test_inputobject_flatten_recursive() {
async fn test_with_default(
&self,
#[arg(default_with = r#"MyInputObject {
#[graphql(default_with = r#"MyInputObject {
b_obj: B {
b: 2,
a_obj: A { a: 1 }
@ -253,13 +253,13 @@ pub async fn test_inputobject_flatten_multiple() {
#[derive(InputObject, Debug, Eq, PartialEq)]
struct ABC {
#[field(flatten)]
#[graphql(flatten)]
a: A,
#[field(flatten)]
#[graphql(flatten)]
b: B,
#[field(flatten)]
#[graphql(flatten)]
c: C,
}

View File

@ -11,7 +11,7 @@ pub async fn test_input_validator_string_min_length() {
#[derive(InputObject)]
struct InputMaxLength {
#[field(validator(StringMinLength(length = "6")))]
#[graphql(validator(StringMinLength(length = "6")))]
pub id: String,
}
@ -19,7 +19,7 @@ pub async fn test_input_validator_string_min_length() {
impl QueryRoot {
async fn field_parameter(
&self,
#[arg(validator(StringMinLength(length = "6")))] _id: String,
#[graphql(validator(StringMinLength(length = "6")))] _id: String,
) -> bool {
true
}
@ -129,7 +129,7 @@ pub async fn test_input_validator_string_max_length() {
#[derive(InputObject)]
struct InputMaxLength {
#[field(validator(StringMaxLength(length = "6")))]
#[graphql(validator(StringMaxLength(length = "6")))]
pub id: String,
}
@ -137,7 +137,7 @@ pub async fn test_input_validator_string_max_length() {
impl QueryRoot {
async fn field_parameter(
&self,
#[arg(validator(StringMaxLength(length = "6")))] _id: String,
#[graphql(validator(StringMaxLength(length = "6")))] _id: String,
) -> bool {
true
}
@ -241,13 +241,13 @@ pub async fn test_input_validator_string_email() {
#[derive(InputObject)]
struct InputEmail {
#[field(validator(Email))]
#[graphql(validator(Email))]
pub email: String,
}
#[Object]
impl QueryRoot {
async fn field_parameter(&self, #[arg(validator(Email))] _email: String) -> bool {
async fn field_parameter(&self, #[graphql(validator(Email))] _email: String) -> bool {
true
}
@ -384,13 +384,13 @@ pub async fn test_input_validator_string_mac() {
#[derive(InputObject)]
struct InputMACWithColon {
#[field(validator(MAC(colon = "true")))]
#[graphql(validator(MAC(colon = "true")))]
pub mac: String,
}
#[derive(InputObject)]
struct InputMACWithoutColon {
#[field(validator(MAC(colon = "false")))]
#[graphql(validator(MAC(colon = "false")))]
pub mac: String,
}
@ -398,7 +398,7 @@ pub async fn test_input_validator_string_mac() {
impl QueryRootWithColon {
async fn field_parameter(
&self,
#[arg(validator(MAC(colon = "true")))] _mac: String,
#[graphql(validator(MAC(colon = "true")))] _mac: String,
) -> bool {
true
}
@ -412,7 +412,7 @@ pub async fn test_input_validator_string_mac() {
impl QueryRootWithoutColon {
async fn field_parameter(
&self,
#[arg(validator(MAC(colon = "false")))] _mac: String,
#[graphql(validator(MAC(colon = "false")))] _mac: String,
) -> bool {
true
}
@ -681,7 +681,7 @@ pub async fn test_input_validator_int_range() {
#[derive(InputObject)]
struct InputIntRange {
#[field(validator(IntRange(min = "-2", max = "5")))]
#[graphql(validator(IntRange(min = "-2", max = "5")))]
pub id: i32,
}
@ -689,7 +689,7 @@ pub async fn test_input_validator_int_range() {
impl QueryRoot {
async fn field_parameter(
&self,
#[arg(validator(IntRange(min = "-2", max = "5")))] _id: i32,
#[graphql(validator(IntRange(min = "-2", max = "5")))] _id: i32,
) -> bool {
true
}
@ -785,7 +785,7 @@ pub async fn test_input_validator_int_less_than() {
#[derive(InputObject)]
struct InputIntLessThan {
#[field(validator(IntLessThan(value = "5")))]
#[graphql(validator(IntLessThan(value = "5")))]
pub id: i32,
}
@ -793,7 +793,7 @@ pub async fn test_input_validator_int_less_than() {
impl QueryRoot {
async fn field_parameter(
&self,
#[arg(validator(IntLessThan(value = "5")))] _id: i32,
#[graphql(validator(IntLessThan(value = "5")))] _id: i32,
) -> bool {
true
}
@ -892,7 +892,7 @@ pub async fn test_input_validator_int_greater_than() {
#[derive(InputObject)]
struct InputIntGreaterThan {
#[field(validator(IntGreaterThan(value = "3")))]
#[graphql(validator(IntGreaterThan(value = "3")))]
pub id: i32,
}
@ -900,7 +900,7 @@ pub async fn test_input_validator_int_greater_than() {
impl QueryRoot {
async fn field_parameter(
&self,
#[arg(validator(IntGreaterThan(value = "3")))] _id: i32,
#[graphql(validator(IntGreaterThan(value = "3")))] _id: i32,
) -> bool {
true
}
@ -1001,13 +1001,13 @@ pub async fn test_input_validator_int_nonzero() {
#[derive(InputObject)]
struct InputIntNonZero {
#[field(validator(IntNonZero))]
#[graphql(validator(IntNonZero))]
pub id: i32,
}
#[Object]
impl QueryRoot {
async fn field_parameter(&self, #[arg(validator(IntNonZero))] _id: i32) -> bool {
async fn field_parameter(&self, #[graphql(validator(IntNonZero))] _id: i32) -> bool {
true
}
@ -1103,13 +1103,16 @@ pub async fn test_input_validator_int_equal() {
#[derive(InputObject)]
struct InputIntEqual {
#[field(validator(IntEqual(value = "5")))]
#[graphql(validator(IntEqual(value = "5")))]
pub id: i32,
}
#[Object]
impl QueryRoot {
async fn field_parameter(&self, #[arg(validator(IntEqual(value = "5")))] _id: i32) -> bool {
async fn field_parameter(
&self,
#[graphql(validator(IntEqual(value = "5")))] _id: i32,
) -> bool {
true
}
@ -1206,7 +1209,7 @@ pub async fn test_input_validator_list_max_length() {
#[derive(InputObject)]
struct InputListMaxLength {
#[field(validator(ListMaxLength(length = "5")))]
#[graphql(validator(ListMaxLength(length = "5")))]
pub id: Vec<i32>,
}
@ -1214,7 +1217,7 @@ pub async fn test_input_validator_list_max_length() {
impl QueryRoot {
async fn field_parameter(
&self,
#[arg(validator(ListMaxLength(length = "5")))] _id: Vec<i32>,
#[graphql(validator(ListMaxLength(length = "5")))] _id: Vec<i32>,
) -> bool {
true
}
@ -1324,7 +1327,7 @@ pub async fn test_input_validator_list_min_length() {
#[derive(InputObject)]
struct InputListMinLength {
#[field(validator(ListMinLength(length = "4")))]
#[graphql(validator(ListMinLength(length = "4")))]
pub id: Vec<i32>,
}
@ -1332,7 +1335,7 @@ pub async fn test_input_validator_list_min_length() {
impl QueryRoot {
async fn field_parameter(
&self,
#[arg(validator(ListMinLength(length = "4")))] _id: Vec<i32>,
#[graphql(validator(ListMinLength(length = "4")))] _id: Vec<i32>,
) -> bool {
true
}
@ -1442,7 +1445,7 @@ pub async fn test_input_validator_operator_or() {
#[derive(InputObject)]
struct InputOrValidator {
#[field(validator(or(Email, MAC(colon = "false"))))]
#[graphql(validator(or(Email, MAC(colon = "false"))))]
pub id: String,
}
@ -1450,7 +1453,7 @@ pub async fn test_input_validator_operator_or() {
impl QueryRoot {
async fn field_parameter(
&self,
#[arg(validator(or(Email, MAC(colon = "false"))))] _id: String,
#[graphql(validator(or(Email, MAC(colon = "false"))))] _id: String,
) -> bool {
true
}
@ -1568,7 +1571,7 @@ pub async fn test_input_validator_operator_and() {
#[derive(InputObject)]
struct InputAndValidator {
#[field(validator(and(Email, StringMinLength(length = "14"))))]
#[graphql(validator(and(Email, StringMinLength(length = "14"))))]
pub email: String,
}
@ -1576,7 +1579,7 @@ pub async fn test_input_validator_operator_and() {
impl QueryRoot {
async fn field_parameter(
&self,
#[arg(validator(and(Email, StringMinLength(length = "14"))))] _email: String,
#[graphql(validator(and(Email, StringMinLength(length = "14"))))] _email: String,
) -> bool {
true
}
@ -1687,7 +1690,7 @@ pub async fn test_input_validator_variable() {
#[derive(InputObject)]
struct InputMaxLength {
#[field(validator(StringMinLength(length = "6")))]
#[graphql(validator(StringMinLength(length = "6")))]
pub id: String,
}
@ -1695,7 +1698,7 @@ pub async fn test_input_validator_variable() {
impl QueryRoot {
async fn field_parameter(
&self,
#[arg(validator(StringMinLength(length = "6")))] _id: String,
#[graphql(validator(StringMinLength(length = "6")))] _id: String,
) -> bool {
true
}

View File

@ -288,7 +288,7 @@ pub async fn test_interface_field_method() {
#[Object]
impl A {
#[field(name = "created_at")]
#[graphql(name = "created_at")]
pub async fn created_at(&self) -> i32 {
1
}
@ -298,7 +298,7 @@ pub async fn test_interface_field_method() {
#[Object]
impl B {
#[field(name = "created_at")]
#[graphql(name = "created_at")]
pub async fn created_at(&self) -> i32 {
2
}

View File

@ -6,7 +6,8 @@ struct Circle {
radius: f32,
}
#[Object(desc = "Circle")]
/// Circle
#[Object]
impl Circle {
async fn scale(&self, s: f32) -> TestInterface {
Circle {
@ -21,9 +22,10 @@ struct Square {
width: f32,
}
#[Object(desc = "Square")]
/// Square
#[Object]
impl Square {
#[field(deprecation = "Field scale is deprecated")]
#[graphql(deprecation = "Field scale is deprecated")]
async fn scale(&self, s: f32) -> TestInterface {
Square {
width: self.width * s,
@ -39,20 +41,21 @@ enum TestInterface {
Square(Square),
}
/// Test Union
#[derive(Clone, Debug, Union)]
#[graphql(desc = "Test Union")]
enum TestUnion {
Circle(Circle),
Square(Square),
}
/// Test Enum
#[derive(Enum, Copy, Clone, Eq, PartialEq)]
#[graphql(desc = "Test Enum")]
enum TestEnum {
#[item(desc = "Kind 1")]
/// Kind 1
Kind1,
#[item(desc = "Kind 2", deprecation = "Kind 2 deprecated")]
/// Kind 2
#[graphql(deprecation = "Kind 2 deprecated")]
Kind2,
}
@ -71,7 +74,8 @@ struct SimpleOption {
#[derive(Clone, Debug)]
struct TestScalar(i32);
#[Scalar(desc = "Test scalar")]
/// Test scalar
#[Scalar]
impl ScalarType for TestScalar {
fn parse(_value: Value) -> InputValueResult<Self> {
Ok(TestScalar(42))
@ -94,17 +98,15 @@ struct SimpleObject {
/// and some more lorem ipsum
a: i32,
#[field(desc = "Value b description")]
/// Value b description
b: String,
/// Value c doc comment
#[field(desc = "Value c description")]
/// Value c description
c: ID,
#[field(desc = "")]
d: SimpleOption,
#[field(deprecation = "Field e is deprecated")]
#[graphql(deprecation = "Field e is deprecated")]
e: bool,
f: TestEnum,
@ -120,7 +122,8 @@ struct SimpleObject {
struct Query;
#[Object(desc = "Global query")]
/// Global query
#[Object]
impl Query {
/// Get a simple object
async fn simple_object(&self) -> SimpleObject {
@ -128,15 +131,16 @@ impl Query {
}
}
/// Simple Input
#[derive(InputObject)]
#[graphql(desc = "Simple Input")]
pub struct SimpleInput {
pub a: String,
}
struct Mutation;
#[Object(desc = "Global mutation")]
/// Global mutation
#[Object]
impl Mutation {
/// simple_mutation description
/// line2
@ -148,10 +152,14 @@ impl Mutation {
struct Subscription;
#[Subscription(desc = "Global subscription")]
/// Global subscription
#[Subscription]
impl Subscription {
/// simple_subscription description
async fn simple_subscription(&self, #[arg(default = 1)] step: i32) -> impl Stream<Item = i32> {
async fn simple_subscription(
&self,
#[graphql(default = 1)] step: i32,
) -> impl Stream<Item = i32> {
stream::once(step)
}
}

View File

@ -24,7 +24,7 @@ impl PostGuard<i32> for RoleGuard {
#[derive(SimpleObject)]
struct MyObj {
#[field(owned, post_guard(UserGuard(username = r#""test""#, value = "88")))]
#[graphql(owned, post_guard(UserGuard(username = r#""test""#, value = "88")))]
value: i32,
}
@ -65,7 +65,7 @@ pub async fn test_post_guard() {
#[Object]
impl Query {
#[field(post_guard(UserGuard(username = r#""test""#, value = "99")))]
#[graphql(post_guard(UserGuard(username = r#""test""#, value = "99")))]
async fn value(&self) -> i32 {
99
}
@ -138,7 +138,7 @@ pub async fn test_post_guard() {
pub async fn test_multiple_post_guards() {
#[derive(SimpleObject)]
struct Query {
#[field(post_guard(
#[graphql(post_guard(
RoleGuard(role = "Role::Admin"),
UserGuard(username = r#""test""#, value = "10")
))]
@ -246,7 +246,7 @@ pub async fn test_post_guard_forward_arguments() {
#[Object]
impl QueryRoot {
#[field(post_guard(UserGuard(id = "@_id")))]
#[graphql(post_guard(UserGuard(id = "@_id")))]
async fn user(&self, _id: ID) -> ID {
"haha".into()
}
@ -302,7 +302,7 @@ pub async fn test_post_guard_generic() {
#[Object]
impl QueryRoot {
#[field(post_guard(UserGuard(id = r#""abc""#)))]
#[graphql(post_guard(UserGuard(id = r#""abc""#)))]
async fn user(&self) -> ID {
"haha".into()
}

View File

@ -309,10 +309,10 @@ pub async fn test_union_flatten() {
#[derive(Union)]
enum MyUnion {
#[item(flatten)]
#[graphql(flatten)]
Inner1(InnerUnion1),
#[item(flatten)]
#[graphql(flatten)]
Inner2(InnerUnion2),
}