juniper/juniper_codegen/src/derive_enum.rs
Kai Ren cbf16c5a33
Make interfaces great again! (#682)
* Bootstrap

* Upd

* Bootstrap macro

* Revert stuff

* Correct PoC to compile

* Bootstrap #[graphql_interface] expansion

* Bootstrap #[graphql_interface] meta parsing

* Bootstrap #[graphql_interface] very basic code generation [skip ci]

* Upd trait code generation and fix keywords usage [skip ci]

* Expand trait impls [skip ci]

* Tune up objects [skip ci]

* Finally! Complies at least... [skip ci]

* Parse meta for fields and its arguments [skip ci]

- also, refactor and bikeshed new macros code

* Impl filling fields meta and bootstrap field resolution [skip ci]

* Poking with fields resolution [skip ci]

* Solve Rust's teen async HRTB problems [skip ci]

* Start parsing trait methods [skip ci]

* Finish parsing fields from trait methods [skip ci]

* Autodetect trait asyncness and allow to specify it [skip ci]

* Allow to autogenerate trait object alias via attribute

* Support generics in trait definition and asyncify them correctly

* Temporary disable explicit async

* Cover arguments and custom names/descriptions in tests

* Re-enable tests with explicit async and fix the codegen to satisfy it

* Check implementers are registered in schema and vice versa

* Check argument camelCases

* Test argument defaults, and allow Into coercions for them

* Re-enable markers

* Re-enable markers and relax Sized requirement on IsInputType/IsOutputType marker traits

* Revert 'juniper_actix' fmt

* Fix missing marks for object

* Fix subscriptions marks

* Deduce result type correctly via traits

* Final fixes

* Fmt

* Restore marks checking

* Support custom ScalarValue

* Cover deprecations with tests

* Impl dowcasting via methods

* Impl dowcasting via external functions

* Support custom context, vol. 1

* Support custom context, vol. 2

* Cover fallible field with test

* Impl explicit generic ScalarValue, vol.1

* Impl explicit generic ScalarValue, vol.2

* Allow passing executor into methods

* Generating enum, vol.1

* Generating enum, vol.2

* Generating enum, vol.3

* Generating enum, vol.3

* Generating enum, vol.4

* Generating enum, vol.5

* Generating enum, vol.6

* Generating enum, vol.7

* Generating enum, vol.8

* Refactor juniper stuff

* Fix juniper tests, vol.1

* Fix juniper tests, vol.2

* Polish 'juniper' crate changes, vol.1

* Polish 'juniper' crate changes, vol.2

* Remove redundant stuf

* Polishing 'juniper_codegen', vol.1

* Polishing 'juniper_codegen', vol.2

* Polishing 'juniper_codegen', vol.3

* Polishing 'juniper_codegen', vol.4

* Polishing 'juniper_codegen', vol.5

* Polishing 'juniper_codegen', vol.6

* Polishing 'juniper_codegen', vol.7

* Polishing 'juniper_codegen', vol.8

* Polishing 'juniper_codegen', vol.9

* Fix other crates tests and make Clippy happier

* Fix examples

* Add codegen failure tests, vol. 1

* Add codegen failure tests, vol. 2

* Add codegen failure tests, vol.3

* Fix codegen failure tests accordingly to latest nightly Rust

* Fix codegen when interface has no implementers

* Fix warnings in book tests

* Describing new interfaces in Book, vol.1

Co-authored-by: Christian Legnitto <LegNeato@users.noreply.github.com>
2020-10-05 21:21:01 -10:00

148 lines
4.7 KiB
Rust

use proc_macro2::TokenStream;
use quote::quote;
use syn::{ext::IdentExt, spanned::Spanned, Data, Fields};
use crate::{
result::{GraphQLScope, UnsupportedAttribute},
util::{self, span_container::SpanContainer},
};
pub fn impl_enum(ast: syn::DeriveInput, error: GraphQLScope) -> syn::Result<TokenStream> {
let ast_span = ast.span();
if !ast.generics.params.is_empty() {
return Err(error.custom_error(ast_span, "does not support generics or lifetimes"));
}
let variants = match ast.data {
Data::Enum(enum_data) => enum_data.variants,
_ => return Err(error.custom_error(ast_span, "can only be applied to enums")),
};
// Parse attributes.
let attrs = util::ObjectAttributes::from_attrs(&ast.attrs)?;
let ident = &ast.ident;
let name = attrs
.name
.clone()
.map(SpanContainer::into_inner)
.unwrap_or_else(|| ident.unraw().to_string());
let fields = variants
.into_iter()
.filter_map(|field| {
let span = field.span();
let field_attrs = match util::FieldAttributes::from_attrs(
&field.attrs,
util::FieldAttributeParseMode::Object,
) {
Ok(attrs) => attrs,
Err(err) => {
proc_macro_error::emit_error!(err);
return None;
}
};
let field_name = field.ident;
let name = field_attrs
.name
.clone()
.map(SpanContainer::into_inner)
.unwrap_or_else(|| util::to_upper_snake_case(&field_name.unraw().to_string()));
let resolver_code = quote!( #ident::#field_name );
let _type = match field.fields {
Fields::Unit => syn::parse_str(&field_name.to_string()).unwrap(),
_ => {
error.emit_custom(
field.fields.span(),
"all fields of the enum must be unnamed, e.g., None",
);
return None;
}
};
if let Some(skip) = field_attrs.skip {
error.unsupported_attribute(skip.span(), UnsupportedAttribute::Skip);
return None;
}
if name.starts_with("__") {
error.no_double_underscore(if let Some(name) = field_attrs.name {
name.span_ident()
} else {
field_name.span()
});
}
if let Some(default) = field_attrs.default {
error.unsupported_attribute_within(
default.span_ident(),
UnsupportedAttribute::Default,
);
}
Some(util::GraphQLTypeDefinitionField {
name,
_type,
args: Vec::new(),
description: field_attrs.description.map(SpanContainer::into_inner),
deprecation: field_attrs.deprecation.map(SpanContainer::into_inner),
resolver_code,
is_type_inferred: true,
is_async: false,
default: None,
span,
})
})
.collect::<Vec<_>>();
proc_macro_error::abort_if_dirty();
if fields.is_empty() {
error.not_empty(ast_span);
}
if let Some(duplicates) =
crate::util::duplicate::Duplicate::find_by_key(&fields, |field| &field.name)
{
error.duplicate(duplicates.iter())
}
if !attrs.interfaces.is_empty() {
attrs.interfaces.iter().for_each(|elm| {
error.unsupported_attribute(elm.span(), UnsupportedAttribute::Interface)
});
}
if let Some(scalar) = attrs.scalar {
error.unsupported_attribute(scalar.span_ident(), UnsupportedAttribute::Scalar);
}
if !attrs.is_internal && name.starts_with("__") {
error.no_double_underscore(if let Some(name) = attrs.name {
name.span_ident()
} else {
ident.span()
});
}
proc_macro_error::abort_if_dirty();
let definition = util::GraphQLTypeDefiniton {
name,
_type: syn::parse_str(&ast.ident.to_string()).unwrap(),
context: attrs.context.map(SpanContainer::into_inner),
scalar: None,
description: attrs.description.map(SpanContainer::into_inner),
fields,
// NOTICE: only unit variants allow -> no generics possible
generics: syn::Generics::default(),
interfaces: vec![],
include_type_generics: true,
generic_scalar: true,
no_async: attrs.no_async.is_some(),
};
Ok(definition.into_enum_tokens())
}