apache_avro_derive/
case.rs

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