Add `name` and `visible` attributes for `Newtype` macro for define a new scalar. #437

This commit is contained in:
Sunli 2021-03-31 19:28:19 +08:00
parent 662454c103
commit 37cacf64dc
5 changed files with 168 additions and 8 deletions

View File

@ -6,7 +6,14 @@ nd this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.
## [Unreleased]
## Removed
- Remove `SchemaBuilder::override_name` method. [#437](https://github.com/async-graphql/async-graphql/issues/437)
## Added
- Add `name` and `visible` attributes for `Newtype` macro for define a new scalar. [#437](https://github.com/async-graphql/async-graphql/issues/437)
- `NewType` macro now also implements `From<InnerType>` and `Into<InnerType>`.
## [2.7.1]

View File

@ -2,6 +2,7 @@ use darling::ast::{Data, Fields};
use darling::util::Ignored;
use darling::{FromDeriveInput, FromField, FromMeta, FromVariant};
use inflector::Inflector;
use std::prelude::v1::Result::Err;
use syn::{
Attribute, Generics, Ident, Lit, LitBool, LitStr, Meta, NestedMeta, Path, Type, Visibility,
};
@ -577,14 +578,51 @@ pub struct Description {
pub internal: bool,
}
#[derive(Debug)]
pub enum NewTypeName {
UseNewName(String),
UseRustName,
UseOriginalName,
}
impl Default for NewTypeName {
fn default() -> Self {
Self::UseOriginalName
}
}
impl FromMeta for NewTypeName {
fn from_word() -> darling::Result<Self> {
Ok(Self::UseRustName)
}
fn from_string(value: &str) -> darling::Result<Self> {
Ok(Self::UseNewName(value.to_string()))
}
fn from_bool(value: bool) -> darling::Result<Self> {
if value {
Ok(Self::UseRustName)
} else {
Ok(Self::UseOriginalName)
}
}
}
#[derive(FromDeriveInput)]
#[darling(attributes(graphql), forward_attrs(doc))]
pub struct NewType {
pub ident: Ident,
pub generics: Generics,
pub attrs: Vec<Attribute>,
pub data: Data<Ignored, syn::Type>,
#[darling(default)]
pub internal: bool,
#[darling(default)]
pub name: NewTypeName,
#[darling(default)]
pub visible: Option<Visible>,
}
#[derive(FromMeta, Default)]

View File

@ -175,8 +175,8 @@ pub fn derive_merged_subscription(input: TokenStream) -> TokenStream {
}
}
#[proc_macro_derive(Description)]
pub fn derive_merged_description(input: TokenStream) -> TokenStream {
#[proc_macro_derive(Description, attributes(graphql))]
pub fn derive_description(input: TokenStream) -> TokenStream {
let desc_args =
match args::Description::from_derive_input(&parse_macro_input!(input as DeriveInput)) {
Ok(desc_args) => desc_args,
@ -188,7 +188,7 @@ pub fn derive_merged_description(input: TokenStream) -> TokenStream {
}
}
#[proc_macro_derive(NewType)]
#[proc_macro_derive(NewType, attributes(graphql))]
pub fn derive_newtype(input: TokenStream) -> TokenStream {
let newtype_args =
match args::NewType::from_derive_input(&parse_macro_input!(input as DeriveInput)) {

View File

@ -3,13 +3,22 @@ use proc_macro::TokenStream;
use quote::quote;
use syn::Error;
use crate::args;
use crate::utils::{get_crate_name, GeneratorResult};
use crate::args::{self, NewTypeName, RenameTarget};
use crate::utils::{get_crate_name, get_rustdoc, visible_fn, GeneratorResult};
pub fn generate(newtype_args: &args::NewType) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(newtype_args.internal);
let ident = &newtype_args.ident;
let (impl_generics, ty_generics, where_clause) = newtype_args.generics.split_for_impl();
let gql_typename = match &newtype_args.name {
NewTypeName::UseNewName(name) => Some(name.clone()),
NewTypeName::UseRustName => Some(RenameTarget::Type.rename(ident.to_string())),
NewTypeName::UseOriginalName => None,
};
let desc = get_rustdoc(&newtype_args.attrs)?
.map(|s| quote! { ::std::option::Option::Some(#s) })
.unwrap_or_else(|| quote! {::std::option::Option::None});
let visible = visible_fn(&newtype_args.visible);
let fields = match &newtype_args.data {
Data::Struct(e) => e,
@ -24,6 +33,22 @@ pub fn generate(newtype_args: &args::NewType) -> GeneratorResult<TokenStream> {
return Err(Error::new_spanned(ident, "Invalid type.").into());
}
let inner_ty = &fields.fields[0];
let type_name = match &gql_typename {
Some(name) => quote! { ::std::borrow::Cow::Borrowed(#name) },
None => quote! { <#inner_ty as #crate_name::Type>::type_name() },
};
let create_type_info = if let Some(name) = &gql_typename {
quote! {
registry.create_type::<#ident, _>(|_| #crate_name::registry::MetaType::Scalar {
name: ::std::borrow::ToOwned::to_owned(#name),
description: #desc,
is_valid: |value| <#ident as #crate_name::ScalarType>::is_valid(value),
visible: #visible,
})
}
} else {
quote! { <#inner_ty as #crate_name::Type>::create_type_info(registry) }
};
let expanded = quote! {
#[allow(clippy::all, clippy::pedantic)]
@ -37,14 +62,26 @@ pub fn generate(newtype_args: &args::NewType) -> GeneratorResult<TokenStream> {
}
}
impl #impl_generics ::std::convert::From<#inner_ty> for #ident #ty_generics #where_clause {
fn from(value: #inner_ty) -> Self {
Self(value)
}
}
impl #impl_generics ::std::convert::Into<#inner_ty> for #ident #ty_generics #where_clause {
fn into(self) -> #inner_ty {
self.0
}
}
#[allow(clippy::all, clippy::pedantic)]
impl #impl_generics #crate_name::Type for #ident #ty_generics #where_clause {
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
<#inner_ty as #crate_name::Type>::type_name()
#type_name
}
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String {
<#inner_ty as #crate_name::Type>::create_type_info(registry)
#create_type_info
}
}

View File

@ -952,8 +952,21 @@ pub use async_graphql_derive::Scalar;
/// Define a NewType Scalar
///
/// It also implements `From<InnerType>` and `Into<InnerType>`.
///
/// # Macro parameters
///
/// | Attribute | description | Type | Optional |
/// |-------------|---------------------------|----------|----------|
/// | name | If this attribute is provided then define a new scalar, otherwise it is just a transparent proxy for the internal scalar. | string | Y |
/// | name | If this attribute is provided then define a new scalar, otherwise it is just a transparent proxy for the internal scalar. | bool | Y |
/// | visible(Only valid for new scalars.) | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y |
/// | visible(Only valid for new scalars.) | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
///
/// # Examples
///
/// ## Use the original scalar name
///
/// ```rust
/// use async_graphql::*;
///
@ -969,6 +982,64 @@ pub use async_graphql_derive::Scalar;
/// }
/// }
///
/// // Test conversion
/// let weight: Weight = 10f64.into();
/// let weight_f64: f64 = weight.into();
///
/// tokio::runtime::Runtime::new().unwrap().block_on(async move {
/// let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription).data("hello".to_string()).finish();
///
/// let res = schema.execute("{ value }").await.into_result().unwrap().data;
/// assert_eq!(res, value!({
/// "value": 1.234,
/// }));
///
/// let res = schema.execute(r#"
/// {
/// __type(name: "QueryRoot") {
/// fields {
/// name type {
/// kind
/// ofType { name }
/// }
/// }
/// }
/// }"#).await.into_result().unwrap().data;
/// assert_eq!(res, value!({
/// "__type": {
/// "fields": [{
/// "name": "value",
/// "type": {
/// "kind": "NON_NULL",
/// "ofType": {
/// "name": "Float"
/// }
/// }
/// }]
/// }
/// }));
/// });
/// ```
///
/// ## Define a new scalar
///
/// ```rust
/// use async_graphql::*;
///
/// /// Widget NewType
/// #[derive(NewType)]
/// #[graphql(name)] // or: #[graphql(name = true)], #[graphql(name = "Weight")]
/// struct Weight(f64);
///
/// struct QueryRoot;
///
/// #[Object]
/// impl QueryRoot {
/// async fn value(&self) -> Weight {
/// Weight(1.234)
/// }
/// }
///
/// tokio::runtime::Runtime::new().unwrap().block_on(async move {
/// let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription).data("hello".to_string()).finish();
///
@ -995,12 +1066,19 @@ pub use async_graphql_derive::Scalar;
/// "type": {
/// "kind": "NON_NULL",
/// "ofType": {
/// "name": "Float"
/// "name": "Weight"
/// }
/// }
/// }]
/// }
/// }));
///
/// assert_eq!(schema.execute(r#"{ __type(name: "Weight") { name description } }"#).
/// await.into_result().unwrap().data, value!({
/// "__type": {
/// "name": "Weight", "description": "Widget NewType"
/// }
/// }));
/// });
/// ```
pub use async_graphql_derive::NewType;