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
4// See https://users.rust-lang.org/t/psa-dealing-with-warning-unused-import-std-ascii-asciiext-in-today-s-nightly/13726
5#[allow(deprecated, unused_imports)]
6use std::ascii::AsciiExt;
7
8use std::str::FromStr;
9
10use self::RenameRule::*;
11
12/// The different possible ways to change case of fields in a struct, or variants in an enum.
13#[derive(Copy, Clone, PartialEq)]
14pub enum RenameRule {
15    /// Don't apply a default rename rule.
16    None,
17    /// Rename direct children to "lowercase" style.
18    LowerCase,
19    /// Rename direct children to "UPPERCASE" style.
20    UPPERCASE,
21    /// Rename direct children to "PascalCase" style, as typically used for
22    /// enum variants.
23    PascalCase,
24    /// Rename direct children to "camelCase" style.
25    CamelCase,
26    /// Rename direct children to "snake_case" style, as commonly used for
27    /// fields.
28    SnakeCase,
29    /// Rename direct children to "SCREAMING_SNAKE_CASE" style, as commonly
30    /// used for constants.
31    ScreamingSnakeCase,
32    /// Rename direct children to "kebab-case" style.
33    KebabCase,
34    /// Rename direct children to "SCREAMING-KEBAB-CASE" style.
35    ScreamingKebabCase,
36}
37
38impl RenameRule {
39    /// Apply a renaming rule to an enum variant, returning the version expected in the source.
40    pub fn apply_to_variant(&self, variant: &str) -> String {
41        match *self {
42            None | PascalCase => variant.to_owned(),
43            LowerCase => variant.to_ascii_lowercase(),
44            UPPERCASE => variant.to_ascii_uppercase(),
45            CamelCase => variant[..1].to_ascii_lowercase() + &variant[1..],
46            SnakeCase => {
47                let mut snake = String::new();
48                for (i, ch) in variant.char_indices() {
49                    if i > 0 && ch.is_uppercase() {
50                        snake.push('_');
51                    }
52                    snake.push(ch.to_ascii_lowercase());
53                }
54                snake
55            }
56            ScreamingSnakeCase => SnakeCase.apply_to_variant(variant).to_ascii_uppercase(),
57            KebabCase => SnakeCase.apply_to_variant(variant).replace('_', "-"),
58            ScreamingKebabCase => ScreamingSnakeCase
59                .apply_to_variant(variant)
60                .replace('_', "-"),
61        }
62    }
63
64    /// Apply a renaming rule to a struct field, returning the version expected in the source.
65    pub fn apply_to_field(&self, field: &str) -> String {
66        match *self {
67            None | LowerCase | SnakeCase => field.to_owned(),
68            UPPERCASE => field.to_ascii_uppercase(),
69            PascalCase => {
70                let mut pascal = String::new();
71                let mut capitalize = true;
72                for ch in field.chars() {
73                    if ch == '_' {
74                        capitalize = true;
75                    } else if capitalize {
76                        pascal.push(ch.to_ascii_uppercase());
77                        capitalize = false;
78                    } else {
79                        pascal.push(ch);
80                    }
81                }
82                pascal
83            }
84            CamelCase => {
85                let pascal = PascalCase.apply_to_field(field);
86                pascal[..1].to_ascii_lowercase() + &pascal[1..]
87            }
88            ScreamingSnakeCase => field.to_ascii_uppercase(),
89            KebabCase => field.replace('_', "-"),
90            ScreamingKebabCase => ScreamingSnakeCase.apply_to_field(field).replace('_', "-"),
91        }
92    }
93}
94
95impl FromStr for RenameRule {
96    type Err = ();
97
98    fn from_str(rename_all_str: &str) -> Result<Self, Self::Err> {
99        match rename_all_str {
100            "lowercase" => Ok(LowerCase),
101            "UPPERCASE" => Ok(UPPERCASE),
102            "PascalCase" => Ok(PascalCase),
103            "camelCase" => Ok(CamelCase),
104            "snake_case" => Ok(SnakeCase),
105            "SCREAMING_SNAKE_CASE" => Ok(ScreamingSnakeCase),
106            "kebab-case" => Ok(KebabCase),
107            "SCREAMING-KEBAB-CASE" => Ok(ScreamingKebabCase),
108            _ => Err(()),
109        }
110    }
111}
112
113#[test]
114fn rename_variants() {
115    for &(original, lower, upper, camel, snake, screaming, kebab, screaming_kebab) in &[
116        (
117            "Outcome", "outcome", "OUTCOME", "outcome", "outcome", "OUTCOME", "outcome", "OUTCOME",
118        ),
119        (
120            "VeryTasty",
121            "verytasty",
122            "VERYTASTY",
123            "veryTasty",
124            "very_tasty",
125            "VERY_TASTY",
126            "very-tasty",
127            "VERY-TASTY",
128        ),
129        ("A", "a", "A", "a", "a", "A", "a", "A"),
130        ("Z42", "z42", "Z42", "z42", "z42", "Z42", "z42", "Z42"),
131    ] {
132        assert_eq!(None.apply_to_variant(original), original);
133        assert_eq!(LowerCase.apply_to_variant(original), lower);
134        assert_eq!(UPPERCASE.apply_to_variant(original), upper);
135        assert_eq!(PascalCase.apply_to_variant(original), original);
136        assert_eq!(CamelCase.apply_to_variant(original), camel);
137        assert_eq!(SnakeCase.apply_to_variant(original), snake);
138        assert_eq!(ScreamingSnakeCase.apply_to_variant(original), screaming);
139        assert_eq!(KebabCase.apply_to_variant(original), kebab);
140        assert_eq!(
141            ScreamingKebabCase.apply_to_variant(original),
142            screaming_kebab
143        );
144    }
145}
146
147#[test]
148fn rename_fields() {
149    for &(original, upper, pascal, camel, screaming, kebab, screaming_kebab) in &[
150        (
151            "outcome", "OUTCOME", "Outcome", "outcome", "OUTCOME", "outcome", "OUTCOME",
152        ),
153        (
154            "very_tasty",
155            "VERY_TASTY",
156            "VeryTasty",
157            "veryTasty",
158            "VERY_TASTY",
159            "very-tasty",
160            "VERY-TASTY",
161        ),
162        ("a", "A", "A", "a", "A", "a", "A"),
163        ("z42", "Z42", "Z42", "z42", "Z42", "z42", "Z42"),
164    ] {
165        assert_eq!(None.apply_to_field(original), original);
166        assert_eq!(UPPERCASE.apply_to_field(original), upper);
167        assert_eq!(PascalCase.apply_to_field(original), pascal);
168        assert_eq!(CamelCase.apply_to_field(original), camel);
169        assert_eq!(SnakeCase.apply_to_field(original), original);
170        assert_eq!(ScreamingSnakeCase.apply_to_field(original), screaming);
171        assert_eq!(KebabCase.apply_to_field(original), kebab);
172        assert_eq!(ScreamingKebabCase.apply_to_field(original), screaming_kebab);
173    }
174}