Skip to content

Commit 26d9daa

Browse files
authored
Rework #[derive(GraphQLEnum)] macro implementation (#1047)
1 parent 4dd5150 commit 26d9daa

30 files changed

+4931
-562
lines changed

juniper/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ schema-language = ["graphql-parser"]
3838
anyhow = { version = "1.0.32", default-features = false, optional = true }
3939
async-trait = "0.1.39"
4040
bigdecimal = { version = "0.3", optional = true }
41-
bson = { version = "2.0", features = ["chrono-0_4"], optional = true }
41+
bson = { version = "2.3", features = ["chrono-0_4"], optional = true }
4242
chrono = { version = "0.4", features = ["alloc"], default-features = false, optional = true }
4343
chrono-tz = { version = "0.6", default-features = false, optional = true }
4444
fnv = "1.0.3"

juniper/src/integrations/bson.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ mod utc_date_time {
2828
use super::*;
2929

3030
pub(super) fn to_output<S: ScalarValue>(v: &UtcDateTime) -> Value<S> {
31-
Value::scalar((*v).to_rfc3339_string())
31+
Value::scalar(
32+
(*v).try_to_rfc3339_string()
33+
.unwrap_or_else(|e| panic!("failed to format `UtcDateTime` as RFC3339: {}", e)),
34+
)
3235
}
3336

3437
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<UtcDateTime, String> {

juniper_codegen/src/derive_enum.rs

Lines changed: 0 additions & 153 deletions
This file was deleted.
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
//! Code generation for `#[derive(GraphQLEnum)]` macro.
2+
3+
use proc_macro2::TokenStream;
4+
use quote::ToTokens as _;
5+
use std::collections::HashSet;
6+
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned};
7+
8+
use crate::{
9+
common::scalar,
10+
result::GraphQLScope,
11+
util::{span_container::SpanContainer, RenameRule},
12+
};
13+
14+
use super::{ContainerAttr, Definition, ValueDefinition, VariantAttr};
15+
16+
/// [`GraphQLScope`] of errors for `#[derive(GraphQLEnum)]` macro.
17+
const ERR: GraphQLScope = GraphQLScope::EnumDerive;
18+
19+
/// Expands `#[derive(GraphQLEnum)]` macro into generated code.
20+
pub(crate) fn expand(input: TokenStream) -> syn::Result<TokenStream> {
21+
let ast = syn::parse2::<syn::DeriveInput>(input)?;
22+
let attr = ContainerAttr::from_attrs("graphql", &ast.attrs)?;
23+
24+
let data = if let syn::Data::Enum(data) = &ast.data {
25+
data
26+
} else {
27+
return Err(ERR.custom_error(ast.span(), "can only be derived on enums"));
28+
};
29+
30+
let mut has_ignored_variants = false;
31+
let renaming = attr
32+
.rename_values
33+
.map(SpanContainer::into_inner)
34+
.unwrap_or(RenameRule::ScreamingSnakeCase);
35+
let values = data
36+
.variants
37+
.iter()
38+
.filter_map(|v| {
39+
parse_value(v, renaming).or_else(|| {
40+
has_ignored_variants = true;
41+
None
42+
})
43+
})
44+
.collect::<Vec<_>>();
45+
46+
proc_macro_error::abort_if_dirty();
47+
48+
if values.is_empty() {
49+
return Err(ERR.custom_error(
50+
data.variants.span(),
51+
"expected at least 1 non-ignored enum variant",
52+
));
53+
}
54+
55+
let unique_values = values.iter().map(|v| &v.name).collect::<HashSet<_>>();
56+
if unique_values.len() != values.len() {
57+
return Err(ERR.custom_error(
58+
data.variants.span(),
59+
"expected all GraphQL enum values to have unique names",
60+
));
61+
}
62+
63+
let name = attr
64+
.name
65+
.clone()
66+
.map(SpanContainer::into_inner)
67+
.unwrap_or_else(|| ast.ident.unraw().to_string())
68+
.into_boxed_str();
69+
if !attr.is_internal && name.starts_with("__") {
70+
ERR.no_double_underscore(
71+
attr.name
72+
.as_ref()
73+
.map(SpanContainer::span_ident)
74+
.unwrap_or_else(|| ast.ident.span()),
75+
);
76+
}
77+
78+
let context = attr
79+
.context
80+
.map_or_else(|| parse_quote! { () }, SpanContainer::into_inner);
81+
82+
let description = attr.description.map(|d| d.into_inner().into_boxed_str());
83+
84+
let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
85+
86+
proc_macro_error::abort_if_dirty();
87+
88+
let definition = Definition {
89+
ident: ast.ident,
90+
generics: ast.generics,
91+
name,
92+
description,
93+
context,
94+
scalar,
95+
values,
96+
has_ignored_variants,
97+
};
98+
99+
Ok(definition.into_token_stream())
100+
}
101+
102+
/// Parses a [`ValueDefinition`] from the given Rust enum variant definition.
103+
///
104+
/// Returns [`None`] if the parsing fails, or the enum variant is ignored.
105+
fn parse_value(v: &syn::Variant, renaming: RenameRule) -> Option<ValueDefinition> {
106+
let attr = VariantAttr::from_attrs("graphql", &v.attrs)
107+
.map_err(|e| proc_macro_error::emit_error!(e))
108+
.ok()?;
109+
110+
if attr.ignore.is_some() {
111+
return None;
112+
}
113+
114+
if !v.fields.is_empty() {
115+
err_variant_with_fields(&v.fields)?;
116+
}
117+
118+
let name = attr
119+
.name
120+
.map_or_else(
121+
|| renaming.apply(&v.ident.unraw().to_string()),
122+
SpanContainer::into_inner,
123+
)
124+
.into_boxed_str();
125+
126+
let description = attr.description.map(|d| d.into_inner().into_boxed_str());
127+
128+
let deprecated = attr.deprecated.map(|desc| {
129+
desc.into_inner()
130+
.as_ref()
131+
.map(|lit| lit.value().into_boxed_str())
132+
});
133+
134+
Some(ValueDefinition {
135+
ident: v.ident.clone(),
136+
name,
137+
description,
138+
deprecated,
139+
})
140+
}
141+
142+
/// Emits "no fields allowed for non-ignored variants" [`syn::Error`] pointing
143+
/// to the given `span`.
144+
pub fn err_variant_with_fields<T, S: Spanned>(span: &S) -> Option<T> {
145+
ERR.emit_custom(span.span(), "no fields allowed for non-ignored variants");
146+
None
147+
}

0 commit comments

Comments
 (0)