ruma_macros/serde/
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//! This is a minimally modified version of the same code [in serde].
5//!
6//! [serde]: https://github.com/serde-rs/serde/blame/a9f8ea0a1e8ba1206f8c28d96b924606847b85a9/serde_derive/src/internals/case.rs
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    /// Rename direct children to "M_MATRIX_ERROR_CASE" style, as used for responses with error in
37    /// Matrix spec.
38    MatrixErrorCase,
39    /// Rename the direct children to "m.lowercase" style.
40    MatrixLowerCase,
41    /// Rename the direct children to "m.snake_case" style.
42    MatrixSnakeCase,
43    /// Rename the direct children to "m.dotted.case" style.
44    MatrixDottedCase,
45    /// Rename the direct children to "m.rule.snake_case" style.
46    MatrixRuleSnakeCase,
47    /// Rename the direct children to "m.role.snake_case" style.
48    MatrixRoleSnakeCase,
49}
50
51impl RenameRule {
52    /// Apply a renaming rule to an enum variant, returning the version expected in the source.
53    pub fn apply_to_variant(&self, variant: &str) -> String {
54        match *self {
55            None | PascalCase => variant.to_owned(),
56            LowerCase => variant.to_ascii_lowercase(),
57            Uppercase => variant.to_ascii_uppercase(),
58            CamelCase => variant[..1].to_ascii_lowercase() + &variant[1..],
59            SnakeCase => {
60                let mut snake = String::new();
61                for (i, ch) in variant.char_indices() {
62                    if i > 0 && ch.is_uppercase() {
63                        snake.push('_');
64                    }
65                    snake.push(ch.to_ascii_lowercase());
66                }
67                snake
68            }
69            ScreamingSnakeCase => SnakeCase.apply_to_variant(variant).to_ascii_uppercase(),
70            KebabCase => SnakeCase.apply_to_variant(variant).replace('_', "-"),
71            ScreamingKebabCase => ScreamingSnakeCase.apply_to_variant(variant).replace('_', "-"),
72            MatrixErrorCase => String::from("M_") + &ScreamingSnakeCase.apply_to_variant(variant),
73            MatrixLowerCase => String::from("m.") + &LowerCase.apply_to_variant(variant),
74            MatrixSnakeCase => String::from("m.") + &SnakeCase.apply_to_variant(variant),
75            MatrixDottedCase => {
76                String::from("m.") + &SnakeCase.apply_to_variant(variant).replace('_', ".")
77            }
78            MatrixRuleSnakeCase => String::from(".m.rule.") + &SnakeCase.apply_to_variant(variant),
79            MatrixRoleSnakeCase => String::from("m.role.") + &SnakeCase.apply_to_variant(variant),
80        }
81    }
82
83    /// Apply a renaming rule to a struct field, returning the version expected in the source.
84    #[allow(dead_code)]
85    pub fn apply_to_field(&self, field: &str) -> String {
86        match *self {
87            None | LowerCase | SnakeCase => field.to_owned(),
88            Uppercase => field.to_ascii_uppercase(),
89            PascalCase => {
90                let mut pascal = String::new();
91                let mut capitalize = true;
92                for ch in field.chars() {
93                    if ch == '_' {
94                        capitalize = true;
95                    } else if capitalize {
96                        pascal.push(ch.to_ascii_uppercase());
97                        capitalize = false;
98                    } else {
99                        pascal.push(ch);
100                    }
101                }
102                pascal
103            }
104            CamelCase => {
105                let pascal = PascalCase.apply_to_field(field);
106                pascal[..1].to_ascii_lowercase() + &pascal[1..]
107            }
108            ScreamingSnakeCase => field.to_ascii_uppercase(),
109            KebabCase => field.replace('_', "-"),
110            ScreamingKebabCase => ScreamingSnakeCase.apply_to_field(field).replace('_', "-"),
111            MatrixErrorCase => String::from("M_") + &ScreamingSnakeCase.apply_to_field(field),
112            MatrixLowerCase => String::from("m.") + field,
113            MatrixSnakeCase => String::from("m.") + field,
114            MatrixDottedCase => String::from("m.") + &field.replace('_', "."),
115            MatrixRuleSnakeCase => String::from(".m.rule.") + field,
116            MatrixRoleSnakeCase => String::from("m.role.") + field,
117        }
118    }
119}
120
121impl FromStr for RenameRule {
122    type Err = ();
123
124    fn from_str(rename_all_str: &str) -> Result<Self, Self::Err> {
125        match rename_all_str {
126            "lowercase" => Ok(LowerCase),
127            "UPPERCASE" => Ok(Uppercase),
128            "PascalCase" => Ok(PascalCase),
129            "camelCase" => Ok(CamelCase),
130            "snake_case" => Ok(SnakeCase),
131            "SCREAMING_SNAKE_CASE" => Ok(ScreamingSnakeCase),
132            "kebab-case" => Ok(KebabCase),
133            "SCREAMING-KEBAB-CASE" => Ok(ScreamingKebabCase),
134            "M_MATRIX_ERROR_CASE" => Ok(MatrixErrorCase),
135            "m.snake_case" => Ok(MatrixSnakeCase),
136            "m.lowercase" => Ok(MatrixLowerCase),
137            "m.dotted.case" => Ok(MatrixDottedCase),
138            ".m.rule.snake_case" => Ok(MatrixRuleSnakeCase),
139            "m.role.snake_case" => Ok(MatrixRoleSnakeCase),
140            _ => Err(()),
141        }
142    }
143}
144
145#[test]
146fn rename_variants() {
147    for &(
148        original,
149        lower,
150        upper,
151        camel,
152        snake,
153        screaming,
154        kebab,
155        screaming_kebab,
156        matrix_error,
157        m_lower,
158        m_snake,
159        m_dotted,
160        m_rule_snake,
161        m_role_snake,
162    ) in &[
163        (
164            "Outcome",
165            "outcome",
166            "OUTCOME",
167            "outcome",
168            "outcome",
169            "OUTCOME",
170            "outcome",
171            "OUTCOME",
172            "M_OUTCOME",
173            "m.outcome",
174            "m.outcome",
175            "m.outcome",
176            ".m.rule.outcome",
177            "m.role.outcome",
178        ),
179        (
180            "VeryTasty",
181            "verytasty",
182            "VERYTASTY",
183            "veryTasty",
184            "very_tasty",
185            "VERY_TASTY",
186            "very-tasty",
187            "VERY-TASTY",
188            "M_VERY_TASTY",
189            "m.verytasty",
190            "m.very_tasty",
191            "m.very.tasty",
192            ".m.rule.very_tasty",
193            "m.role.very_tasty",
194        ),
195        (
196            "A",
197            "a",
198            "A",
199            "a",
200            "a",
201            "A",
202            "a",
203            "A",
204            "M_A",
205            "m.a",
206            "m.a",
207            "m.a",
208            ".m.rule.a",
209            "m.role.a",
210        ),
211        (
212            "Z42",
213            "z42",
214            "Z42",
215            "z42",
216            "z42",
217            "Z42",
218            "z42",
219            "Z42",
220            "M_Z42",
221            "m.z42",
222            "m.z42",
223            "m.z42",
224            ".m.rule.z42",
225            "m.role.z42",
226        ),
227    ] {
228        assert_eq!(None.apply_to_variant(original), original);
229        assert_eq!(LowerCase.apply_to_variant(original), lower);
230        assert_eq!(Uppercase.apply_to_variant(original), upper);
231        assert_eq!(PascalCase.apply_to_variant(original), original);
232        assert_eq!(CamelCase.apply_to_variant(original), camel);
233        assert_eq!(SnakeCase.apply_to_variant(original), snake);
234        assert_eq!(ScreamingSnakeCase.apply_to_variant(original), screaming);
235        assert_eq!(KebabCase.apply_to_variant(original), kebab);
236        assert_eq!(ScreamingKebabCase.apply_to_variant(original), screaming_kebab);
237        assert_eq!(MatrixErrorCase.apply_to_variant(original), matrix_error);
238        assert_eq!(MatrixLowerCase.apply_to_variant(original), m_lower);
239        assert_eq!(MatrixSnakeCase.apply_to_variant(original), m_snake);
240        assert_eq!(MatrixDottedCase.apply_to_variant(original), m_dotted);
241        assert_eq!(MatrixRuleSnakeCase.apply_to_variant(original), m_rule_snake);
242        assert_eq!(MatrixRoleSnakeCase.apply_to_variant(original), m_role_snake);
243    }
244}
245
246#[test]
247fn rename_fields() {
248    for &(
249        original,
250        upper,
251        pascal,
252        camel,
253        screaming,
254        kebab,
255        screaming_kebab,
256        matrix_error,
257        m_lower,
258        m_snake,
259        m_dotted,
260        m_rule_snake,
261        m_role_snake,
262    ) in &[
263        (
264            "outcome",
265            "OUTCOME",
266            "Outcome",
267            "outcome",
268            "OUTCOME",
269            "outcome",
270            "OUTCOME",
271            "M_OUTCOME",
272            "m.outcome",
273            "m.outcome",
274            "m.outcome",
275            ".m.rule.outcome",
276            "m.role.outcome",
277        ),
278        (
279            "very_tasty",
280            "VERY_TASTY",
281            "VeryTasty",
282            "veryTasty",
283            "VERY_TASTY",
284            "very-tasty",
285            "VERY-TASTY",
286            "M_VERY_TASTY",
287            "m.very_tasty",
288            "m.very_tasty",
289            "m.very.tasty",
290            ".m.rule.very_tasty",
291            "m.role.very_tasty",
292        ),
293        ("a", "A", "A", "a", "A", "a", "A", "M_A", "m.a", "m.a", "m.a", ".m.rule.a", "m.role.a"),
294        (
295            "z42",
296            "Z42",
297            "Z42",
298            "z42",
299            "Z42",
300            "z42",
301            "Z42",
302            "M_Z42",
303            "m.z42",
304            "m.z42",
305            "m.z42",
306            ".m.rule.z42",
307            "m.role.z42",
308        ),
309    ] {
310        assert_eq!(None.apply_to_field(original), original);
311        assert_eq!(Uppercase.apply_to_field(original), upper);
312        assert_eq!(PascalCase.apply_to_field(original), pascal);
313        assert_eq!(CamelCase.apply_to_field(original), camel);
314        assert_eq!(SnakeCase.apply_to_field(original), original);
315        assert_eq!(ScreamingSnakeCase.apply_to_field(original), screaming);
316        assert_eq!(KebabCase.apply_to_field(original), kebab);
317        assert_eq!(ScreamingKebabCase.apply_to_field(original), screaming_kebab);
318        assert_eq!(MatrixErrorCase.apply_to_field(original), matrix_error);
319        assert_eq!(MatrixLowerCase.apply_to_field(original), m_lower);
320        assert_eq!(MatrixSnakeCase.apply_to_field(original), m_snake);
321        assert_eq!(MatrixDottedCase.apply_to_field(original), m_dotted);
322        assert_eq!(MatrixRuleSnakeCase.apply_to_field(original), m_rule_snake);
323        assert_eq!(MatrixRoleSnakeCase.apply_to_field(original), m_role_snake);
324    }
325}