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 Stream,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
19pub enum RenameRule {
20 #[serde(rename = "camelCase")]
21 CamelCase,
22 #[serde(rename = "snake_case")]
23 SnakeCase,
24 #[serde(rename = "PascalCase")]
25 PascalCase,
26 #[serde(rename = "SCREAMING_SNAKE_CASE")]
27 ScreamingSnakeCase,
28 #[serde(rename = "kebab-case")]
29 KebabCase,
30 #[serde(rename = "SCREAMING-KEBAB-CASE")]
31 ScreamingKebabCase,
32 #[serde(rename = "lowercase")]
33 Lowercase,
34 #[serde(rename = "UPPERCASE")]
35 Uppercase,
36}
37
38impl RenameRule {
39 pub fn apply(&self, input: &str) -> String {
41 if input.is_empty() {
42 return String::new();
43 }
44 let words = split_words(input);
45 match self {
46 RenameRule::CamelCase => {
47 let mut result = String::new();
48 for (i, word) in words.iter().enumerate() {
49 if i == 0 {
50 result.push_str(&word.to_lowercase());
51 } else {
52 capitalize_into(word, &mut result);
53 }
54 }
55 result
56 }
57 RenameRule::PascalCase => {
58 let mut result = String::new();
59 for word in &words {
60 capitalize_into(word, &mut result);
61 }
62 result
63 }
64 RenameRule::SnakeCase => join_mapped(&words, "_", str::to_lowercase),
65 RenameRule::ScreamingSnakeCase => join_mapped(&words, "_", str::to_uppercase),
66 RenameRule::KebabCase => join_mapped(&words, "-", str::to_lowercase),
67 RenameRule::ScreamingKebabCase => join_mapped(&words, "-", str::to_uppercase),
68 RenameRule::Lowercase => join_mapped(&words, "", str::to_lowercase),
69 RenameRule::Uppercase => join_mapped(&words, "", str::to_uppercase),
70 }
71 }
72}
73
74fn join_mapped(words: &[String], sep: &str, f: fn(&str) -> String) -> String {
76 let mut out = String::new();
77 for (i, w) in words.iter().enumerate() {
78 if i > 0 {
79 out.push_str(sep);
80 }
81 out.push_str(&f(w));
82 }
83 out
84}
85
86fn capitalize_into(word: &str, out: &mut String) {
88 let mut chars = word.chars();
89 if let Some(first) = chars.next() {
90 out.extend(first.to_uppercase());
91 out.push_str(&chars.as_str().to_lowercase());
92 }
93}
94
95fn split_words(input: &str) -> Vec<String> {
103 let mut words = Vec::new();
104 for segment in input.split('_') {
105 if segment.is_empty() {
106 continue;
107 }
108 let chars: Vec<char> = segment.chars().collect();
109 let mut current = String::new();
110 for i in 0..chars.len() {
111 let ch = chars[i];
112 if ch.is_uppercase() && !current.is_empty() {
113 let prev_lower = current.chars().last().is_some_and(|c| c.is_lowercase());
114 let next_lower = chars.get(i + 1).is_some_and(|c| c.is_lowercase());
115 if prev_lower || next_lower {
118 words.push(current);
119 current = String::new();
120 }
121 }
122 current.push(ch);
123 }
124 if !current.is_empty() {
125 words.push(current);
126 }
127 }
128 words
129}
130
131#[derive(Debug, Error)]
133#[error("unknown rename_all rule: `{0}`")]
134pub struct UnknownRenameRule(String);
135
136impl FromStr for RenameRule {
137 type Err = UnknownRenameRule;
138
139 fn from_str(s: &str) -> Result<Self, Self::Err> {
140 match s {
141 "camelCase" => Ok(RenameRule::CamelCase),
142 "snake_case" => Ok(RenameRule::SnakeCase),
143 "PascalCase" => Ok(RenameRule::PascalCase),
144 "SCREAMING_SNAKE_CASE" => Ok(RenameRule::ScreamingSnakeCase),
145 "kebab-case" => Ok(RenameRule::KebabCase),
146 "SCREAMING-KEBAB-CASE" => Ok(RenameRule::ScreamingKebabCase),
147 "lowercase" => Ok(RenameRule::Lowercase),
148 "UPPERCASE" => Ok(RenameRule::Uppercase),
149 _ => Err(UnknownRenameRule(s.to_owned())),
150 }
151 }
152}
153
154#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
159pub struct RustType {
160 pub name: String,
162 pub generics: Vec<RustType>,
164}
165
166impl RustType {
167 pub fn simple(name: impl Into<String>) -> Self {
169 Self {
170 name: name.into(),
171 generics: vec![],
172 }
173 }
174
175 pub fn with_generics(name: impl Into<String>, generics: Vec<RustType>) -> Self {
177 Self {
178 name: name.into(),
179 generics,
180 }
181 }
182
183 pub fn base_name(&self) -> &str {
188 self.name.rsplit("::").next().unwrap_or(&self.name)
189 }
190}
191
192impl fmt::Display for RustType {
193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194 write!(f, "{}", self.name)?;
195 if !self.generics.is_empty() {
196 write!(f, "<")?;
197 for (i, g) in self.generics.iter().enumerate() {
198 if i > 0 {
199 write!(f, ", ")?;
200 }
201 write!(f, "{g}")?;
202 }
203 write!(f, ">")?;
204 }
205 Ok(())
206 }
207}
208
209#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
211pub struct FieldDef {
212 pub name: String,
213 pub ty: RustType,
214 #[serde(skip_serializing_if = "Option::is_none")]
215 pub rename: Option<String>,
216 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
217 pub skip: bool,
218 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
219 pub has_default: bool,
220 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
221 pub flatten: bool,
222}
223
224#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct Procedure {
227 pub name: String,
229 pub kind: ProcedureKind,
231 pub input: Option<RustType>,
233 pub output: Option<RustType>,
235 pub source_file: PathBuf,
237 #[serde(skip_serializing_if = "Option::is_none")]
239 pub docs: Option<String>,
240 #[serde(skip_serializing_if = "Option::is_none")]
242 pub timeout_ms: Option<u64>,
243 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
245 pub idempotent: bool,
246}
247
248#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct StructDef {
252 pub name: String,
254 #[serde(default, skip_serializing_if = "Vec::is_empty")]
256 pub generics: Vec<String>,
257 pub fields: Vec<FieldDef>,
259 #[serde(default, skip_serializing_if = "Vec::is_empty")]
261 pub tuple_fields: Vec<RustType>,
262 pub source_file: PathBuf,
264 #[serde(skip_serializing_if = "Option::is_none")]
266 pub docs: Option<String>,
267 #[serde(skip_serializing_if = "Option::is_none")]
269 pub rename_all: Option<RenameRule>,
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize)]
274pub struct EnumVariant {
275 pub name: String,
277 pub kind: VariantKind,
279 #[serde(skip_serializing_if = "Option::is_none")]
281 pub rename: Option<String>,
282}
283
284#[derive(Debug, Clone, Serialize, Deserialize)]
286pub enum VariantKind {
287 Unit,
289 Tuple(Vec<RustType>),
291 Struct(Vec<FieldDef>),
293}
294
295#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
303pub enum EnumTagging {
304 #[default]
305 External,
306 Internal {
307 tag: String,
308 },
309 Adjacent {
310 tag: String,
311 content: String,
312 },
313 Untagged,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
319pub struct EnumDef {
320 pub name: String,
322 #[serde(default, skip_serializing_if = "Vec::is_empty")]
324 pub generics: Vec<String>,
325 pub variants: Vec<EnumVariant>,
327 pub source_file: PathBuf,
329 #[serde(skip_serializing_if = "Option::is_none")]
331 pub docs: Option<String>,
332 #[serde(skip_serializing_if = "Option::is_none")]
334 pub rename_all: Option<RenameRule>,
335 #[serde(default)]
337 pub tagging: EnumTagging,
338}
339
340#[derive(Debug, Clone, Default, Serialize, Deserialize)]
342pub struct Manifest {
343 pub procedures: Vec<Procedure>,
344 pub structs: Vec<StructDef>,
345 pub enums: Vec<EnumDef>,
346}