Add NewType derive macro. #388

This commit is contained in:
Sunli 2021-01-15 10:29:03 +08:00
parent 8ee82b3d6e
commit 66aadd6319
5 changed files with 158 additions and 4 deletions

View File

@ -542,3 +542,13 @@ pub struct Description {
#[darling(default)]
pub internal: bool,
}
#[derive(FromDeriveInput)]
pub struct NewType {
pub ident: Ident,
pub generics: Generics,
pub data: Data<Ignored, syn::Type>,
#[darling(default)]
pub internal: bool,
}

View File

@ -7,11 +7,10 @@ use crate::utils::{get_crate_name, get_rustdoc, GeneratorResult};
pub fn generate(desc_args: &args::Description) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(desc_args.internal);
let ident = &desc_args.ident;
let generics = &desc_args.generics;
let where_clause = &generics.where_clause;
let (impl_generics, ty_generics, where_clause) = desc_args.generics.split_for_impl();
let doc = get_rustdoc(&desc_args.attrs)?.unwrap_or_default();
let expanded = quote! {
impl #generics #crate_name::Description for #ident #generics #where_clause {
impl #impl_generics #crate_name::Description for #ident #ty_generics #where_clause {
fn description() -> &'static str {
#doc
}

View File

@ -10,6 +10,7 @@ mod input_object;
mod interface;
mod merged_object;
mod merged_subscription;
mod newtype;
mod object;
mod output_type;
mod scalar;
@ -161,7 +162,7 @@ pub fn derive_merged_subscription(input: TokenStream) -> TokenStream {
pub fn derive_merged_description(input: TokenStream) -> TokenStream {
let desc_args =
match args::Description::from_derive_input(&parse_macro_input!(input as DeriveInput)) {
Ok(object_args) => object_args,
Ok(desc_args) => desc_args,
Err(err) => return TokenStream::from(err.write_errors()),
};
match description::generate(&desc_args) {
@ -169,3 +170,16 @@ pub fn derive_merged_description(input: TokenStream) -> TokenStream {
Err(err) => err.write_errors().into(),
}
}
#[proc_macro_derive(NewType)]
pub fn derive_newtype(input: TokenStream) -> TokenStream {
let newtype_args =
match args::NewType::from_derive_input(&parse_macro_input!(input as DeriveInput)) {
Ok(newtype_args) => newtype_args,
Err(err) => return TokenStream::from(err.write_errors()),
};
match newtype::generate(&newtype_args) {
Ok(expanded) => expanded,
Err(err) => err.write_errors().into(),
}
}

76
derive/src/newtype.rs Normal file
View File

@ -0,0 +1,76 @@
use darling::ast::{Data, Style};
use proc_macro::TokenStream;
use quote::quote;
use syn::Error;
use crate::args;
use crate::utils::{get_crate_name, 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 fields = match &newtype_args.data {
Data::Struct(e) => e,
_ => {
return Err(
Error::new_spanned(ident, "NewType can only be applied to an struct.").into(),
)
}
};
if fields.style == Style::Tuple && fields.fields.len() != 1 {
return Err(Error::new_spanned(ident, "Invalid type.").into());
}
let inner_ty = &fields.fields[0];
let expanded = quote! {
#[allow(clippy::all, clippy::pedantic)]
impl #impl_generics #crate_name::ScalarType for #ident #ty_generics #where_clause {
fn parse(value: #crate_name::Value) -> #crate_name::InputValueResult<Self> {
<#inner_ty as #crate_name::ScalarType>::parse(value).map(#ident).map_err(#crate_name::InputValueError::propagate)
}
fn to_value(&self) -> #crate_name::Value {
<#inner_ty as #crate_name::ScalarType>::to_value(&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()
}
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String {
<#inner_ty as #crate_name::Type>::create_type_info(registry)
}
}
#[allow(clippy::all, clippy::pedantic)]
impl #impl_generics #crate_name::InputType for #ident #ty_generics #where_clause {
fn parse(value: ::std::option::Option<#crate_name::Value>) -> #crate_name::InputValueResult<Self> {
<#ident as #crate_name::ScalarType>::parse(value.unwrap_or_default())
}
fn to_value(&self) -> #crate_name::Value {
<#ident as #crate_name::ScalarType>::to_value(self)
}
}
#[allow(clippy::all, clippy::pedantic)]
#[#crate_name::async_trait::async_trait]
impl #impl_generics #crate_name::OutputType for #ident #ty_generics #where_clause {
async fn resolve(
&self,
_: &#crate_name::ContextSelectionSet<'_>,
_field: &#crate_name::Positioned<#crate_name::parser::types::Field>
) -> #crate_name::ServerResult<#crate_name::Value> {
::std::result::Result::Ok(#crate_name::ScalarType::to_value(self))
}
}
};
Ok(expanded.into())
}

View File

@ -874,6 +874,61 @@ pub use async_graphql_derive::Subscription;
///
pub use async_graphql_derive::Scalar;
/// Define a NewType Scalar
///
/// # Examples
///
/// ```rust
/// use async_graphql::*;
///
/// #[derive(NewType)]
/// struct Weight(f64);
///
/// struct QueryRoot;
///
/// #[Object]
/// impl QueryRoot {
/// async fn value(&self) -> Weight {
/// Weight(1.234)
/// }
/// }
///
/// async_std::task::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"
/// }
/// }
/// }]
/// }
/// }));
/// });
/// ```
pub use async_graphql_derive::NewType;
/// Define a merged object with multiple object types.
///
/// *[See also the Book](https://async-graphql.github.io/async-graphql/en/merging_objects.html).*