1use crate::{GenerationResult, GeneratorError, Result};
8use quote::ToTokens;
9use serde::{Deserialize, Serialize};
10use syn::{Item, Visibility};
11
12#[derive(Debug, Clone)]
14pub struct GenerationWithBindings {
15 pub generation: GenerationResult,
16 pub bindings: BindingsMetadata,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
23pub struct BindingsMetadata {
24 pub schema_version: u32,
25 pub generator_version: String,
26 pub module_label: String,
27 pub coverage: Vec<String>,
28 pub modules: Vec<String>,
29 pub reexports: Vec<String>,
30 pub symbols: Vec<BindingSymbol>,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
34pub struct BindingSymbol {
35 pub path: String,
36 pub name: String,
37 pub kind: String,
38 pub attributes: Vec<String>,
39 pub generics: String,
40 pub impl_generics: Option<String>,
41 pub impl_trait: Option<String>,
42 pub rust_type: Option<String>,
43 pub fields: Vec<BindingField>,
44 pub variants: Vec<BindingVariant>,
45 pub signature: Option<BindingSignature>,
46 pub operation: Option<BindingOperation>,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
50pub struct BindingField {
51 pub name: Option<String>,
52 pub index: usize,
53 pub public: bool,
54 pub wire_name: Option<String>,
55 pub rust_type: String,
56 pub attributes: Vec<String>,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
60pub struct BindingVariant {
61 pub name: String,
62 pub wire_name: Option<String>,
63 pub attributes: Vec<String>,
64 pub fields: Vec<BindingField>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
68pub struct BindingSignature {
69 pub rust: String,
71 pub asynchronous: bool,
72 pub receiver: Option<String>,
73 pub arguments: Vec<BindingArgument>,
74 pub return_type: String,
75 pub generics: String,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
79pub struct BindingArgument {
80 pub name: String,
81 pub rust_type: String,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
85pub struct BindingOperation {
86 pub operation_id: String,
87 pub source_json_pointer: String,
88 pub source_method: String,
89 pub source_path: String,
90 pub source_operation_id: Option<String>,
91 pub webhook: bool,
92 pub response_statuses: Vec<String>,
93 pub response_excluded_statuses: Vec<String>,
95 pub response_media_type: Option<String>,
96 pub response_kind: String,
97 pub consumption: String,
98 pub multipart_filenames: bool,
99}
100
101impl BindingsMetadata {
102 pub(crate) fn new(module_label: &str, http_client: bool) -> Self {
103 let mut coverage = vec!["models".to_string(), "public_helpers".to_string()];
104 if http_client {
105 coverage.extend(["http_client".to_string(), "enabled_builders".to_string()]);
106 }
107 Self {
108 schema_version: 1,
109 generator_version: crate::VERSION.to_string(),
110 module_label: module_label.to_string(),
111 coverage,
112 modules: Vec::new(),
113 reexports: Vec::new(),
114 symbols: Vec::new(),
115 }
116 }
117
118 pub fn to_json(&self) -> Result<String> {
120 let mut output = serde_json::to_string_pretty(self).map_err(|error| {
121 GeneratorError::CodeGenError(format!("Failed to serialize binding metadata: {error}"))
122 })?;
123 output.push('\n');
124 Ok(output)
125 }
126
127 pub(crate) fn finish(&mut self) {
128 self.modules.sort();
129 self.modules.dedup();
130 self.reexports.sort();
131 self.reexports.dedup();
132 self.symbols.sort_by(|a, b| {
133 (&a.path, &a.kind, &a.impl_trait, &a.attributes).cmp(&(
134 &b.path,
135 &b.kind,
136 &b.impl_trait,
137 &b.attributes,
138 ))
139 });
140 }
141
142 pub(crate) fn collect(&mut self, module: &str, ast: &syn::File) {
144 if module == "sse" {
145 self.coverage.push("sse_runtime".to_string());
146 }
147 self.modules.push(module.to_string());
148 self.collect_items(module, &ast.items);
149 }
150
151 fn collect_items(&mut self, module: &str, items: &[Item]) {
152 let public_types: std::collections::BTreeSet<_> = items
153 .iter()
154 .filter_map(|item| match item {
155 Item::Struct(item) if public(&item.vis) => Some(item.ident.to_string()),
156 Item::Enum(item) if public(&item.vis) => Some(item.ident.to_string()),
157 Item::Type(item) if public(&item.vis) => Some(item.ident.to_string()),
158 _ => None,
159 })
160 .collect();
161 for item in items {
162 match item {
163 Item::Struct(item) if public(&item.vis) => {
164 let mut symbol = symbol(
165 module,
166 &item.ident.to_string(),
167 "struct",
168 &item.attrs,
169 &item.generics,
170 );
171 symbol.fields = fields(
172 &item.fields,
173 serde_derived(&item.attrs),
174 serde_string(&item.attrs, "rename_all").as_deref(),
175 );
176 self.symbols.push(symbol);
177 }
178 Item::Enum(item) if public(&item.vis) => {
179 let mut symbol = symbol(
180 module,
181 &item.ident.to_string(),
182 "enum",
183 &item.attrs,
184 &item.generics,
185 );
186 symbol.variants = item
187 .variants
188 .iter()
189 .map(|variant| BindingVariant {
190 name: variant.ident.to_string(),
191 wire_name: wire_name(
192 &variant.attrs,
193 serde_derived(&item.attrs)
194 .then(|| {
195 renamed(
196 &variant.ident.to_string(),
197 serde_string(&item.attrs, "rename_all").as_deref(),
198 )
199 })
200 .as_deref(),
201 ),
202 attributes: attributes(&variant.attrs),
203 fields: fields(
204 &variant.fields,
205 serde_derived(&item.attrs),
206 serde_string(&variant.attrs, "rename_all").as_deref(),
207 )
208 .into_iter()
209 .map(|mut field| {
210 field.public = true;
211 field
212 })
213 .collect(),
214 })
215 .collect();
216 self.symbols.push(symbol);
217 }
218 Item::Type(item) if public(&item.vis) => {
219 let mut symbol = symbol(
220 module,
221 &item.ident.to_string(),
222 "alias",
223 &item.attrs,
224 &item.generics,
225 );
226 symbol.rust_type = Some(tokens(&item.ty));
227 self.symbols.push(symbol);
228 }
229 Item::Fn(item) if public(&item.vis) => {
230 let mut symbol = symbol(
231 module,
232 &item.sig.ident.to_string(),
233 "function",
234 &item.attrs,
235 &item.sig.generics,
236 );
237 symbol.signature = Some(signature(&item.sig));
238 self.symbols.push(symbol);
239 }
240 Item::Impl(item) => {
241 let syn::Type::Path(owner) = item.self_ty.as_ref() else {
242 continue;
243 };
244 let Some(owner) = owner.path.segments.last() else {
245 continue;
246 };
247 if !public_types.contains(&owner.ident.to_string()) {
248 continue;
249 }
250 for method in &item.items {
251 match method {
252 syn::ImplItem::Fn(method) if public(&method.vis) => {
253 let owner_path = format!("{module}::{}", owner.ident);
254 let mut symbol = symbol(
255 &owner_path,
256 &method.sig.ident.to_string(),
257 "method",
258 &method.attrs,
259 &method.sig.generics,
260 );
261 symbol.attributes.extend(attributes(&item.attrs));
262 symbol.impl_generics = Some(generics(&item.generics));
263 symbol.impl_trait =
264 item.trait_.as_ref().map(|(_, path, _)| tokens(path));
265 symbol.signature = Some(signature(&method.sig));
266 self.symbols.push(symbol);
267 }
268 syn::ImplItem::Const(value) if public(&value.vis) => {
269 let owner_path = format!("{module}::{}", owner.ident);
270 let mut symbol = symbol(
271 &owner_path,
272 &value.ident.to_string(),
273 "constant",
274 &value.attrs,
275 &syn::Generics::default(),
276 );
277 symbol.rust_type = Some(tokens(&value.ty));
278 symbol.impl_generics = Some(generics(&item.generics));
279 self.symbols.push(symbol);
280 }
281 _ => {}
282 }
283 }
284 }
285 Item::Mod(item) if public(&item.vis) => {
286 let path = format!("{module}::{}", item.ident);
287 self.modules.push(path.clone());
288 self.symbols.push(symbol(
289 module,
290 &item.ident.to_string(),
291 "module",
292 &item.attrs,
293 &syn::Generics::default(),
294 ));
295 if let Some((_, items)) = &item.content {
296 self.collect_items(&path, items);
297 }
298 }
299 Item::Const(item) if public(&item.vis) => {
300 let mut symbol = symbol(
301 module,
302 &item.ident.to_string(),
303 "constant",
304 &item.attrs,
305 &syn::Generics::default(),
306 );
307 symbol.rust_type = Some(tokens(&item.ty));
308 self.symbols.push(symbol);
309 }
310 Item::Use(item) if public(&item.vis) => {
311 self.reexports
312 .push(format!("{module}::{}", tokens(&item.tree)));
313 }
314 _ => {}
315 }
316 }
317 }
318}
319
320fn public(visibility: &Visibility) -> bool {
321 matches!(visibility, Visibility::Public(_))
322}
323fn tokens(value: &impl ToTokens) -> String {
324 value.to_token_stream().to_string()
325}
326fn attributes(attrs: &[syn::Attribute]) -> Vec<String> {
327 attrs
328 .iter()
329 .filter(|attribute| !attribute.path().is_ident("doc"))
330 .map(tokens)
331 .collect()
332}
333fn generics(value: &syn::Generics) -> String {
334 let params = tokens(value);
335 match &value.where_clause {
336 Some(clause) => format!("{params} {}", tokens(clause)).trim().to_string(),
337 None => params,
338 }
339}
340fn symbol(
341 module: &str,
342 name: &str,
343 kind: &str,
344 attrs: &[syn::Attribute],
345 params: &syn::Generics,
346) -> BindingSymbol {
347 BindingSymbol {
348 path: format!("{module}::{name}"),
349 name: name.to_string(),
350 kind: kind.to_string(),
351 attributes: attributes(attrs),
352 generics: generics(params),
353 impl_generics: None,
354 impl_trait: None,
355 rust_type: None,
356 fields: Vec::new(),
357 variants: Vec::new(),
358 signature: None,
359 operation: None,
360 }
361}
362fn fields(value: &syn::Fields, default_wire: bool, rename_all: Option<&str>) -> Vec<BindingField> {
363 value
364 .iter()
365 .enumerate()
366 .map(|(index, field)| {
367 let name = field.ident.as_ref().map(ToString::to_string);
368 let fallback = name
369 .as_deref()
370 .filter(|_| default_wire)
371 .map(|name| renamed(name, rename_all));
372 BindingField {
373 wire_name: wire_name(&field.attrs, fallback.as_deref()),
374 name,
375 index,
376 public: public(&field.vis),
377 rust_type: tokens(&field.ty),
378 attributes: attributes(&field.attrs),
379 }
380 })
381 .collect()
382}
383fn serde_derived(attrs: &[syn::Attribute]) -> bool {
384 attrs.iter().filter(|attribute| attribute.path().is_ident("derive")).any(|attribute| {
385 attribute.parse_args_with(syn::punctuated::Punctuated::<syn::Path, syn::Token![,]>::parse_terminated)
386 .is_ok_and(|paths| paths.iter().any(|path| path.segments.last().is_some_and(|segment| segment.ident == "Serialize" || segment.ident == "Deserialize")))
387 }) && !attrs.iter().filter(|attribute| attribute.path().is_ident("serde")).any(|attribute| {
388 attribute.parse_args_with(syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated)
389 .is_ok_and(|parts| parts.iter().any(|part| matches!(part, syn::Meta::Path(path) if path.is_ident("untagged"))))
390 })
391}
392fn serde_string(attrs: &[syn::Attribute], key: &str) -> Option<String> {
393 for attribute in attrs
394 .iter()
395 .filter(|attribute| attribute.path().is_ident("serde"))
396 {
397 if let Ok(parts) = attribute.parse_args_with(
398 syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated,
399 ) {
400 for part in parts {
401 if let syn::Meta::NameValue(value) = part {
402 if value.path.is_ident(key) {
403 if let syn::Expr::Lit(syn::ExprLit {
404 lit: syn::Lit::Str(value),
405 ..
406 }) = value.value
407 {
408 return Some(value.value());
409 }
410 }
411 }
412 }
413 }
414 }
415 None
416}
417fn renamed(name: &str, rule: Option<&str>) -> String {
418 use heck::{
419 ToKebabCase, ToLowerCamelCase, ToShoutyKebabCase, ToShoutySnakeCase, ToSnakeCase,
420 ToUpperCamelCase,
421 };
422 let name = name.strip_prefix("r#").unwrap_or(name);
423 match rule {
424 Some("lowercase") => name.to_lowercase(),
425 Some("UPPERCASE") => name.to_uppercase(),
426 Some("PascalCase") => name.to_upper_camel_case(),
427 Some("camelCase") => name.to_lower_camel_case(),
428 Some("snake_case") => name.to_snake_case(),
429 Some("SCREAMING_SNAKE_CASE") => name.to_shouty_snake_case(),
430 Some("kebab-case") => name.to_kebab_case(),
431 Some("SCREAMING-KEBAB-CASE") => name.to_shouty_kebab_case(),
432 _ => name.to_string(),
433 }
434}
435fn wire_name(attrs: &[syn::Attribute], fallback: Option<&str>) -> Option<String> {
436 for attribute in attrs
437 .iter()
438 .filter(|attribute| attribute.path().is_ident("serde"))
439 {
440 let parsed = attribute.parse_args_with(
441 syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated,
442 );
443 if let Ok(parts) = parsed {
444 for part in parts {
445 match part {
446 syn::Meta::Path(path) if path.is_ident("flatten") || path.is_ident("skip") => {
447 return None;
448 }
449 syn::Meta::NameValue(value) if value.path.is_ident("rename") => {
450 if let syn::Expr::Lit(syn::ExprLit {
451 lit: syn::Lit::Str(value),
452 ..
453 }) = value.value
454 {
455 return Some(value.value());
456 }
457 }
458 _ => {}
459 }
460 }
461 }
462 }
463 fallback.map(|name| name.strip_prefix("r#").unwrap_or(name).to_string())
464}
465fn signature(value: &syn::Signature) -> BindingSignature {
466 let mut canonical = value.clone();
469 if canonical.inputs.trailing_punct() {
470 canonical.inputs.pop_punct();
471 }
472 if canonical.generics.params.trailing_punct() {
473 canonical.generics.params.pop_punct();
474 }
475 let mut receiver = None;
476 let mut arguments = Vec::new();
477 for argument in &value.inputs {
478 match argument {
479 syn::FnArg::Receiver(value) => receiver = Some(tokens(value)),
480 syn::FnArg::Typed(value) => arguments.push(BindingArgument {
481 name: tokens(&value.pat),
482 rust_type: tokens(&value.ty),
483 }),
484 }
485 }
486 let return_type = match &value.output {
487 syn::ReturnType::Default => "()".to_string(),
488 syn::ReturnType::Type(_, ty) => tokens(ty),
489 };
490 BindingSignature {
491 rust: tokens(&canonical),
492 asynchronous: value.asyncness.is_some(),
493 receiver,
494 arguments,
495 return_type,
496 generics: generics(&value.generics),
497 }
498}