cynic_codegen/enum_derive/
input.rs1use darling::util::SpannedValue;
2use proc_macro2::Span;
3
4use crate::{
5 error::Errors,
6 idents::{RenamableFieldIdent, RenameAll},
7 schema::SchemaInput,
8};
9
10#[derive(darling::FromDeriveInput)]
11#[darling(attributes(cynic), supports(enum_unit, enum_newtype))]
12pub struct EnumDeriveInput {
13 pub(super) ident: proc_macro2::Ident,
14 pub(super) data: darling::ast::Data<SpannedValue<EnumDeriveVariant>, ()>,
15
16 #[darling(default)]
17 schema: Option<SpannedValue<String>>,
18 #[darling(default)]
19 schema_path: Option<SpannedValue<String>>,
20
21 #[darling(default, rename = "schema_module")]
22 schema_module_: Option<syn::Path>,
23
24 #[darling(default)]
25 pub graphql_type: Option<SpannedValue<String>>,
26
27 #[darling(default)]
28 pub(super) rename_all: Option<RenameAll>,
29
30 #[darling(default)]
31 pub(super) non_exhaustive: bool,
32}
33
34impl EnumDeriveInput {
35 pub fn schema_module(&self) -> syn::Path {
36 if let Some(schema_module) = &self.schema_module_ {
37 return schema_module.clone();
38 }
39 syn::parse2(quote::quote! { schema }).unwrap()
40 }
41}
42
43#[derive(Debug, darling::FromVariant)]
44#[darling(attributes(cynic))]
45pub struct EnumDeriveVariant {
46 pub(super) ident: proc_macro2::Ident,
47
48 #[darling(default)]
49 pub(super) rename: Option<SpannedValue<String>>,
50
51 #[darling(default)]
52 pub(super) fallback: SpannedValue<bool>,
53
54 pub(super) fields: darling::ast::Fields<()>,
55}
56
57impl EnumDeriveInput {
58 pub fn graphql_type_name(&self) -> String {
59 self.graphql_type
60 .as_ref()
61 .map(|sp| sp.to_string())
62 .unwrap_or_else(|| self.ident.to_string())
63 }
64
65 pub fn graphql_type_span(&self) -> Span {
66 self.graphql_type
67 .as_ref()
68 .map(|val| val.span())
69 .unwrap_or_else(|| self.ident.span())
70 }
71
72 pub fn schema_input(&self) -> Result<SchemaInput, syn::Error> {
73 match (&self.schema, &self.schema_path) {
74 (None, None) => SchemaInput::default().map_err(|e| e.into_syn_error(Span::call_site())),
75 (None, Some(path)) => SchemaInput::from_schema_path(path.as_ref())
76 .map_err(|e| e.into_syn_error(path.span())),
77 (Some(name), None) => SchemaInput::from_schema_name(name.as_ref())
78 .map_err(|e| e.into_syn_error(name.span())),
79 (Some(_), Some(path)) => Err(syn::Error::new(
80 path.span(),
81 "Only one of schema_path & schema can be provided",
82 )),
83 }
84 }
85
86 pub(super) fn validate(&self) -> Result<(), Errors> {
87 let data_ref = self.data.as_ref().take_enum().unwrap();
88 let fallbacks = data_ref.iter().filter(|v| *v.fallback).collect::<Vec<_>>();
89 let mut errors = Errors::default();
90
91 if fallbacks.len() > 1 {
92 errors.extend(
93 fallbacks
94 .into_iter()
95 .map(|f| {
96 syn::Error::new(
97 f.span(),
98 "Enums only support a single fallback, but this enum has many",
99 )
100 })
101 .collect::<Vec<_>>(),
102 );
103 }
104
105 errors.extend(data_ref.iter().filter_map(|v| v.validate(v.span()).err()));
106
107 errors.into_result(())
108 }
109}
110
111impl EnumDeriveVariant {
112 pub(super) fn graphql_ident(&self, rename_rule: RenameAll) -> RenamableFieldIdent {
113 let mut ident = RenamableFieldIdent::from(self.ident.clone());
114 match &self.rename {
115 Some(rename) => {
116 let span = rename.span();
117 let rename = (**rename).clone();
118 ident.set_rename(rename, span)
119 }
120 None => {
121 ident.rename_with(rename_rule, self.ident.span());
122 }
123 }
124 ident
125 }
126
127 fn validate(&self, span: proc_macro2::Span) -> Result<(), Errors> {
128 use darling::ast::Style::*;
129
130 if *self.fallback {
131 match (self.fields.style, self.fields.len()) {
132 (Unit, _) => Ok(()),
133 (Struct, _) => Err(syn::Error::new(
134 span,
135 "Enum derive doesn't support struct variants as a fallback",
136 )
137 .into()),
138 (Tuple, 1) => Ok(()),
139 (Tuple, _) => Err(syn::Error::new(
140 span,
141 "Enum derive fallbacks can only have a single field",
142 )
143 .into()),
144 }
145 } else {
147 match self.fields.style {
148 Unit => Ok(()),
149 _ => Err(syn::Error::new(span, "GraphQL Enums can't have fields").into()),
150 }
151 }
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use darling::FromDeriveInput;
158 use syn::parse_quote;
159
160 use super::*;
161
162 #[test]
163 fn test_enum_with_no_fallbacks() {
164 let input = EnumDeriveInput::from_derive_input(&parse_quote! {
165 enum TestEnum {
166 Foo,
167 Bar
168 }
169 })
170 .unwrap();
171
172 input.validate().expect("no errors");
173 }
174
175 #[test]
176 fn test_enum_with_empty_fallback() {
177 let input = EnumDeriveInput::from_derive_input(&parse_quote! {
178 enum TestEnum {
179 Foo,
180 #[cynic(fallback)]
181 Bar
182 }
183 })
184 .unwrap();
185
186 input.validate().expect("no errors");
187 }
188
189 #[test]
190 fn test_enum_with_string_fallback() {
191 let input = EnumDeriveInput::from_derive_input(&parse_quote! {
192 enum TestEnum {
193 Foo,
194 #[cynic(fallback)]
195 Bar(String)
196 }
197 })
198 .unwrap();
199
200 input.validate().expect("no errors");
201 }
202}