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