serde_derive/internals/
case.rs

1//! Code to convert the Rust-styled field/variant (e.g. `my_field`, `MyType`) to the
2//! case of the source (e.g. `my-field`, `MY_FIELD`).
3
4use self::RenameRule::*;
5use std::fmt::{self, Debug, Display};
6
7/// The different possible ways to change case of fields in a struct, or variants in an enum.
8#[derive(Copy, Clone, PartialEq)]
9pub enum RenameRule {
10    /// Don't apply a default rename rule.
11    None,
12    /// Rename direct children to "lowercase" style.
13    LowerCase,
14    /// Rename direct children to "UPPERCASE" style.
15    UpperCase,
16    /// Rename direct children to "PascalCase" style, as typically used for
17    /// enum variants.
18    PascalCase,
19    /// Rename direct children to "camelCase" style.
20    CamelCase,
21    /// Rename direct children to "snake_case" style, as commonly used for
22    /// fields.
23    SnakeCase,
24    /// Rename direct children to "SCREAMING_SNAKE_CASE" style, as commonly
25    /// used for constants.
26    ScreamingSnakeCase,
27    /// Rename direct children to "kebab-case" style.
28    KebabCase,
29    /// Rename direct children to "SCREAMING-KEBAB-CASE" style.
30    ScreamingKebabCase,
31}
32
33static RENAME_RULES: &[(&str, RenameRule)] = &[
34    ("lowercase", LowerCase),
35    ("UPPERCASE", UpperCase),
36    ("PascalCase", PascalCase),
37    ("camelCase", CamelCase),
38    ("snake_case", SnakeCase),
39    ("SCREAMING_SNAKE_CASE", ScreamingSnakeCase),
40    ("kebab-case", KebabCase),
41    ("SCREAMING-KEBAB-CASE", ScreamingKebabCase),
42];
43
44impl RenameRule {
45    pub fn from_str(rename_all_str: &str) -> Result<Self, ParseError> {
46        for (name, rule) in RENAME_RULES {
47            if rename_all_str == *name {
48                return Ok(*rule);
49            }
50        }
51        Err(ParseError {
52            unknown: rename_all_str,
53        })
54    }
55
56    /// Apply a renaming rule to an enum variant, returning the version expected in the source.
57    pub fn apply_to_variant(self, variant: &str) -> String {
58        match self {
59            None | PascalCase => variant.to_owned(),
60            LowerCase => variant.to_ascii_lowercase(),
61            UpperCase => variant.to_ascii_uppercase(),
62            CamelCase => variant[..1].to_ascii_lowercase() + &variant[1..],
63            SnakeCase => {
64                let mut snake = String::new();
65                for (i, ch) in variant.char_indices() {
66                    if i > 0 && ch.is_uppercase() {
67                        snake.push('_');
68                    }
69                    snake.push(ch.to_ascii_lowercase());
70                }
71                snake
72            }
73            ScreamingSnakeCase => SnakeCase.apply_to_variant(variant).to_ascii_uppercase(),
74            KebabCase => SnakeCase.apply_to_variant(variant).replace('_', "-"),
75            ScreamingKebabCase => ScreamingSnakeCase
76                .apply_to_variant(variant)
77                .replace('_', "-"),
78        }
79    }
80
81    /// Apply a renaming rule to a struct field, returning the version expected in the source.
82    pub fn apply_to_field(self, field: &str) -> String {
83        match self {
84            None | LowerCase | SnakeCase => field.to_owned(),
85            UpperCase => field.to_ascii_uppercase(),
86            PascalCase => {
87                let mut pascal = String::new();
88                let mut capitalize = true;
89                for ch in field.chars() {
90                    if ch == '_' {
91                        capitalize = true;
92                    } else if capitalize {
93                        pascal.push(ch.to_ascii_uppercase());
94                        capitalize = false;
95                    } else {
96                        pascal.push(ch);
97                    }
98                }
99                pascal
100            }
101            CamelCase => {
102                let pascal = PascalCase.apply_to_field(field);
103                pascal[..1].to_ascii_lowercase() + &pascal[1..]
104            }
105            ScreamingSnakeCase => field.to_ascii_uppercase(),
106            KebabCase => field.replace('_', "-"),
107            ScreamingKebabCase => ScreamingSnakeCase.apply_to_field(field).replace('_', "-"),
108        }
109    }
110
111    /// Returns the `RenameRule` if it is not `None`, `rule_b` otherwise.
112    pub fn or(self, rule_b: Self) -> Self {
113        match self {
114            None => rule_b,
115            _ => self,
116        }
117    }
118}
119
120pub struct ParseError<'a> {
121    unknown: &'a str,
122}
123
124impl<'a> Display for ParseError<'a> {
125    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
126        f.write_str("unknown rename rule `rename_all = ")?;
127        Debug::fmt(self.unknown, f)?;
128        f.write_str("`, expected one of ")?;
129        for (i, (name, _rule)) in RENAME_RULES.iter().enumerate() {
130            if i > 0 {
131                f.write_str(", ")?;
132            }
133            Debug::fmt(name, f)?;
134        }
135        Ok(())
136    }
137}
138
139#[test]
140fn rename_variants() {
141    for &(original, lower, upper, camel, snake, screaming, kebab, screaming_kebab) in &[
142        (
143            "Outcome", "outcome", "OUTCOME", "outcome", "outcome", "OUTCOME", "outcome", "OUTCOME",
144        ),
145        (
146            "VeryTasty",
147            "verytasty",
148            "VERYTASTY",
149            "veryTasty",
150            "very_tasty",
151            "VERY_TASTY",
152            "very-tasty",
153            "VERY-TASTY",
154        ),
155        ("A", "a", "A", "a", "a", "A", "a", "A"),
156        ("Z42", "z42", "Z42", "z42", "z42", "Z42", "z42", "Z42"),
157    ] {
158        assert_eq!(None.apply_to_variant(original), original);
159        assert_eq!(LowerCase.apply_to_variant(original), lower);
160        assert_eq!(UpperCase.apply_to_variant(original), upper);
161        assert_eq!(PascalCase.apply_to_variant(original), original);
162        assert_eq!(CamelCase.apply_to_variant(original), camel);
163        assert_eq!(SnakeCase.apply_to_variant(original), snake);
164        assert_eq!(ScreamingSnakeCase.apply_to_variant(original), screaming);
165        assert_eq!(KebabCase.apply_to_variant(original), kebab);
166        assert_eq!(
167            ScreamingKebabCase.apply_to_variant(original),
168            screaming_kebab
169        );
170    }
171}
172
173#[test]
174fn rename_fields() {
175    for &(original, upper, pascal, camel, screaming, kebab, screaming_kebab) in &[
176        (
177            "outcome", "OUTCOME", "Outcome", "outcome", "OUTCOME", "outcome", "OUTCOME",
178        ),
179        (
180            "very_tasty",
181            "VERY_TASTY",
182            "VeryTasty",
183            "veryTasty",
184            "VERY_TASTY",
185            "very-tasty",
186            "VERY-TASTY",
187        ),
188        ("a", "A", "A", "a", "A", "a", "A"),
189        ("z42", "Z42", "Z42", "z42", "Z42", "z42", "Z42"),
190    ] {
191        assert_eq!(None.apply_to_field(original), original);
192        assert_eq!(UpperCase.apply_to_field(original), upper);
193        assert_eq!(PascalCase.apply_to_field(original), pascal);
194        assert_eq!(CamelCase.apply_to_field(original), camel);
195        assert_eq!(SnakeCase.apply_to_field(original), original);
196        assert_eq!(ScreamingSnakeCase.apply_to_field(original), screaming);
197        assert_eq!(KebabCase.apply_to_field(original), kebab);
198        assert_eq!(ScreamingKebabCase.apply_to_field(original), screaming_kebab);
199    }
200}