1use std::fmt;
2use std::path::PathBuf;
3use std::str::FromStr;
4
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "lowercase")]
11pub enum ProcedureKind {
12 Query,
13 Mutation,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18pub enum RenameRule {
19 #[serde(rename = "camelCase")]
20 CamelCase,
21 #[serde(rename = "snake_case")]
22 SnakeCase,
23 #[serde(rename = "PascalCase")]
24 PascalCase,
25 #[serde(rename = "SCREAMING_SNAKE_CASE")]
26 ScreamingSnakeCase,
27 #[serde(rename = "kebab-case")]
28 KebabCase,
29 #[serde(rename = "SCREAMING-KEBAB-CASE")]
30 ScreamingKebabCase,
31 #[serde(rename = "lowercase")]
32 Lowercase,
33 #[serde(rename = "UPPERCASE")]
34 Uppercase,
35}
36
37impl RenameRule {
38 pub fn apply(&self, input: &str) -> String {
40 if input.is_empty() {
41 return String::new();
42 }
43 let words = split_words(input);
44 match self {
45 RenameRule::CamelCase => {
46 let mut result = String::new();
47 for (i, word) in words.iter().enumerate() {
48 if i == 0 {
49 result.push_str(&word.to_lowercase());
50 } else {
51 capitalize_into(word, &mut result);
52 }
53 }
54 result
55 }
56 RenameRule::PascalCase => {
57 let mut result = String::new();
58 for word in &words {
59 capitalize_into(word, &mut result);
60 }
61 result
62 }
63 RenameRule::SnakeCase => join_mapped(&words, "_", str::to_lowercase),
64 RenameRule::ScreamingSnakeCase => join_mapped(&words, "_", str::to_uppercase),
65 RenameRule::KebabCase => join_mapped(&words, "-", str::to_lowercase),
66 RenameRule::ScreamingKebabCase => join_mapped(&words, "-", str::to_uppercase),
67 RenameRule::Lowercase => join_mapped(&words, "", str::to_lowercase),
68 RenameRule::Uppercase => join_mapped(&words, "", str::to_uppercase),
69 }
70 }
71}
72
73fn join_mapped(words: &[String], sep: &str, f: fn(&str) -> String) -> String {
75 let mut out = String::new();
76 for (i, w) in words.iter().enumerate() {
77 if i > 0 {
78 out.push_str(sep);
79 }
80 out.push_str(&f(w));
81 }
82 out
83}
84
85fn capitalize_into(word: &str, out: &mut String) {
87 let mut chars = word.chars();
88 if let Some(first) = chars.next() {
89 out.extend(first.to_uppercase());
90 out.push_str(&chars.as_str().to_lowercase());
91 }
92}
93
94fn split_words(input: &str) -> Vec<String> {
102 let mut words = Vec::new();
103 for segment in input.split('_') {
104 if segment.is_empty() {
105 continue;
106 }
107 let chars: Vec<char> = segment.chars().collect();
108 let mut current = String::new();
109 for i in 0..chars.len() {
110 let ch = chars[i];
111 if ch.is_uppercase() && !current.is_empty() {
112 let prev_lower = current.chars().last().is_some_and(|c| c.is_lowercase());
113 let next_lower = chars.get(i + 1).is_some_and(|c| c.is_lowercase());
114 if prev_lower || next_lower {
117 words.push(current);
118 current = String::new();
119 }
120 }
121 current.push(ch);
122 }
123 if !current.is_empty() {
124 words.push(current);
125 }
126 }
127 words
128}
129
130#[derive(Debug, Error)]
132#[error("unknown rename_all rule: `{0}`")]
133pub struct UnknownRenameRule(String);
134
135impl FromStr for RenameRule {
136 type Err = UnknownRenameRule;
137
138 fn from_str(s: &str) -> Result<Self, Self::Err> {
139 match s {
140 "camelCase" => Ok(RenameRule::CamelCase),
141 "snake_case" => Ok(RenameRule::SnakeCase),
142 "PascalCase" => Ok(RenameRule::PascalCase),
143 "SCREAMING_SNAKE_CASE" => Ok(RenameRule::ScreamingSnakeCase),
144 "kebab-case" => Ok(RenameRule::KebabCase),
145 "SCREAMING-KEBAB-CASE" => Ok(RenameRule::ScreamingKebabCase),
146 "lowercase" => Ok(RenameRule::Lowercase),
147 "UPPERCASE" => Ok(RenameRule::Uppercase),
148 _ => Err(UnknownRenameRule(s.to_owned())),
149 }
150 }
151}
152
153#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
158pub struct RustType {
159 pub name: String,
161 pub generics: Vec<RustType>,
163}
164
165impl RustType {
166 pub fn simple(name: impl Into<String>) -> Self {
168 Self {
169 name: name.into(),
170 generics: vec![],
171 }
172 }
173
174 pub fn with_generics(name: impl Into<String>, generics: Vec<RustType>) -> Self {
176 Self {
177 name: name.into(),
178 generics,
179 }
180 }
181}
182
183impl fmt::Display for RustType {
184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 write!(f, "{}", self.name)?;
186 if !self.generics.is_empty() {
187 write!(f, "<")?;
188 for (i, g) in self.generics.iter().enumerate() {
189 if i > 0 {
190 write!(f, ", ")?;
191 }
192 write!(f, "{g}")?;
193 }
194 write!(f, ">")?;
195 }
196 Ok(())
197 }
198}
199
200#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
202pub struct FieldDef {
203 pub name: String,
204 pub ty: RustType,
205 #[serde(skip_serializing_if = "Option::is_none")]
206 pub rename: Option<String>,
207 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
208 pub skip: bool,
209 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
210 pub has_default: bool,
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct Procedure {
216 pub name: String,
218 pub kind: ProcedureKind,
220 pub input: Option<RustType>,
222 pub output: Option<RustType>,
224 pub source_file: PathBuf,
226 #[serde(skip_serializing_if = "Option::is_none")]
228 pub docs: Option<String>,
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct StructDef {
235 pub name: String,
237 pub fields: Vec<FieldDef>,
239 pub source_file: PathBuf,
241 #[serde(skip_serializing_if = "Option::is_none")]
243 pub docs: Option<String>,
244 #[serde(skip_serializing_if = "Option::is_none")]
246 pub rename_all: Option<RenameRule>,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct EnumVariant {
252 pub name: String,
254 pub kind: VariantKind,
256 #[serde(skip_serializing_if = "Option::is_none")]
258 pub rename: Option<String>,
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize)]
263pub enum VariantKind {
264 Unit,
266 Tuple(Vec<RustType>),
268 Struct(Vec<FieldDef>),
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize)]
275pub struct EnumDef {
276 pub name: String,
278 pub variants: Vec<EnumVariant>,
280 pub source_file: PathBuf,
282 #[serde(skip_serializing_if = "Option::is_none")]
284 pub docs: Option<String>,
285 #[serde(skip_serializing_if = "Option::is_none")]
287 pub rename_all: Option<RenameRule>,
288}
289
290#[derive(Debug, Clone, Default, Serialize, Deserialize)]
292pub struct Manifest {
293 pub procedures: Vec<Procedure>,
294 pub structs: Vec<StructDef>,
295 pub enums: Vec<EnumDef>,
296}