torn_api_codegen/model/
enum.rs1use heck::ToUpperCamelCase;
2use proc_macro2::TokenStream;
3use quote::{format_ident, quote};
4
5use crate::openapi::{
6 parameter::OpenApiParameterSchema,
7 r#type::{OpenApiType, OpenApiVariants},
8};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum EnumRepr {
12 U8,
13 U32,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum EnumVariantTupleValue {
18 Ref(String),
19}
20
21impl EnumVariantTupleValue {
22 pub fn from_schema(schema: &OpenApiType) -> Option<Self> {
23 if let OpenApiType {
24 ref_path: Some(path),
25 ..
26 } = schema
27 {
28 Some(Self::Ref((*path).to_owned()))
29 } else {
30 None
31 }
32 }
33
34 pub fn name(&self) -> Option<&str> {
35 let Self::Ref(path) = self;
36
37 path.strip_prefix("#/components/schemas/")
38 }
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub enum EnumVariantValue {
43 Repr(u32),
44 String { rename: Option<String> },
45 Tuple(Vec<EnumVariantTupleValue>),
46}
47
48impl Default for EnumVariantValue {
49 fn default() -> Self {
50 Self::String { rename: None }
51 }
52}
53
54impl EnumVariantValue {
55 pub fn codegen_display(&self, name: &str) -> Option<TokenStream> {
56 match self {
57 Self::Repr(i) => Some(quote! { write!(f, "{}", #i) }),
58 Self::String { rename } => {
59 let name = rename.as_deref().unwrap_or(name);
60 Some(quote! { write!(f, #name) })
61 }
62 _ => None,
63 }
64 }
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Default)]
68pub struct EnumVariant {
69 pub name: String,
70 pub description: Option<String>,
71 pub value: EnumVariantValue,
72}
73
74impl EnumVariant {
75 pub fn codegen(&self) -> Option<TokenStream> {
76 let doc = self.description.as_ref().map(|d| {
77 quote! {
78 #[doc = #d]
79 }
80 });
81
82 let name = format_ident!("{}", self.name);
83
84 match &self.value {
85 EnumVariantValue::Repr(repr) => Some(quote! {
86 #doc
87 #name = #repr
88 }),
89 EnumVariantValue::String { rename } => {
90 let serde_attr = rename.as_ref().map(|r| {
91 quote! {
92 #[serde(rename = #r)]
93 }
94 });
95
96 Some(quote! {
97 #doc
98 #serde_attr
99 #name
100 })
101 }
102 EnumVariantValue::Tuple(values) => {
103 let mut val_tys = Vec::with_capacity(values.len());
104
105 for value in values {
106 let ty_name = value.name()?;
107 let ty_name = format_ident!("{ty_name}");
108
109 val_tys.push(quote! {
110 crate::models::#ty_name
111 });
112 }
113
114 Some(quote! {
115 #name(#(#val_tys),*)
116 })
117 }
118 }
119 }
120
121 pub fn codegen_display(&self) -> Option<TokenStream> {
122 let rhs = self.value.codegen_display(&self.name)?;
123 let name = format_ident!("{}", self.name);
124
125 Some(quote! {
126 Self::#name => #rhs
127 })
128 }
129}
130
131#[derive(Debug, Clone, PartialEq, Eq, Default)]
132pub struct Enum {
133 pub name: String,
134 pub description: Option<String>,
135 pub repr: Option<EnumRepr>,
136 pub copy: bool,
137 pub display: bool,
138 pub untagged: bool,
139 pub variants: Vec<EnumVariant>,
140}
141
142impl Enum {
143 pub fn from_schema(name: &str, schema: &OpenApiType) -> Option<Self> {
144 let mut result = Enum {
145 name: name.to_owned(),
146 description: schema.description.as_deref().map(ToOwned::to_owned),
147 copy: true,
148 ..Default::default()
149 };
150
151 match &schema.r#enum {
152 Some(OpenApiVariants::Int(int_variants)) => {
153 result.repr = Some(EnumRepr::U32);
154 result.display = true;
155 result.variants = int_variants
156 .iter()
157 .copied()
158 .map(|i| EnumVariant {
159 name: format!("Variant{i}"),
160 value: EnumVariantValue::Repr(i as u32),
161 ..Default::default()
162 })
163 .collect();
164 }
165 Some(OpenApiVariants::Str(str_variants)) => {
166 result.display = true;
167 result.variants = str_variants
168 .iter()
169 .copied()
170 .map(|s| {
171 let transformed = s.replace('&', "And").to_upper_camel_case();
172 EnumVariant {
173 value: EnumVariantValue::String {
174 rename: (transformed != s).then(|| s.to_owned()),
175 },
176 name: transformed,
177 ..Default::default()
178 }
179 })
180 .collect();
181 }
182 None => return None,
183 }
184
185 Some(result)
186 }
187
188 pub fn from_parameter_schema(name: &str, schema: &OpenApiParameterSchema) -> Option<Self> {
189 let mut result = Self {
190 name: name.to_owned(),
191 copy: true,
192 display: true,
193 ..Default::default()
194 };
195
196 for var in schema.r#enum.as_ref()? {
197 let transformed = var.to_upper_camel_case();
198 result.variants.push(EnumVariant {
199 value: EnumVariantValue::String {
200 rename: (transformed != *var).then(|| transformed.clone()),
201 },
202 name: transformed,
203 ..Default::default()
204 });
205 }
206
207 Some(result)
208 }
209
210 pub fn from_one_of(name: &str, schemas: &[OpenApiType]) -> Option<Self> {
211 let mut result = Self {
212 name: name.to_owned(),
213 untagged: true,
214 ..Default::default()
215 };
216
217 for schema in schemas {
218 let value = EnumVariantTupleValue::from_schema(schema)?;
219 let name = value.name()?.to_owned();
220
221 result.variants.push(EnumVariant {
222 name,
223 value: EnumVariantValue::Tuple(vec![value]),
224 ..Default::default()
225 });
226 }
227
228 Some(result)
229 }
230
231 pub fn codegen(&self) -> Option<TokenStream> {
232 let repr = self.repr.map(|r| match r {
233 EnumRepr::U8 => quote! { #[repr(u8)]},
234 EnumRepr::U32 => quote! { #[repr(u32)]},
235 });
236 let name = format_ident!("{}", self.name);
237 let desc = self.description.as_ref().map(|d| {
238 quote! {
239 #repr
240 #[doc = #d]
241 }
242 });
243
244 let mut display = Vec::with_capacity(self.variants.len());
245 let mut variants = Vec::with_capacity(self.variants.len());
246 for variant in &self.variants {
247 variants.push(variant.codegen()?);
248
249 if self.display {
250 display.push(variant.codegen_display()?);
251 }
252 }
253
254 let mut derives = vec![];
255
256 if self.copy {
257 derives.extend_from_slice(&["Copy", "Hash"]);
258 }
259
260 let derives = derives.into_iter().map(|d| format_ident!("{d}"));
261
262 let serde_attr = self.untagged.then(|| {
263 quote! {
264 #[serde(untagged)]
265 }
266 });
267
268 let display = self.display.then(|| {
269 quote! {
270 impl std::fmt::Display for #name {
271 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
272 match self {
273 #(#display),*
274 }
275 }
276 }
277 }
278 });
279
280 Some(quote! {
281 #desc
282 #[derive(Debug, Clone, PartialEq, serde::Deserialize, #(#derives),*)]
283 #serde_attr
284 pub enum #name {
285 #(#variants),*
286 }
287 #display
288 })
289 }
290}
291
292#[cfg(test)]
293mod test {
294 use crate::openapi::schema::OpenApiSchema;
295
296 use super::*;
297
298 #[test]
299 fn codegen() {
300 let schema = OpenApiSchema::read().unwrap();
301
302 let revive_setting = schema.components.schemas.get("ReviveSetting").unwrap();
303
304 let r#enum = Enum::from_schema("ReviveSetting", revive_setting).unwrap();
305
306 let code = r#enum.codegen().unwrap();
307
308 panic!("{code}");
309 }
310}