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 pub fn base_name(&self) -> &str {
187 self.name.rsplit("::").next().unwrap_or(&self.name)
188 }
189}
190
191impl fmt::Display for RustType {
192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193 write!(f, "{}", self.name)?;
194 if !self.generics.is_empty() {
195 write!(f, "<")?;
196 for (i, g) in self.generics.iter().enumerate() {
197 if i > 0 {
198 write!(f, ", ")?;
199 }
200 write!(f, "{g}")?;
201 }
202 write!(f, ">")?;
203 }
204 Ok(())
205 }
206}
207
208#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
210pub struct FieldDef {
211 pub name: String,
212 pub ty: RustType,
213 #[serde(skip_serializing_if = "Option::is_none")]
214 pub rename: Option<String>,
215 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
216 pub skip: bool,
217 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
218 pub has_default: bool,
219 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
220 pub flatten: bool,
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct Procedure {
226 pub name: String,
228 pub kind: ProcedureKind,
230 pub input: Option<RustType>,
232 pub output: Option<RustType>,
234 pub source_file: PathBuf,
236 #[serde(skip_serializing_if = "Option::is_none")]
238 pub docs: Option<String>,
239 #[serde(skip_serializing_if = "Option::is_none")]
241 pub timeout_ms: Option<u64>,
242 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
244 pub idempotent: bool,
245}
246
247#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct StructDef {
251 pub name: String,
253 #[serde(default, skip_serializing_if = "Vec::is_empty")]
255 pub generics: Vec<String>,
256 pub fields: Vec<FieldDef>,
258 #[serde(default, skip_serializing_if = "Vec::is_empty")]
260 pub tuple_fields: Vec<RustType>,
261 pub source_file: PathBuf,
263 #[serde(skip_serializing_if = "Option::is_none")]
265 pub docs: Option<String>,
266 #[serde(skip_serializing_if = "Option::is_none")]
268 pub rename_all: Option<RenameRule>,
269}
270
271#[derive(Debug, Clone, Serialize, Deserialize)]
273pub struct EnumVariant {
274 pub name: String,
276 pub kind: VariantKind,
278 #[serde(skip_serializing_if = "Option::is_none")]
280 pub rename: Option<String>,
281}
282
283#[derive(Debug, Clone, Serialize, Deserialize)]
285pub enum VariantKind {
286 Unit,
288 Tuple(Vec<RustType>),
290 Struct(Vec<FieldDef>),
292}
293
294#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
302pub enum EnumTagging {
303 #[default]
304 External,
305 Internal {
306 tag: String,
307 },
308 Adjacent {
309 tag: String,
310 content: String,
311 },
312 Untagged,
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize)]
318pub struct EnumDef {
319 pub name: String,
321 #[serde(default, skip_serializing_if = "Vec::is_empty")]
323 pub generics: Vec<String>,
324 pub variants: Vec<EnumVariant>,
326 pub source_file: PathBuf,
328 #[serde(skip_serializing_if = "Option::is_none")]
330 pub docs: Option<String>,
331 #[serde(skip_serializing_if = "Option::is_none")]
333 pub rename_all: Option<RenameRule>,
334 #[serde(default)]
336 pub tagging: EnumTagging,
337}
338
339#[derive(Debug, Clone, Default, Serialize, Deserialize)]
341pub struct Manifest {
342 pub procedures: Vec<Procedure>,
343 pub structs: Vec<StructDef>,
344 pub enums: Vec<EnumDef>,
345}