Make Object and Subscription macros support #cfg(...) attribute. #281

This commit is contained in:
Sunli 2020-09-27 10:20:20 +08:00
parent b47d08c5b5
commit 7d3eb9b62c
7 changed files with 56 additions and 115 deletions

View File

@ -205,7 +205,6 @@ pub struct Field {
pub owned: bool,
pub guard: Option<TokenStream>,
pub post_guard: Option<TokenStream>,
pub features: Vec<String>,
}
impl Field {
@ -217,7 +216,6 @@ impl Field {
let mut external = false;
let mut provides = None;
let mut requires = None;
let mut features = Vec::new();
let mut owned = false;
let mut guard = None;
let mut post_guard = None;
@ -290,20 +288,6 @@ impl Field {
"Attribute 'requires' should be a string.",
));
}
} else if nv.path.is_ident("feature") {
if let syn::Lit::Str(lit) = &nv.lit {
features = lit
.value()
.to_string()
.split(',')
.map(|s| s.trim().to_string())
.collect();
} else {
return Err(Error::new_spanned(
&nv.lit,
"Attribute 'feature' should be a string.",
));
}
}
}
NestedMeta::Meta(Meta::List(ls)) => {
@ -334,7 +318,6 @@ impl Field {
owned,
guard,
post_guard,
features,
}))
}
}

View File

@ -1,6 +1,6 @@
use crate::args;
use crate::output_type::OutputType;
use crate::utils::{feature_block, get_crate_name, get_param_getter_ident, get_rustdoc};
use crate::utils::{get_cfg_attrs, get_crate_name, get_param_getter_ident, get_rustdoc};
use inflector::Inflector;
use proc_macro::TokenStream;
use quote::quote;
@ -45,6 +45,8 @@ 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 cfg_attrs = get_cfg_attrs(&method.attrs);
if method.sig.asyncness.is_none() {
return Err(Error::new_spanned(&method, "Must be asynchronous"));
}
@ -182,6 +184,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
find_entities.push((
args.len(),
quote! {
#(#cfg_attrs)*
if typename == &<#entity_type as #crate_name::Type>::type_name() {
if let (#(#key_pat),*) = (#(#key_getter),*) {
#(#requires_getter)*
@ -221,7 +224,6 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
.map(|s| quote! {Some(#s)})
.unwrap_or_else(|| quote! {None});
let external = field.external;
let features = field.features;
let requires = match &field.requires {
Some(requires) => quote! { Some(#requires) },
None => quote! { None },
@ -246,6 +248,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
}
}
};
let cfg_attrs = get_cfg_attrs(&method.attrs);
let mut create_ctx = true;
let mut args = Vec::new();
@ -357,6 +360,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
let schema_ty = ty.value_type();
schema_fields.push(quote! {
#(#cfg_attrs)*
fields.insert(#field_name.to_string(), #crate_name::registry::MetaField {
name: #field_name.to_string(),
description: #field_desc,
@ -390,13 +394,6 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
.expect("invalid result type");
}
method.block =
syn::parse2::<Block>(feature_block(&crate_name, &features, &field_name, {
let block = &method.block;
quote! { #block }
}))
.expect("invalid block");
let resolve_obj = quote! {
{
let res = self.#field_ident(ctx, #(#use_params),*).await;
@ -418,6 +415,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
});
resolvers.push(quote! {
#(#cfg_attrs)*
if ctx.item.node.name.node == #field_name {
#(#get_params)*
#guard

View File

@ -1,5 +1,5 @@
use crate::args;
use crate::utils::{feature_block, get_crate_name, get_rustdoc};
use crate::utils::{get_crate_name, get_rustdoc};
use inflector::Inflector;
use proc_macro::TokenStream;
use quote::quote;
@ -103,33 +103,20 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
.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()))?; });
let features = &field.features;
getters.push(if !field.owned {
let block = feature_block(
&crate_name,
&features,
&field_name,
quote! { Ok(&self.#ident) },
);
quote! {
#[inline]
#[allow(missing_docs)]
#vis async fn #ident(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::FieldResult<&#ty> {
#block
Ok(&self.#ident)
}
}
} else {
let block = feature_block(
&crate_name,
&features,
&field_name,
quote! { Ok(self.#ident.clone()) },
);
quote! {
#[inline]
#[allow(missing_docs)]
#vis async fn #ident(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::FieldResult<#ty> {
#block
Ok(self.#ident.clone())
}
}
});

View File

@ -1,6 +1,6 @@
use crate::args;
use crate::output_type::OutputType;
use crate::utils::{feature_block, get_crate_name, get_param_getter_ident, get_rustdoc};
use crate::utils::{get_cfg_attrs, get_crate_name, get_param_getter_ident, get_rustdoc};
use inflector::Inflector;
use proc_macro::TokenStream;
use quote::quote;
@ -59,7 +59,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 features = field.features;
let cfg_attrs = get_cfg_attrs(&method.attrs);
if method.sig.asyncness.is_none() {
return Err(Error::new_spanned(
@ -207,14 +207,8 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
.expect("invalid result type");
}
method.block =
syn::parse2::<Block>(feature_block(&crate_name, &features, &field_name, {
let block = &method.block;
quote! { #block }
}))
.expect("invalid block");
schema_fields.push(quote! {
#(#cfg_attrs)*
fields.insert(#field_name.to_string(), #crate_name::registry::MetaField {
name: #field_name.to_string(),
description: #field_desc,
@ -327,6 +321,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
};
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(

View File

@ -278,29 +278,10 @@ pub fn get_param_getter_ident(name: &str) -> Ident {
Ident::new(&format!("__{}_getter", name), Span::call_site())
}
pub fn feature_block(
crate_name: &TokenStream,
features: &[String],
field_name: &str,
block: TokenStream,
) -> TokenStream {
if !features.is_empty() {
let error_message = format!(
"`{}` is only available if the features `{}` are enabled",
field_name,
features.join(",")
);
quote!({
#[cfg(not(all(#(feature = #features),*)))]
{
return Err(#crate_name::FieldError::from(#error_message)).map_err(::std::convert::Into::into);
}
#[cfg(all(#(feature = #features),*))]
{
#block
}
})
} else {
block
}
pub fn get_cfg_attrs(attrs: &[Attribute]) -> Vec<Attribute> {
attrs
.iter()
.filter(|attr| !attr.path.segments.is_empty() && attr.path.segments[0].ident == "cfg")
.cloned()
.collect()
}

View File

@ -205,7 +205,6 @@ 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 |
/// | feature | It's like a `#[cfg(feature = "foo")]` attribute but instead of not compiling this field it will just return a proper `FieldError` to tell you this feature is not enabled | string ("feature1,feature2") | Y |
///
/// # Field argument parameters
///
@ -319,7 +318,6 @@ 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 |
/// | feature | It's like a `#[cfg(feature = "foo")]` attribute but instead of not compiling this field it will just return a proper `FieldError` to tell you this feature is not enabled | string ("feature1,feature2") | Y |
///
/// # Examples
///
@ -672,7 +670,6 @@ pub use async_graphql_derive::Union;
/// | desc | Field description | string | Y |
/// | deprecation | Field deprecation reason | string | Y |
/// | guard | Field of guard | [`Guard`](guard/trait.Guard.html) | Y |
/// | feature | It's like a `#[cfg(feature = "foo")]` attribute but instead of not compiling this field it will just return a proper `FieldError` to tell you this feature is not enabled | string ("feature1,feature2") | Y |
///
/// # Field argument parameters
///

View File

@ -2,35 +2,32 @@
use async_graphql::*;
use futures::{Stream, StreamExt};
use std::pin::Pin;
#[async_std::test]
pub async fn test_field_features() {
#[derive(SimpleObject)]
struct MyObj {
value: i32,
#[field(feature = "bson")]
#[cfg(feature = "bson")]
value_bson: i32,
#[field(feature = "abc")]
#[cfg(feature = "abc")]
value_abc: i32,
}
struct Subscription;
struct SubscriptionRoot;
#[Subscription]
impl Subscription {
impl SubscriptionRoot {
async fn values(&self) -> impl Stream<Item = i32> {
futures::stream::once(async move { 10 })
}
#[field(feature = "bson")]
#[cfg(feature = "bson")]
async fn values_bson(&self) -> impl Stream<Item = i32> {
futures::stream::once(async move { 10 })
}
#[field(feature = "abc")]
#[cfg(feature = "abc")]
async fn values_abc(
&self,
) -> Pin<Box<dyn async_graphql::futures::Stream<Item = i32> + Send + 'static>> {
@ -46,12 +43,12 @@ pub async fn test_field_features() {
10
}
#[field(feature = "bson")]
#[cfg(feature = "bson")]
async fn value_bson(&self) -> i32 {
10
}
#[field(feature = "abc")]
#[cfg(feature = "abc")]
async fn value_abc(&self) -> i32 {
10
}
@ -59,13 +56,15 @@ pub async fn test_field_features() {
async fn obj(&self) -> MyObj {
MyObj {
value: 10,
#[cfg(feature = "bson")]
value_bson: 10,
#[cfg(feature = "abc")]
value_abc: 10,
}
}
}
let schema = Schema::new(QueryRoot, EmptyMutation, Subscription);
let schema = Schema::new(QueryRoot, EmptyMutation, SubscriptionRoot);
let query = "{ value }";
assert_eq!(
schema.execute(query).await.data,
@ -85,13 +84,13 @@ pub async fn test_field_features() {
let query = "{ valueAbc }";
assert_eq!(
schema.execute(query).await.into_result().unwrap_err(),
Error::Query {
pos: Pos { column: 3, line: 1 },
path: Some(serde_json::json!(["valueAbc"])),
err: QueryError::FieldError {
err: "`valueAbc` is only available if the features `abc` are enabled".to_string(),
extended_error: None
}
Error::Rule {
errors: vec![RuleError {
locations: vec![Pos { line: 1, column: 3 }],
message: r#"Unknown field "valueAbc" on type "QueryRoot". Did you mean "value"?"#
.to_string(),
}]
.into(),
}
);
@ -114,13 +113,13 @@ pub async fn test_field_features() {
let query = "{ obj { valueAbc } }";
assert_eq!(
schema.execute(query).await.into_result().unwrap_err(),
Error::Query {
pos: Pos { column: 9, line: 1 },
path: Some(serde_json::json!(["obj", "valueAbc"])),
err: QueryError::FieldError {
err: "`valueAbc` is only available if the features `abc` are enabled".to_string(),
extended_error: None
}
Error::Rule {
errors: vec![RuleError {
locations: vec![Pos { line: 1, column: 9 }],
message: r#"Unknown field "valueAbc" on type "MyObj". Did you mean "value"?"#
.to_string(),
}]
.into(),
}
);
@ -152,16 +151,17 @@ pub async fn test_field_features() {
.unwrap()
.error
.unwrap(),
Error::Query {
pos: Pos {
column: 16,
line: 1
},
path: Some(serde_json::json!(["valuesAbc"])),
err: QueryError::FieldError {
err: "`valuesAbc` is only available if the features `abc` are enabled".to_string(),
extended_error: None
}
Error::Rule {
errors: vec![RuleError {
locations: vec![Pos {
line: 1,
column: 16
}],
message:
r#"Unknown field "valuesAbc" on type "SubscriptionRoot". Did you mean "values", "valuesBson"?"#
.to_string(),
}]
.into(),
}
);
}