cynic_codegen/idents/
old_ident.rs

1#[derive(Debug, Clone, Copy)]
2/// Rules to rename all fields in an InputObject or variants in an Enum
3/// as GraphQL naming conventions usually don't match rust
4pub enum RenameAll {
5    None,
6    /// For names that are entirely lowercase in GraphQL: `myfield`
7    Lowercase,
8    /// For names that are entirely uppercase in GraphQL: `MYFIELD`
9    Uppercase,
10    /// For names that are entirely pascal case in GraphQL: `MyField`
11    PascalCase,
12    /// For names that are entirely camel case in GraphQL: `myField`
13    CamelCase,
14    /// For names that are entirely snake case in GraphQL: `my_field`
15    SnakeCase,
16    /// For names that are entirely snake case in GraphQL: `MY_FIELD`
17    ScreamingSnakeCase,
18}
19
20impl RenameAll {
21    pub(super) fn apply(&self, string: impl AsRef<str>) -> String {
22        match self {
23            RenameAll::Lowercase => string.as_ref().to_lowercase(),
24            RenameAll::Uppercase => string.as_ref().to_uppercase(),
25            RenameAll::PascalCase => to_pascal_case(string.as_ref()),
26            RenameAll::CamelCase => to_camel_case(string.as_ref()),
27            RenameAll::SnakeCase => to_snake_case(string.as_ref()),
28            RenameAll::ScreamingSnakeCase => to_snake_case(string.as_ref()).to_uppercase(),
29            RenameAll::None => string.as_ref().to_string(),
30        }
31    }
32}
33
34impl darling::FromMeta for RenameAll {
35    fn from_string(value: &str) -> Result<RenameAll, darling::Error> {
36        match value.to_lowercase().as_ref() {
37            "none" => Ok(RenameAll::None),
38            "lowercase" => Ok(RenameAll::Lowercase),
39            "uppercase" => Ok(RenameAll::Uppercase),
40            "pascalcase" => Ok(RenameAll::PascalCase),
41            "camelcase" => Ok(RenameAll::CamelCase),
42            "snake_case" => Ok(RenameAll::SnakeCase),
43            "screaming_snake_case" => Ok(RenameAll::ScreamingSnakeCase),
44            _ => {
45                // Feels like it'd be nice if this error listed all the options...
46                Err(darling::Error::unknown_value(value))
47            }
48        }
49    }
50}
51
52pub fn to_snake_case(s: &str) -> String {
53    let mut buf = String::with_capacity(s.len());
54    // Setting this to true to avoid adding underscores at the beginning
55    let mut prev_is_upper = true;
56    for c in s.chars() {
57        if c.is_uppercase() && !prev_is_upper {
58            buf.push('_');
59            buf.extend(c.to_lowercase());
60            prev_is_upper = true;
61        } else if c.is_uppercase() {
62            buf.extend(c.to_lowercase());
63        } else {
64            prev_is_upper = false;
65            buf.push(c);
66        }
67    }
68    buf
69}
70
71// TODO: move this somewhere else...
72pub fn to_pascal_case(s: &str) -> String {
73    let mut buf = String::with_capacity(s.len());
74    let mut first_char = true;
75    let mut prev_is_upper = false;
76    let mut prev_is_underscore = false;
77    let mut chars = s.chars().peekable();
78    loop {
79        let c = chars.next();
80        if c.is_none() {
81            break;
82        }
83        let c = c.unwrap();
84        if first_char {
85            if c == '_' {
86                // keep leading underscores
87                buf.push('_');
88                while let Some('_') = chars.peek() {
89                    buf.push(chars.next().unwrap());
90                }
91            } else if c.is_uppercase() {
92                prev_is_upper = true;
93                buf.push(c);
94            } else {
95                buf.extend(c.to_uppercase());
96            }
97            first_char = false;
98            continue;
99        }
100
101        if c.is_uppercase() {
102            if prev_is_upper {
103                buf.extend(c.to_lowercase());
104            } else {
105                buf.push(c);
106            }
107            prev_is_upper = true;
108        } else if c == '_' {
109            prev_is_underscore = true;
110            prev_is_upper = false;
111        } else {
112            if prev_is_upper {
113                buf.extend(c.to_lowercase())
114            } else if prev_is_underscore {
115                buf.extend(c.to_uppercase());
116            } else {
117                buf.push(c);
118            }
119            prev_is_upper = false;
120            prev_is_underscore = false;
121        }
122    }
123
124    buf
125}
126
127pub(super) fn to_camel_case(s: &str) -> String {
128    let s = to_pascal_case(s);
129
130    let mut buf = String::with_capacity(s.len());
131    let mut chars = s.chars();
132
133    if let Some(first_char) = chars.next() {
134        buf.extend(first_char.to_lowercase());
135    }
136
137    buf.extend(chars);
138
139    buf
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn test_underscore() {
148        assert_eq!(to_snake_case("_hello"), "_hello");
149        assert_eq!(to_snake_case("_"), "_");
150    }
151
152    #[test]
153    fn test_to_snake_case() {
154        assert_eq!(to_snake_case("aString"), "a_string");
155        assert_eq!(to_snake_case("MyString"), "my_string");
156        assert_eq!(to_snake_case("my_string"), "my_string");
157        assert_eq!(to_snake_case("_another_one"), "_another_one");
158        assert_eq!(to_snake_case("RepeatedUPPERCASE"), "repeated_uppercase");
159        assert_eq!(to_snake_case("UUID"), "uuid");
160    }
161
162    #[test]
163    fn test_to_camel_case() {
164        assert_eq!(to_camel_case("aString"), "aString");
165        assert_eq!(to_camel_case("MyString"), "myString");
166        assert_eq!(to_camel_case("my_string"), "myString");
167        assert_eq!(to_camel_case("_another_one"), "_anotherOne");
168        assert_eq!(to_camel_case("RepeatedUPPERCASE"), "repeatedUppercase");
169        assert_eq!(to_camel_case("UUID"), "uuid");
170        assert_eq!(to_camel_case("__typename"), "__typename");
171    }
172
173    #[test]
174    fn test_to_pascal_case() {
175        assert_eq!(to_pascal_case("aString"), "AString");
176        assert_eq!(to_pascal_case("MyString"), "MyString");
177        assert_eq!(to_pascal_case("my_string"), "MyString");
178        assert_eq!(to_pascal_case("_another_one"), "_anotherOne");
179        assert_eq!(to_pascal_case("RepeatedUPPERCASE"), "RepeatedUppercase");
180        assert_eq!(to_pascal_case("UUID"), "Uuid");
181        assert_eq!(to_pascal_case("CREATED_AT"), "CreatedAt");
182        assert_eq!(to_pascal_case("__typename"), "__typename");
183    }
184
185    #[test]
186    fn casings_are_not_lossy_where_possible() {
187        for s in ["snake_case_thing", "snake"] {
188            assert_eq!(to_snake_case(&to_pascal_case(s)), s);
189        }
190
191        for s in ["PascalCase", "Pascal"] {
192            assert_eq!(to_pascal_case(&to_snake_case(s)), s);
193        }
194
195        for s in ["camelCase", "camel"] {
196            assert_eq!(to_camel_case(&to_snake_case(s)), s);
197        }
198    }
199}