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 syn::{buffer::Cursor, Attribute, Error};
7
8use crate::Diagnostics;
9
10#[inline]
11fn parse_next_lit_str(next: Cursor) -> Option<(String, Span)> {
12    match next.token_tree() {
13        Some((tt, next)) => match tt {
14            TokenTree::Punct(punct) if punct.as_char() == '=' => parse_next_lit_str(next),
15            TokenTree::Literal(literal) => {
16                Some((literal.to_string().replace('\"', ""), literal.span()))
17            }
18            _ => None,
19        },
20        _ => None,
21    }
22}
23
24#[derive(Default)]
25#[cfg_attr(feature = "debug", derive(Debug))]
26#[cfg_attr(test, derive(PartialEq, Eq))]
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, Default)]
91#[cfg_attr(feature = "debug", derive(Debug))]
92#[cfg_attr(test, derive(PartialEq, Eq))]
93pub enum SerdeEnumRepr {
94    #[default]
95    ExternallyTagged,
96    InternallyTagged {
97        tag: String,
98    },
99    AdjacentlyTagged {
100        tag: String,
101        content: String,
102    },
103    Untagged,
104    /// This is a variant that can never happen because `serde` will not accept it.
105    /// With the current implementation it is necessary to have it as an intermediate state when parsing the
106    /// attributes
107    UnfinishedAdjacentlyTagged {
108        content: String,
109    },
110}
111
112/// Attributes defined within a `#[serde(...)]` container attribute.
113#[derive(Default)]
114#[cfg_attr(feature = "debug", derive(Debug))]
115#[cfg_attr(test, derive(PartialEq, Eq))]
116pub struct SerdeContainer {
117    pub rename_all: Option<RenameRule>,
118    pub enum_repr: SerdeEnumRepr,
119    pub default: bool,
120    pub deny_unknown_fields: bool,
121}
122
123impl SerdeContainer {
124    /// Parse a single serde attribute, currently supported attributes are:
125    ///     * `rename_all = ...`
126    ///     * `tag = ...`
127    ///     * `content = ...`
128    ///     * `untagged = ...`
129    ///     * `default = ...`
130    ///     * `deny_unknown_fields`
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                            return Err(syn::Error::new(span, "Duplicate serde tag argument"));
157                        }
158                        SerdeEnumRepr::Untagged => {
159                            return Err(syn::Error::new(span, "Untagged enum cannot have tag"))
160                        }
161                    };
162                }
163            }
164            "content" => {
165                if let Some((literal, span)) = parse_next_lit_str(next) {
166                    self.enum_repr = match &self.enum_repr {
167                        SerdeEnumRepr::InternallyTagged { tag } => {
168                            SerdeEnumRepr::AdjacentlyTagged {
169                                tag: tag.clone(),
170                                content: literal,
171                            }
172                        }
173                        SerdeEnumRepr::ExternallyTagged => {
174                            SerdeEnumRepr::UnfinishedAdjacentlyTagged { content: literal }
175                        }
176                        SerdeEnumRepr::AdjacentlyTagged { .. }
177                        | SerdeEnumRepr::UnfinishedAdjacentlyTagged { .. } => {
178                            return Err(syn::Error::new(span, "Duplicate serde content argument"))
179                        }
180                        SerdeEnumRepr::Untagged => {
181                            return Err(syn::Error::new(span, "Untagged enum cannot have content"))
182                        }
183                    };
184                }
185            }
186            "untagged" => {
187                self.enum_repr = SerdeEnumRepr::Untagged;
188            }
189            "default" => {
190                self.default = true;
191            }
192            "deny_unknown_fields" => {
193                self.deny_unknown_fields = true;
194            }
195            _ => {}
196        }
197        Ok(())
198    }
199
200    /// Parse the attributes inside a `#[serde(...)]` container attribute.
201    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
202        let mut container = Self::default();
203
204        input.step(|cursor| {
205            let mut rest = *cursor;
206            while let Some((tt, next)) = rest.token_tree() {
207                if let TokenTree::Ident(ident) = tt {
208                    container.parse_attribute(ident, next)?
209                }
210
211                rest = next;
212            }
213            Ok(((), rest))
214        })?;
215
216        Ok(container)
217    }
218}
219
220pub fn parse_value(attributes: &[Attribute]) -> Result<SerdeValue, Diagnostics> {
221    Ok(attributes
222        .iter()
223        .filter(|attribute| attribute.path().is_ident("serde"))
224        .map(|serde_attribute| {
225            serde_attribute
226                .parse_args_with(SerdeValue::parse)
227                .map_err(Diagnostics::from)
228        })
229        .collect::<Result<Vec<_>, Diagnostics>>()?
230        .into_iter()
231        .fold(SerdeValue::default(), |mut acc, value| {
232            if value.skip {
233                acc.skip = value.skip;
234            }
235            if value.skip_serializing_if {
236                acc.skip_serializing_if = value.skip_serializing_if;
237            }
238            if value.rename.is_some() {
239                acc.rename = value.rename;
240            }
241            if value.flatten {
242                acc.flatten = value.flatten;
243            }
244            if value.default {
245                acc.default = value.default;
246            }
247            if value.double_option {
248                acc.double_option = value.double_option;
249            }
250
251            acc
252        }))
253}
254
255pub fn parse_container(attributes: &[Attribute]) -> Result<SerdeContainer, Diagnostics> {
256    Ok(attributes
257        .iter()
258        .filter(|attribute| attribute.path().is_ident("serde"))
259        .map(|serde_attribute| {
260            serde_attribute
261                .parse_args_with(SerdeContainer::parse)
262                .map_err(Diagnostics::from)
263        })
264        .collect::<Result<Vec<_>, Diagnostics>>()?
265        .into_iter()
266        .fold(SerdeContainer::default(), |mut acc, value| {
267            if value.default {
268                acc.default = value.default;
269            }
270            if value.deny_unknown_fields {
271                acc.deny_unknown_fields = value.deny_unknown_fields;
272            }
273            match value.enum_repr {
274                SerdeEnumRepr::ExternallyTagged => {}
275                SerdeEnumRepr::Untagged
276                | SerdeEnumRepr::InternallyTagged { .. }
277                | SerdeEnumRepr::AdjacentlyTagged { .. }
278                | SerdeEnumRepr::UnfinishedAdjacentlyTagged { .. } => {
279                    acc.enum_repr = value.enum_repr;
280                }
281            }
282            if value.rename_all.is_some() {
283                acc.rename_all = value.rename_all;
284            }
285
286            acc
287        }))
288}
289
290#[derive(Clone)]
291#[cfg_attr(feature = "debug", derive(Debug))]
292#[cfg_attr(test, derive(PartialEq, Eq))]
293pub enum RenameRule {
294    Lower,
295    Upper,
296    Camel,
297    Snake,
298    ScreamingSnake,
299    Pascal,
300    Kebab,
301    ScreamingKebab,
302}
303
304impl RenameRule {
305    pub fn rename(&self, value: &str) -> String {
306        match self {
307            RenameRule::Lower => value.to_ascii_lowercase(),
308            RenameRule::Upper => value.to_ascii_uppercase(),
309            RenameRule::Camel => {
310                let mut camel_case = String::new();
311
312                let mut upper = false;
313                for letter in value.chars() {
314                    if letter == '_' {
315                        upper = true;
316                        continue;
317                    }
318
319                    if upper {
320                        camel_case.push(letter.to_ascii_uppercase());
321                        upper = false;
322                    } else {
323                        camel_case.push(letter)
324                    }
325                }
326
327                camel_case
328            }
329            RenameRule::Snake => value.to_string(),
330            RenameRule::ScreamingSnake => Self::Snake.rename(value).to_ascii_uppercase(),
331            RenameRule::Pascal => {
332                let mut pascal_case = String::from(&value[..1].to_ascii_uppercase());
333                pascal_case.push_str(&Self::Camel.rename(&value[1..]));
334
335                pascal_case
336            }
337            RenameRule::Kebab => Self::Snake.rename(value).replace('_', "-"),
338            RenameRule::ScreamingKebab => Self::Kebab.rename(value).to_ascii_uppercase(),
339        }
340    }
341
342    pub fn rename_variant(&self, variant: &str) -> String {
343        match self {
344            RenameRule::Lower => variant.to_ascii_lowercase(),
345            RenameRule::Upper => variant.to_ascii_uppercase(),
346            RenameRule::Camel => {
347                let mut snake_case = String::from(&variant[..1].to_ascii_lowercase());
348                snake_case.push_str(&variant[1..]);
349
350                snake_case
351            }
352            RenameRule::Snake => {
353                let mut snake_case = String::new();
354
355                for (index, letter) in variant.char_indices() {
356                    if index > 0 && letter.is_uppercase() {
357                        snake_case.push('_');
358                    }
359                    snake_case.push(letter);
360                }
361
362                snake_case.to_ascii_lowercase()
363            }
364            RenameRule::ScreamingSnake => Self::Snake.rename_variant(variant).to_ascii_uppercase(),
365            RenameRule::Pascal => variant.to_string(),
366            RenameRule::Kebab => Self::Snake.rename_variant(variant).replace('_', "-"),
367            RenameRule::ScreamingKebab => Self::Kebab.rename_variant(variant).to_ascii_uppercase(),
368        }
369    }
370}
371
372const RENAME_RULE_NAME_MAPPING: [(&str, RenameRule); 8] = [
373    ("lowercase", RenameRule::Lower),
374    ("UPPERCASE", RenameRule::Upper),
375    ("PascalCase", RenameRule::Pascal),
376    ("camelCase", RenameRule::Camel),
377    ("snake_case", RenameRule::Snake),
378    ("SCREAMING_SNAKE_CASE", RenameRule::ScreamingSnake),
379    ("kebab-case", RenameRule::Kebab),
380    ("SCREAMING-KEBAB-CASE", RenameRule::ScreamingKebab),
381];
382
383impl FromStr for RenameRule {
384    type Err = Error;
385
386    fn from_str(s: &str) -> Result<Self, Self::Err> {
387        let expected_one_of = RENAME_RULE_NAME_MAPPING
388            .into_iter()
389            .map(|(name, _)| format!(r#""{name}""#))
390            .collect::<Vec<_>>()
391            .join(", ");
392        RENAME_RULE_NAME_MAPPING
393            .into_iter()
394            .find_map(|(case, rule)| if case == s { Some(rule) } else { None })
395            .ok_or_else(|| {
396                Error::new(
397                    Span::call_site(),
398                    format!(r#"unexpected rename rule, expected one of: {expected_one_of}"#),
399                )
400            })
401    }
402}
403
404#[cfg(test)]
405mod tests {
406    use super::{parse_container, RenameRule, SerdeContainer, RENAME_RULE_NAME_MAPPING};
407    use syn::{parse_quote, Attribute};
408
409    macro_rules! test_rename_rule {
410        ( $($case:expr=> $value:literal = $expected:literal)* ) => {
411            #[test]
412            fn rename_all_rename_rules() {
413                $(
414                    let value = $case.rename($value);
415                    assert_eq!(value, $expected, "expected case: {} => {} != {}", stringify!($case), $value, $expected);
416                )*
417            }
418        };
419    }
420
421    macro_rules! test_rename_variant_rule {
422        ( $($case:expr=> $value:literal = $expected:literal)* ) => {
423            #[test]
424            fn rename_all_rename_variant_rules() {
425                $(
426                    let value = $case.rename_variant($value);
427                    assert_eq!(value, $expected, "expected case: {} => {} != {}", stringify!($case), $value, $expected);
428                )*
429            }
430        };
431    }
432
433    test_rename_rule! {
434        RenameRule::Lower=> "single" = "single"
435        RenameRule::Upper=> "single" = "SINGLE"
436        RenameRule::Pascal=> "single" = "Single"
437        RenameRule::Camel=> "single" = "single"
438        RenameRule::Snake=> "single" = "single"
439        RenameRule::ScreamingSnake=> "single" = "SINGLE"
440        RenameRule::Kebab=> "single" = "single"
441        RenameRule::ScreamingKebab=> "single" = "SINGLE"
442
443        RenameRule::Lower=> "multi_value" = "multi_value"
444        RenameRule::Upper=> "multi_value" = "MULTI_VALUE"
445        RenameRule::Pascal=> "multi_value" = "MultiValue"
446        RenameRule::Camel=> "multi_value" = "multiValue"
447        RenameRule::Snake=> "multi_value" = "multi_value"
448        RenameRule::ScreamingSnake=> "multi_value" = "MULTI_VALUE"
449        RenameRule::Kebab=> "multi_value" = "multi-value"
450        RenameRule::ScreamingKebab=> "multi_value" = "MULTI-VALUE"
451    }
452
453    test_rename_variant_rule! {
454        RenameRule::Lower=> "Single" = "single"
455        RenameRule::Upper=> "Single" = "SINGLE"
456        RenameRule::Pascal=> "Single" = "Single"
457        RenameRule::Camel=> "Single" = "single"
458        RenameRule::Snake=> "Single" = "single"
459        RenameRule::ScreamingSnake=> "Single" = "SINGLE"
460        RenameRule::Kebab=> "Single" = "single"
461        RenameRule::ScreamingKebab=> "Single" = "SINGLE"
462
463        RenameRule::Lower=> "MultiValue" = "multivalue"
464        RenameRule::Upper=> "MultiValue" = "MULTIVALUE"
465        RenameRule::Pascal=> "MultiValue" = "MultiValue"
466        RenameRule::Camel=> "MultiValue" = "multiValue"
467        RenameRule::Snake=> "MultiValue" = "multi_value"
468        RenameRule::ScreamingSnake=> "MultiValue" = "MULTI_VALUE"
469        RenameRule::Kebab=> "MultiValue" = "multi-value"
470        RenameRule::ScreamingKebab=> "MultiValue" = "MULTI-VALUE"
471    }
472
473    #[test]
474    fn test_serde_rename_rule_from_str() {
475        for (s, _) in RENAME_RULE_NAME_MAPPING {
476            s.parse::<RenameRule>().unwrap();
477        }
478    }
479
480    #[test]
481    fn test_serde_parse_container() {
482        let default_attribute_1: syn::Attribute = parse_quote! {
483            #[serde(default)]
484        };
485        let default_attribute_2: syn::Attribute = parse_quote! {
486            #[serde(default)]
487        };
488        let deny_unknown_fields_attribute: syn::Attribute = parse_quote! {
489            #[serde(deny_unknown_fields)]
490        };
491        let unsupported_attribute: syn::Attribute = parse_quote! {
492            #[serde(expecting = "...")]
493        };
494        let attributes: &[Attribute] = &[
495            default_attribute_1,
496            default_attribute_2,
497            deny_unknown_fields_attribute,
498            unsupported_attribute,
499        ];
500
501        let expected = SerdeContainer {
502            default: true,
503            deny_unknown_fields: true,
504            ..Default::default()
505        };
506
507        let result = parse_container(attributes).expect("parse success");
508        assert_eq!(expected, result);
509    }
510}