utoipa_gen/component/
serde.rs

1//! Provides serde related features parsing serde attributes from types.
2
3use std::str::FromStr;
4
5use proc_macro2::{Ident, Span, TokenTree};
6use proc_macro_error::abort;
7use syn::{buffer::Cursor, Attribute, Error};
8
9use crate::ResultExt;
10
11#[inline]
12fn parse_next_lit_str(next: Cursor) -> Option<(String, Span)> {
13    match next.token_tree() {
14        Some((tt, next)) => match tt {
15            TokenTree::Punct(punct) if punct.as_char() == '=' => parse_next_lit_str(next),
16            TokenTree::Literal(literal) => {
17                Some((literal.to_string().replace('\"', ""), literal.span()))
18            }
19            _ => None,
20        },
21        _ => None,
22    }
23}
24
25#[derive(Default)]
26#[cfg_attr(feature = "debug", derive(Debug))]
27pub struct SerdeValue {
28    pub skip: bool,
29    pub rename: Option<String>,
30    pub default: bool,
31    pub flatten: bool,
32    pub skip_serializing_if: bool,
33    pub double_option: bool,
34}
35
36impl SerdeValue {
37    const SERDE_WITH_DOUBLE_OPTION: &'static str = "::serde_with::rust::double_option";
38}
39
40impl SerdeValue {
41    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
42        let mut value = Self::default();
43
44        input.step(|cursor| {
45            let mut rest = *cursor;
46            while let Some((tt, next)) = rest.token_tree() {
47                match tt {
48                    TokenTree::Ident(ident)
49                        if ident == "skip"
50                            || ident == "skip_serializing"
51                            || ident == "skip_deserializing" =>
52                    {
53                        value.skip = true
54                    }
55                    TokenTree::Ident(ident) if ident == "skip_serializing_if" => {
56                        value.skip_serializing_if = true
57                    }
58                    TokenTree::Ident(ident) if ident == "with" => {
59                        value.double_option = parse_next_lit_str(next)
60                            .and_then(|(literal, _)| {
61                                if literal == SerdeValue::SERDE_WITH_DOUBLE_OPTION {
62                                    Some(true)
63                                } else {
64                                    None
65                                }
66                            })
67                            .unwrap_or(false);
68                    }
69                    TokenTree::Ident(ident) if ident == "flatten" => value.flatten = true,
70                    TokenTree::Ident(ident) if ident == "rename" => {
71                        if let Some((literal, _)) = parse_next_lit_str(next) {
72                            value.rename = Some(literal)
73                        };
74                    }
75                    TokenTree::Ident(ident) if ident == "default" => value.default = true,
76                    _ => (),
77                }
78
79                rest = next;
80            }
81            Ok(((), rest))
82        })?;
83
84        Ok(value)
85    }
86}
87
88/// The [Serde Enum representation](https://serde.rs/enum-representations.html) being used
89/// The default case (when no serde attributes are present) is `ExternallyTagged`.
90#[derive(Clone, Debug)]
91pub enum SerdeEnumRepr {
92    ExternallyTagged,
93    InternallyTagged {
94        tag: String,
95    },
96    AdjacentlyTagged {
97        tag: String,
98        content: String,
99    },
100    Untagged,
101    /// This is a variant that can never happen because `serde` will not accept it.
102    /// With the current implementation it is necessary to have it as an intermediate state when parsing the
103    /// attributes
104    UnfinishedAdjacentlyTagged {
105        content: String,
106    },
107}
108
109impl Default for SerdeEnumRepr {
110    fn default() -> SerdeEnumRepr {
111        SerdeEnumRepr::ExternallyTagged
112    }
113}
114
115/// Attributes defined within a `#[serde(...)]` container attribute.
116#[derive(Default)]
117#[cfg_attr(feature = "debug", derive(Debug))]
118pub struct SerdeContainer {
119    pub rename_all: Option<RenameRule>,
120    pub enum_repr: SerdeEnumRepr,
121    pub default: bool,
122}
123
124impl SerdeContainer {
125    /// Parse a single serde attribute, currently supported attributes are:
126    ///     * `rename_all = ...`
127    ///     * `tag = ...`
128    ///     * `content = ...`
129    ///     * `untagged = ...`
130    ///     * `default = ...`
131    fn parse_attribute(&mut self, ident: Ident, next: Cursor) -> syn::Result<()> {
132        match ident.to_string().as_str() {
133            "rename_all" => {
134                if let Some((literal, span)) = parse_next_lit_str(next) {
135                    self.rename_all = Some(
136                        literal
137                            .parse::<RenameRule>()
138                            .map_err(|error| Error::new(span, error.to_string()))?,
139                    );
140                }
141            }
142            "tag" => {
143                if let Some((literal, span)) = parse_next_lit_str(next) {
144                    self.enum_repr = match &self.enum_repr {
145                        SerdeEnumRepr::ExternallyTagged => {
146                            SerdeEnumRepr::InternallyTagged { tag: literal }
147                        }
148                        SerdeEnumRepr::UnfinishedAdjacentlyTagged { content } => {
149                            SerdeEnumRepr::AdjacentlyTagged {
150                                tag: literal,
151                                content: content.clone(),
152                            }
153                        }
154                        SerdeEnumRepr::InternallyTagged { .. }
155                        | SerdeEnumRepr::AdjacentlyTagged { .. } => {
156                            abort!(span, "Duplicate serde tag argument")
157                        }
158                        SerdeEnumRepr::Untagged => abort!(span, "Untagged enum cannot have tag"),
159                    };
160                }
161            }
162            "content" => {
163                if let Some((literal, span)) = parse_next_lit_str(next) {
164                    self.enum_repr = match &self.enum_repr {
165                        SerdeEnumRepr::InternallyTagged { tag } => {
166                            SerdeEnumRepr::AdjacentlyTagged {
167                                tag: tag.clone(),
168                                content: literal,
169                            }
170                        }
171                        SerdeEnumRepr::ExternallyTagged => {
172                            SerdeEnumRepr::UnfinishedAdjacentlyTagged { content: literal }
173                        }
174                        SerdeEnumRepr::AdjacentlyTagged { .. }
175                        | SerdeEnumRepr::UnfinishedAdjacentlyTagged { .. } => {
176                            abort!(span, "Duplicate serde content argument")
177                        }
178                        SerdeEnumRepr::Untagged => {
179                            abort!(span, "Untagged enum cannot have content")
180                        }
181                    };
182                }
183            }
184            "untagged" => {
185                self.enum_repr = SerdeEnumRepr::Untagged;
186            }
187            "default" => {
188                self.default = true;
189            }
190            _ => {}
191        }
192        Ok(())
193    }
194
195    /// Parse the attributes inside a `#[serde(...)]` container attribute.
196    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
197        let mut container = Self::default();
198
199        input.step(|cursor| {
200            let mut rest = *cursor;
201            while let Some((tt, next)) = rest.token_tree() {
202                if let TokenTree::Ident(ident) = tt {
203                    container.parse_attribute(ident, next)?
204                }
205
206                rest = next;
207            }
208            Ok(((), rest))
209        })?;
210
211        Ok(container)
212    }
213}
214
215pub fn parse_value(attributes: &[Attribute]) -> Option<SerdeValue> {
216    attributes
217        .iter()
218        .filter(|attribute| attribute.path().is_ident("serde"))
219        .map(|serde_attribute| {
220            serde_attribute
221                .parse_args_with(SerdeValue::parse)
222                .unwrap_or_abort()
223        })
224        .fold(Some(SerdeValue::default()), |acc, value| {
225            acc.map(|mut acc| {
226                if value.skip {
227                    acc.skip = value.skip;
228                }
229                if value.skip_serializing_if {
230                    acc.skip_serializing_if = value.skip_serializing_if;
231                }
232                if value.rename.is_some() {
233                    acc.rename = value.rename;
234                }
235                if value.flatten {
236                    acc.flatten = value.flatten;
237                }
238                if value.default {
239                    acc.default = value.default;
240                }
241                if value.double_option {
242                    acc.double_option = value.double_option;
243                }
244
245                acc
246            })
247        })
248}
249
250pub fn parse_container(attributes: &[Attribute]) -> Option<SerdeContainer> {
251    attributes
252        .iter()
253        .filter(|attribute| attribute.path().is_ident("serde"))
254        .map(|serde_attribute| {
255            serde_attribute
256                .parse_args_with(SerdeContainer::parse)
257                .unwrap_or_abort()
258        })
259        .fold(Some(SerdeContainer::default()), |acc, value| {
260            acc.map(|mut acc| {
261                if value.default {
262                    acc.default = value.default;
263                }
264                match value.enum_repr {
265                    SerdeEnumRepr::ExternallyTagged => {}
266                    SerdeEnumRepr::Untagged
267                    | SerdeEnumRepr::InternallyTagged { .. }
268                    | SerdeEnumRepr::AdjacentlyTagged { .. }
269                    | SerdeEnumRepr::UnfinishedAdjacentlyTagged { .. } => {
270                        acc.enum_repr = value.enum_repr;
271                    }
272                }
273                if value.rename_all.is_some() {
274                    acc.rename_all = value.rename_all;
275                }
276
277                acc
278            })
279        })
280}
281
282#[derive(Clone)]
283#[cfg_attr(feature = "debug", derive(Debug))]
284pub enum RenameRule {
285    Lower,
286    Upper,
287    Camel,
288    Snake,
289    ScreamingSnake,
290    Pascal,
291    Kebab,
292    ScreamingKebab,
293}
294
295impl RenameRule {
296    pub fn rename(&self, value: &str) -> String {
297        match self {
298            RenameRule::Lower => value.to_ascii_lowercase(),
299            RenameRule::Upper => value.to_ascii_uppercase(),
300            RenameRule::Camel => {
301                let mut camel_case = String::new();
302
303                let mut upper = false;
304                for letter in value.chars() {
305                    if letter == '_' {
306                        upper = true;
307                        continue;
308                    }
309
310                    if upper {
311                        camel_case.push(letter.to_ascii_uppercase());
312                        upper = false;
313                    } else {
314                        camel_case.push(letter)
315                    }
316                }
317
318                camel_case
319            }
320            RenameRule::Snake => value.to_string(),
321            RenameRule::ScreamingSnake => Self::Snake.rename(value).to_ascii_uppercase(),
322            RenameRule::Pascal => {
323                let mut pascal_case = String::from(&value[..1].to_ascii_uppercase());
324                pascal_case.push_str(&Self::Camel.rename(&value[1..]));
325
326                pascal_case
327            }
328            RenameRule::Kebab => Self::Snake.rename(value).replace('_', "-"),
329            RenameRule::ScreamingKebab => Self::Kebab.rename(value).to_ascii_uppercase(),
330        }
331    }
332
333    pub fn rename_variant(&self, variant: &str) -> String {
334        match self {
335            RenameRule::Lower => variant.to_ascii_lowercase(),
336            RenameRule::Upper => variant.to_ascii_uppercase(),
337            RenameRule::Camel => {
338                let mut snake_case = String::from(&variant[..1].to_ascii_lowercase());
339                snake_case.push_str(&variant[1..]);
340
341                snake_case
342            }
343            RenameRule::Snake => {
344                let mut snake_case = String::new();
345
346                for (index, letter) in variant.char_indices() {
347                    if index > 0 && letter.is_uppercase() {
348                        snake_case.push('_');
349                    }
350                    snake_case.push(letter);
351                }
352
353                snake_case.to_ascii_lowercase()
354            }
355            RenameRule::ScreamingSnake => Self::Snake.rename_variant(variant).to_ascii_uppercase(),
356            RenameRule::Pascal => variant.to_string(),
357            RenameRule::Kebab => Self::Snake.rename_variant(variant).replace('_', "-"),
358            RenameRule::ScreamingKebab => Self::Kebab.rename_variant(variant).to_ascii_uppercase(),
359        }
360    }
361}
362
363const RENAME_RULE_NAME_MAPPING: [(&str, RenameRule); 8] = [
364    ("lowercase", RenameRule::Lower),
365    ("UPPERCASE", RenameRule::Upper),
366    ("PascalCase", RenameRule::Pascal),
367    ("camelCase", RenameRule::Camel),
368    ("snake_case", RenameRule::Snake),
369    ("SCREAMING_SNAKE_CASE", RenameRule::ScreamingSnake),
370    ("kebab-case", RenameRule::Kebab),
371    ("SCREAMING-KEBAB-CASE", RenameRule::ScreamingKebab),
372];
373
374impl FromStr for RenameRule {
375    type Err = Error;
376
377    fn from_str(s: &str) -> Result<Self, Self::Err> {
378        let expected_one_of = RENAME_RULE_NAME_MAPPING
379            .into_iter()
380            .map(|(name, _)| format!(r#""{name}""#))
381            .collect::<Vec<_>>()
382            .join(", ");
383        RENAME_RULE_NAME_MAPPING
384            .into_iter()
385            .find_map(|(case, rule)| if case == s { Some(rule) } else { None })
386            .ok_or_else(|| {
387                Error::new(
388                    Span::call_site(),
389                    format!(r#"unexpected rename rule, expected one of: {expected_one_of}"#),
390                )
391            })
392    }
393}
394
395#[cfg(test)]
396mod tests {
397    use super::{RenameRule, RENAME_RULE_NAME_MAPPING};
398
399    macro_rules! test_rename_rule {
400        ( $($case:expr=> $value:literal = $expected:literal)* ) => {
401            #[test]
402            fn rename_all_rename_rules() {
403                $(
404                    let value = $case.rename($value);
405                    assert_eq!(value, $expected, "expected case: {} => {} != {}", stringify!($case), $value, $expected);
406                )*
407            }
408        };
409    }
410
411    macro_rules! test_rename_variant_rule {
412        ( $($case:expr=> $value:literal = $expected:literal)* ) => {
413            #[test]
414            fn rename_all_rename_variant_rules() {
415                $(
416                    let value = $case.rename_variant($value);
417                    assert_eq!(value, $expected, "expected case: {} => {} != {}", stringify!($case), $value, $expected);
418                )*
419            }
420        };
421    }
422
423    test_rename_rule! {
424        RenameRule::Lower=> "single" = "single"
425        RenameRule::Upper=> "single" = "SINGLE"
426        RenameRule::Pascal=> "single" = "Single"
427        RenameRule::Camel=> "single" = "single"
428        RenameRule::Snake=> "single" = "single"
429        RenameRule::ScreamingSnake=> "single" = "SINGLE"
430        RenameRule::Kebab=> "single" = "single"
431        RenameRule::ScreamingKebab=> "single" = "SINGLE"
432
433        RenameRule::Lower=> "multi_value" = "multi_value"
434        RenameRule::Upper=> "multi_value" = "MULTI_VALUE"
435        RenameRule::Pascal=> "multi_value" = "MultiValue"
436        RenameRule::Camel=> "multi_value" = "multiValue"
437        RenameRule::Snake=> "multi_value" = "multi_value"
438        RenameRule::ScreamingSnake=> "multi_value" = "MULTI_VALUE"
439        RenameRule::Kebab=> "multi_value" = "multi-value"
440        RenameRule::ScreamingKebab=> "multi_value" = "MULTI-VALUE"
441    }
442
443    test_rename_variant_rule! {
444        RenameRule::Lower=> "Single" = "single"
445        RenameRule::Upper=> "Single" = "SINGLE"
446        RenameRule::Pascal=> "Single" = "Single"
447        RenameRule::Camel=> "Single" = "single"
448        RenameRule::Snake=> "Single" = "single"
449        RenameRule::ScreamingSnake=> "Single" = "SINGLE"
450        RenameRule::Kebab=> "Single" = "single"
451        RenameRule::ScreamingKebab=> "Single" = "SINGLE"
452
453        RenameRule::Lower=> "MultiValue" = "multivalue"
454        RenameRule::Upper=> "MultiValue" = "MULTIVALUE"
455        RenameRule::Pascal=> "MultiValue" = "MultiValue"
456        RenameRule::Camel=> "MultiValue" = "multiValue"
457        RenameRule::Snake=> "MultiValue" = "multi_value"
458        RenameRule::ScreamingSnake=> "MultiValue" = "MULTI_VALUE"
459        RenameRule::Kebab=> "MultiValue" = "multi-value"
460        RenameRule::ScreamingKebab=> "MultiValue" = "MULTI-VALUE"
461    }
462
463    #[test]
464    fn test_serde_rename_rule_from_str() {
465        for (s, _) in RENAME_RULE_NAME_MAPPING {
466            s.parse::<RenameRule>().unwrap();
467        }
468    }
469}