1use crate::error::Result;
2use crate::lexicon::{
3 LexArrayItem, LexInteger, LexObject, LexObjectProperty, LexRecord, LexString,
4};
5use heck::{ToPascalCase, ToSnakeCase};
6use proc_macro2::TokenStream;
7use quote::quote;
8
9use super::CodeGenerator;
10use super::utils::{make_ident, value_to_variant_name};
11
12/// Enum variant kind for IntoStatic generation
13#[derive(Debug, Clone)]
14#[allow(dead_code)]
15pub(super) enum EnumVariantKind {
16 Unit,
17 Tuple,
18 Struct(Vec<String>),
19}
20
21/// Check if a type name conflicts with types referenced by bon::Builder macro.
22/// bon::Builder generates code that uses unqualified `Option` and `Result`,
23/// so structs with these names cause compilation errors.
24pub(crate) fn conflicts_with_builder_macro(type_name: &str) -> bool {
25 matches!(type_name, "Option" | "Result")
26}
27
28/// Count the number of required fields in a lexicon object.
29/// Used to determine whether to generate builders or Default impls.
30pub(crate) fn count_required_fields(obj: &LexObject<'static>) -> usize {
31 let required = obj.required.as_ref().map(|r| r.as_slice()).unwrap_or(&[]);
32 required.len()
33}
34
35/// Check if a field property is a plain string that can default to empty.
36/// Returns true for bare CowStr fields (no format constraints).
37fn is_defaultable_string(prop: &LexObjectProperty<'static>) -> bool {
38 matches!(prop, LexObjectProperty::String(s) if s.format.is_none())
39}
40
41/// Check if all required fields in an object are defaultable strings.
42pub(crate) fn all_required_are_defaultable_strings(obj: &LexObject<'static>) -> bool {
43 let required = obj.required.as_ref().map(|r| r.as_slice()).unwrap_or(&[]);
44
45 if required.is_empty() {
46 return false; // Handled separately by count check
47 }
48
49 required.iter().all(|field_name| {
50 let field_name_str: &str = field_name.as_ref();
51 obj.properties.get(field_name_str)
52 .map(is_defaultable_string)
53 .unwrap_or(false)
54 })
55}
56
57impl<'c> CodeGenerator<'c> {
58 pub(super) fn generate_record(
59 &self,
60 nsid: &str,
61 def_name: &str,
62 record: &LexRecord<'static>,
63 ) -> Result<TokenStream> {
64 match &record.record {
65 crate::lexicon::LexRecordRecord::Object(obj) => {
66 let type_name = self.def_to_type_name(nsid, def_name);
67 let ident = syn::Ident::new(&type_name, proc_macro2::Span::call_site());
68
69 // Records always get a lifetime since they have the #[lexicon] attribute
70 // which adds extra_data: BTreeMap<..., Data<'a>>
71 // Skip bon::Builder for types that conflict with the macro's unqualified type references
72 let has_builder = !conflicts_with_builder_macro(&type_name);
73
74 // Generate main struct fields
75 let fields = self.generate_object_fields(nsid, &type_name, obj, has_builder)?;
76 let doc = self.generate_doc_comment(record.description.as_ref());
77
78 let struct_def = if has_builder {
79 quote! {
80 #doc
81 #[jacquard_derive::lexicon]
82 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, jacquard_derive::IntoStatic, bon::Builder)]
83 #[serde(rename_all = "camelCase")]
84 pub struct #ident<'a> {
85 #fields
86 }
87 }
88 } else {
89 quote! {
90 #doc
91 #[jacquard_derive::lexicon]
92 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, jacquard_derive::IntoStatic)]
93 #[serde(rename_all = "camelCase")]
94 pub struct #ident<'a> {
95 #fields
96 }
97 }
98 };
99
100 // Generate union types and nested object types for this record
101 let mut unions = Vec::new();
102 for (field_name, field_type) in &obj.properties {
103 match field_type {
104 LexObjectProperty::Union(union) => {
105 // Skip empty, single-variant unions unless they're self-referential
106 if !union.refs.is_empty()
107 && (union.refs.len() > 1
108 || self.is_self_referential_union(nsid, &type_name, union))
109 {
110 let union_name =
111 self.generate_field_type_name(nsid, &type_name, field_name, "");
112 let refs: Vec<_> = union.refs.iter().cloned().collect();
113 let union_def = self.generate_union(
114 nsid,
115 &union_name,
116 &refs,
117 None,
118 union.closed,
119 )?;
120 unions.push(union_def);
121 }
122 }
123 LexObjectProperty::Object(nested_obj) => {
124 let object_name =
125 self.generate_field_type_name(nsid, &type_name, field_name, "");
126 let obj_def = self.generate_object(nsid, &object_name, nested_obj)?;
127 unions.push(obj_def);
128 }
129 LexObjectProperty::Array(array) => {
130 if let LexArrayItem::Union(union) = &array.items {
131 // Skip single-variant array unions
132 if union.refs.len() > 1 {
133 let union_name = self.generate_field_type_name(
134 nsid, &type_name, field_name, "Item",
135 );
136 let refs: Vec<_> = union.refs.iter().cloned().collect();
137 let union_def = self.generate_union(
138 nsid,
139 &union_name,
140 &refs,
141 None,
142 union.closed,
143 )?;
144 unions.push(union_def);
145 }
146 }
147 }
148 _ => {}
149 }
150 }
151
152 // Generate typed GetRecordOutput wrapper
153 let output_type_name = format!("{}GetRecordOutput", type_name);
154 let output_type_ident =
155 syn::Ident::new(&output_type_name, proc_macro2::Span::call_site());
156
157 let output_wrapper = quote! {
158 /// Typed wrapper for GetRecord response with this collection's record type.
159 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, jacquard_derive::IntoStatic)]
160 #[serde(rename_all = "camelCase")]
161 pub struct #output_type_ident<'a> {
162 #[serde(skip_serializing_if = "std::option::Option::is_none")]
163 #[serde(borrow)]
164 pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>,
165 #[serde(borrow)]
166 pub uri: jacquard_common::types::string::AtUri<'a>,
167 #[serde(borrow)]
168 pub value: #ident<'a>,
169 }
170 };
171
172 // Generate marker struct for XrpcResp
173 let record_marker_name = format!("{}Record", type_name);
174 let record_marker_ident =
175 syn::Ident::new(&record_marker_name, proc_macro2::Span::call_site());
176
177 let record_marker = quote! {
178 /// Marker type for deserializing records from this collection.
179 pub struct #record_marker_ident;
180
181 impl jacquard_common::xrpc::XrpcResp for #record_marker_ident {
182 const NSID: &'static str = #nsid;
183 const ENCODING: &'static str = "application/json";
184 type Output<'de> = #output_type_ident<'de>;
185 type Err<'de> = jacquard_common::types::collection::RecordError<'de>;
186 }
187
188
189 };
190 let from_impl = quote! {
191 impl From<#output_type_ident<'_>> for #ident<'_> {
192 fn from(output: #output_type_ident<'_>) -> Self {
193 use jacquard_common::IntoStatic;
194 output.value.into_static()
195 }
196 }
197 };
198
199 // Generate Collection trait impl
200 let collection_impl = quote! {
201 impl jacquard_common::types::collection::Collection for #ident<'_> {
202 const NSID: &'static str = #nsid;
203 type Record = #record_marker_ident;
204 }
205 };
206
207 Ok(quote! {
208 #struct_def
209 #(#unions)*
210 #output_wrapper
211 #record_marker
212 #collection_impl
213 #from_impl
214 })
215 }
216 }
217 }
218
219 /// Generate an object type
220 pub(super) fn generate_object(
221 &self,
222 nsid: &str,
223 def_name: &str,
224 obj: &LexObject<'static>,
225 ) -> Result<TokenStream> {
226 let type_name = self.def_to_type_name(nsid, def_name);
227 let ident = syn::Ident::new(&type_name, proc_macro2::Span::call_site());
228
229 // Objects always get a lifetime since they have the #[lexicon] attribute
230 // which adds extra_data: BTreeMap<..., Data<'a>>
231
232 // Smart heuristics for builder generation:
233 // - 0 required fields: Default instead of builder
234 // - All required fields are bare strings: Default instead of builder
235 // - 1+ required fields (not all strings): bon::Builder (but not if name conflicts)
236 let required_count = count_required_fields(obj);
237 let has_default = required_count == 0 || all_required_are_defaultable_strings(obj);
238 let has_builder = required_count >= 1 && !has_default && !conflicts_with_builder_macro(&type_name);
239
240 let fields = self.generate_object_fields(nsid, &type_name, obj, has_builder)?;
241 let doc = self.generate_doc_comment(obj.description.as_ref());
242
243 let struct_def = if has_builder {
244 quote! {
245 #doc
246 #[jacquard_derive::lexicon]
247 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, jacquard_derive::IntoStatic, bon::Builder)]
248 #[serde(rename_all = "camelCase")]
249 pub struct #ident<'a> {
250 #fields
251 }
252 }
253 } else if has_default {
254 quote! {
255 #doc
256 #[jacquard_derive::lexicon]
257 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, jacquard_derive::IntoStatic, Default)]
258 #[serde(rename_all = "camelCase")]
259 pub struct #ident<'a> {
260 #fields
261 }
262 }
263 } else {
264 quote! {
265 #doc
266 #[jacquard_derive::lexicon]
267 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, jacquard_derive::IntoStatic)]
268 #[serde(rename_all = "camelCase")]
269 pub struct #ident<'a> {
270 #fields
271 }
272 }
273 };
274
275 // Generate union types and nested object types for this object
276 let mut unions = Vec::new();
277 for (field_name, field_type) in &obj.properties {
278 match field_type {
279 LexObjectProperty::Union(union) => {
280 // Skip empty, single-variant unions unless they're self-referential
281 if !union.refs.is_empty()
282 && (union.refs.len() > 1
283 || self.is_self_referential_union(nsid, &type_name, union))
284 {
285 let union_name =
286 self.generate_field_type_name(nsid, &type_name, field_name, "");
287 let refs: Vec<_> = union.refs.iter().cloned().collect();
288 let union_def =
289 self.generate_union(nsid, &union_name, &refs, None, union.closed)?;
290 unions.push(union_def);
291 }
292 }
293 LexObjectProperty::Object(nested_obj) => {
294 let object_name =
295 self.generate_field_type_name(nsid, &type_name, field_name, "");
296 let obj_def = self.generate_object(nsid, &object_name, nested_obj)?;
297 unions.push(obj_def);
298 }
299 LexObjectProperty::Array(array) => {
300 if let LexArrayItem::Union(union) = &array.items {
301 // Skip single-variant array unions
302 if union.refs.len() > 1 {
303 let union_name =
304 self.generate_field_type_name(nsid, &type_name, field_name, "Item");
305 let refs: Vec<_> = union.refs.iter().cloned().collect();
306 let union_def =
307 self.generate_union(nsid, &union_name, &refs, None, union.closed)?;
308 unions.push(union_def);
309 }
310 }
311 }
312 _ => {}
313 }
314 }
315
316 Ok(quote! {
317 #struct_def
318 #(#unions)*
319 })
320 }
321
322 /// Generate fields for an object
323 pub(super) fn generate_object_fields(
324 &self,
325 nsid: &str,
326 parent_type_name: &str,
327 obj: &LexObject<'static>,
328 is_builder: bool,
329 ) -> Result<TokenStream> {
330 let required = obj.required.as_ref().map(|r| r.as_slice()).unwrap_or(&[]);
331
332 let mut fields = Vec::new();
333 for (field_name, field_type) in &obj.properties {
334 let is_required = required.contains(field_name);
335 let field_tokens = self.generate_field(
336 nsid,
337 parent_type_name,
338 field_name,
339 field_type,
340 is_required,
341 is_builder,
342 )?;
343 fields.push(field_tokens);
344 }
345
346 Ok(quote! { #(#fields)* })
347 }
348
349 /// Generate a single field
350 pub(super) fn generate_field(
351 &self,
352 nsid: &str,
353 parent_type_name: &str,
354 field_name: &str,
355 field_type: &LexObjectProperty<'static>,
356 is_required: bool,
357 is_builder: bool,
358 ) -> Result<TokenStream> {
359 if field_name.is_empty() {
360 eprintln!(
361 "Warning: Empty field name in lexicon '{}' type '{}', using 'unknown' as fallback",
362 nsid, parent_type_name
363 );
364 }
365 let field_ident = make_ident(&field_name.to_snake_case());
366
367 let rust_type =
368 self.property_to_rust_type(nsid, parent_type_name, field_name, field_type)?;
369 let needs_lifetime = self.property_needs_lifetime(field_type);
370
371 // Check if this is a CowStr field for builder(into) attribute
372 let is_cowstr = matches!(field_type, LexObjectProperty::String(s) if s.format.is_none());
373
374 let rust_type = if is_required {
375 rust_type
376 } else {
377 // Use std::option::Option for non-builder structs to avoid name collision
378 if is_builder {
379 quote! { Option<#rust_type> }
380 } else {
381 quote! { std::option::Option<#rust_type> }
382 }
383 };
384
385 // Extract description from field type
386 let description = match field_type {
387 LexObjectProperty::Ref(r) => r.description.as_ref(),
388 LexObjectProperty::Union(u) => u.description.as_ref(),
389 LexObjectProperty::Bytes(b) => b.description.as_ref(),
390 LexObjectProperty::CidLink(c) => c.description.as_ref(),
391 LexObjectProperty::Array(a) => a.description.as_ref(),
392 LexObjectProperty::Blob(b) => b.description.as_ref(),
393 LexObjectProperty::Object(o) => o.description.as_ref(),
394 LexObjectProperty::Boolean(b) => b.description.as_ref(),
395 LexObjectProperty::Integer(i) => i.description.as_ref(),
396 LexObjectProperty::String(s) => s.description.as_ref(),
397 LexObjectProperty::Unknown(u) => u.description.as_ref(),
398 };
399 let doc = self.generate_doc_comment(description);
400
401 let mut attrs = Vec::new();
402
403 if !is_required {
404 attrs.push(quote! { #[serde(skip_serializing_if = "std::option::Option::is_none")] });
405 }
406
407 if is_builder && !is_required {
408 attrs.push(quote! { #[builder(into)] });
409 }
410
411 // Add serde(borrow) to all fields with lifetimes
412 if needs_lifetime {
413 attrs.push(quote! { #[serde(borrow)] });
414 }
415
416 // Add builder(into) for CowStr fields to allow String, &str, etc., but only for builder structs
417 if is_builder && is_cowstr && is_required {
418 attrs.push(quote! { #[builder(into)] });
419 }
420
421 Ok(quote! {
422 #doc
423 #(#attrs)*
424 pub #field_ident: #rust_type,
425 })
426 }
427
428 /// Generate a union enum for refs
429 pub fn generate_union(
430 &self,
431 current_nsid: &str,
432 union_name: &str,
433 refs: &[jacquard_common::CowStr<'static>],
434 description: Option<&str>,
435 closed: Option<bool>,
436 ) -> Result<TokenStream> {
437 let enum_ident = syn::Ident::new(union_name, proc_macro2::Span::call_site());
438
439 // Extract namespace prefix from current NSID (first two segments: "sh.weaver" from "sh.weaver.embed.recordWithMedia")
440 let parts: Vec<_> = current_nsid.splitn(3, '.').collect();
441 let current_namespace = if parts.len() >= 2 {
442 format!("{}.{}", parts[0], parts[1])
443 } else {
444 current_nsid.to_string()
445 };
446
447 // First pass: collect all variant names and detect collisions
448 #[derive(Debug)]
449 struct VariantInfo {
450 ref_str: String,
451 ref_nsid: String,
452 simple_name: String,
453 is_current_namespace: bool,
454 }
455
456 let mut variant_infos = Vec::new();
457 for ref_str in refs {
458 // Normalize local refs (starting with #) by prepending current NSID
459 let normalized_ref = if ref_str.starts_with('#') {
460 format!("{}{}", current_nsid, ref_str)
461 } else {
462 ref_str.to_string()
463 };
464
465 // Parse ref to get NSID and def name
466 let (ref_nsid_str, ref_def) =
467 if let Some((nsid, fragment)) = normalized_ref.split_once('#') {
468 (nsid, fragment)
469 } else {
470 (normalized_ref.as_str(), "main")
471 };
472
473 // Skip unknown refs - they'll be handled by Unknown variant
474 if !self.corpus.ref_exists(&normalized_ref) {
475 continue;
476 }
477
478 // Check if ref is in current namespace and if it's the same module
479 let is_current_namespace = ref_nsid_str.starts_with(¤t_namespace);
480 let is_same_module = ref_nsid_str == current_nsid;
481
482 // Generate simple variant name (without namespace prefix)
483 let last_segment = ref_nsid_str.split('.').last().unwrap();
484 let simple_name = if ref_def == "main" {
485 // For main, use the last NSID segment
486 // e.g. app.bsky.embed.images#main -> Images
487 last_segment.to_pascal_case()
488 } else if last_segment == "defs" {
489 // For defs modules, just use the fragment name without "Defs" prefix
490 // e.g. app.bsky.embed.defs#images -> Images (not DefsImages)
491 ref_def.to_pascal_case()
492 } else if is_same_module {
493 // For same-module refs, just use the fragment name to avoid redundancy
494 // e.g. sh.weaver.embed.records#viewRecord in records.rs -> ViewRecord (not RecordsViewRecord)
495 ref_def.to_pascal_case()
496 } else {
497 // For other fragments, include the last NSID segment to avoid collisions
498 // e.g. app.bsky.embed.images#view -> ImagesView
499 // app.bsky.embed.video#view -> VideoView
500 format!(
501 "{}{}",
502 last_segment.to_pascal_case(),
503 ref_def.to_pascal_case()
504 )
505 };
506
507 variant_infos.push(VariantInfo {
508 ref_str: normalized_ref.clone(),
509 ref_nsid: ref_nsid_str.to_string(),
510 simple_name,
511 is_current_namespace,
512 });
513 }
514
515 // Second pass: detect collisions and disambiguate
516 use std::collections::HashMap;
517 let mut name_counts: HashMap<String, usize> = HashMap::new();
518 for info in &variant_infos {
519 *name_counts.entry(info.simple_name.clone()).or_insert(0) += 1;
520 }
521
522 let mut variants = Vec::new();
523 for info in variant_infos {
524 let has_collision = name_counts.get(&info.simple_name).copied().unwrap_or(0) > 1;
525
526 // Track namespace dependency for foreign refs
527 if !info.is_current_namespace {
528 let parts: Vec<_> = info.ref_nsid.splitn(3, '.').collect();
529 let foreign_namespace = if parts.len() >= 2 {
530 format!("{}.{}", parts[0], parts[1])
531 } else {
532 info.ref_nsid.to_string()
533 };
534 self.namespace_deps
535 .borrow_mut()
536 .entry(current_namespace.clone())
537 .or_default()
538 .insert(foreign_namespace);
539 }
540
541 // Disambiguate: add second NSID segment prefix only to foreign refs when there's a collision
542 let variant_name = if has_collision && !info.is_current_namespace {
543 // Get second segment (namespace identifier: "bsky" from "app.bsky.embed.images")
544 let segments: Vec<&str> = info.ref_nsid.split('.').collect();
545 let prefix = if segments.len() >= 2 {
546 segments[1].to_pascal_case()
547 } else {
548 // Fallback: use first segment if only one exists
549 segments[0].to_pascal_case()
550 };
551 format!("{}{}", prefix, info.simple_name)
552 } else {
553 info.simple_name.clone()
554 };
555
556 let variant_ident = syn::Ident::new(&variant_name, proc_macro2::Span::call_site());
557
558 // Get the Rust type for this ref
559 let rust_type = self.ref_to_rust_type(&info.ref_str)?;
560
561 // Add serde rename for the full NSID
562 let ref_str_literal = &info.ref_str;
563 variants.push(quote! {
564 #[serde(rename = #ref_str_literal)]
565 #variant_ident(Box<#rust_type>)
566 });
567 }
568
569 let doc = description
570 .map(|d| quote! { #[doc = #d] })
571 .unwrap_or_else(|| quote! {});
572
573 // Only add open_union if not closed
574 let is_open = closed != Some(true);
575
576 if is_open {
577 Ok(quote! {
578 #doc
579 #[jacquard_derive::open_union]
580 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, jacquard_derive::IntoStatic)]
581 #[serde(tag = "$type")]
582 #[serde(bound(deserialize = "'de: 'a"))]
583 pub enum #enum_ident<'a> {
584 #(#variants,)*
585 }
586 })
587 } else {
588 Ok(quote! {
589 #doc
590 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, jacquard_derive::IntoStatic)]
591 #[serde(tag = "$type")]
592 #[serde(bound(deserialize = "'de: 'a"))]
593 pub enum #enum_ident<'a> {
594 #(#variants,)*
595 }
596 })
597 }
598 }
599
600 /// Generate enum for string with known values
601 pub(super) fn generate_known_values_enum(
602 &self,
603 nsid: &str,
604 def_name: &str,
605 string: &LexString<'static>,
606 ) -> Result<TokenStream> {
607 let type_name = self.def_to_type_name(nsid, def_name);
608 let ident = syn::Ident::new(&type_name, proc_macro2::Span::call_site());
609
610 let known_values = string.known_values.as_ref().unwrap();
611 let mut variants = Vec::new();
612 let mut from_str_arms = Vec::new();
613 let mut as_str_arms = Vec::new();
614
615 for value in known_values {
616 // Convert value to valid Rust identifier
617 let value_str = value.as_ref();
618 let variant_name = value_to_variant_name(value_str);
619 let variant_ident = syn::Ident::new(&variant_name, proc_macro2::Span::call_site());
620
621 variants.push(quote! {
622 #variant_ident
623 });
624
625 from_str_arms.push(quote! {
626 #value_str => Self::#variant_ident
627 });
628
629 as_str_arms.push(quote! {
630 Self::#variant_ident => #value_str
631 });
632 }
633
634 let doc = self.generate_doc_comment(string.description.as_ref());
635
636 // Generate IntoStatic impl
637 let variant_info: Vec<(String, EnumVariantKind)> = known_values
638 .iter()
639 .map(|value| {
640 let variant_name = value_to_variant_name(value.as_ref());
641 (variant_name, EnumVariantKind::Unit)
642 })
643 .chain(std::iter::once((
644 "Other".to_string(),
645 EnumVariantKind::Tuple,
646 )))
647 .collect();
648 let into_static_impl =
649 self.generate_into_static_for_enum(&type_name, &variant_info, true, false);
650
651 Ok(quote! {
652 #doc
653 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
654 pub enum #ident<'a> {
655 #(#variants,)*
656 Other(jacquard_common::CowStr<'a>),
657 }
658
659 impl<'a> #ident<'a> {
660 pub fn as_str(&self) -> &str {
661 match self {
662 #(#as_str_arms,)*
663 Self::Other(s) => s.as_ref(),
664 }
665 }
666 }
667
668 impl<'a> From<&'a str> for #ident<'a> {
669 fn from(s: &'a str) -> Self {
670 match s {
671 #(#from_str_arms,)*
672 _ => Self::Other(jacquard_common::CowStr::from(s)),
673 }
674 }
675 }
676
677 impl<'a> From<String> for #ident<'a> {
678 fn from(s: String) -> Self {
679 match s.as_str() {
680 #(#from_str_arms,)*
681 _ => Self::Other(jacquard_common::CowStr::from(s)),
682 }
683 }
684 }
685
686 impl<'a> AsRef<str> for #ident<'a> {
687 fn as_ref(&self) -> &str {
688 self.as_str()
689 }
690 }
691
692 impl<'a> serde::Serialize for #ident<'a> {
693 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
694 where
695 S: serde::Serializer,
696 {
697 serializer.serialize_str(self.as_str())
698 }
699 }
700
701 impl<'de, 'a> serde::Deserialize<'de> for #ident<'a>
702 where
703 'de: 'a,
704 {
705 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
706 where
707 D: serde::Deserializer<'de>,
708 {
709 let s = <&'de str>::deserialize(deserializer)?;
710 Ok(Self::from(s))
711 }
712 }
713
714 #into_static_impl
715 })
716 }
717
718 /// Generate enum for integer with enum values
719 pub(super) fn generate_integer_enum(
720 &self,
721 nsid: &str,
722 def_name: &str,
723 integer: &LexInteger<'static>,
724 ) -> Result<TokenStream> {
725 let type_name = self.def_to_type_name(nsid, def_name);
726 let ident = syn::Ident::new(&type_name, proc_macro2::Span::call_site());
727
728 let enum_values = integer.r#enum.as_ref().unwrap();
729 let mut variants = Vec::new();
730 let mut from_i64_arms = Vec::new();
731 let mut to_i64_arms = Vec::new();
732
733 for value in enum_values {
734 let variant_name = format!("Value{}", value.abs());
735 let variant_ident = syn::Ident::new(&variant_name, proc_macro2::Span::call_site());
736
737 variants.push(quote! {
738 #[serde(rename = #value)]
739 #variant_ident
740 });
741
742 from_i64_arms.push(quote! {
743 #value => Self::#variant_ident
744 });
745
746 to_i64_arms.push(quote! {
747 Self::#variant_ident => #value
748 });
749 }
750
751 let doc = self.generate_doc_comment(integer.description.as_ref());
752
753 Ok(quote! {
754 #doc
755 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
756 pub enum #ident {
757 #(#variants,)*
758 #[serde(untagged)]
759 Other(i64),
760 }
761
762 impl #ident {
763 pub fn as_i64(&self) -> i64 {
764 match self {
765 #(#to_i64_arms,)*
766 Self::Other(n) => *n,
767 }
768 }
769 }
770
771 impl From<i64> for #ident {
772 fn from(n: i64) -> Self {
773 match n {
774 #(#from_i64_arms,)*
775 _ => Self::Other(n),
776 }
777 }
778 }
779
780 impl serde::Serialize for #ident {
781 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
782 where
783 S: serde::Serializer,
784 {
785 serializer.serialize_i64(self.as_i64())
786 }
787 }
788
789 impl<'de> serde::Deserialize<'de> for #ident {
790 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
791 where
792 D: serde::Deserializer<'de>,
793 {
794 let n = i64::deserialize(deserializer)?;
795 Ok(Self::from(n))
796 }
797 }
798 })
799 }
800
801 /// Generate IntoStatic impl for a struct
802 #[allow(dead_code)]
803 pub(super) fn generate_into_static_for_struct(
804 &self,
805 type_name: &str,
806 field_names: &[&str],
807 has_lifetime: bool,
808 has_extra_data: bool,
809 ) -> TokenStream {
810 let ident = syn::Ident::new(type_name, proc_macro2::Span::call_site());
811
812 let field_idents: Vec<_> = field_names
813 .iter()
814 .map(|name| make_ident(&name.to_snake_case()))
815 .collect();
816
817 if has_lifetime {
818 let field_conversions: Vec<_> = field_idents
819 .iter()
820 .map(|field| quote! { #field: self.#field.into_static() })
821 .collect();
822
823 let extra_data_conversion = if has_extra_data {
824 quote! { extra_data: self.extra_data.into_static(), }
825 } else {
826 quote! {}
827 };
828
829 quote! {
830 impl jacquard_common::IntoStatic for #ident<'_> {
831 type Output = #ident<'static>;
832
833 fn into_static(self) -> Self::Output {
834 #ident {
835 #(#field_conversions,)*
836 #extra_data_conversion
837 }
838 }
839 }
840 }
841 } else {
842 quote! {
843 impl jacquard_common::IntoStatic for #ident {
844 type Output = #ident;
845
846 fn into_static(self) -> Self::Output {
847 self
848 }
849 }
850 }
851 }
852 }
853
854 /// Generate IntoStatic impl for an enum
855 pub(super) fn generate_into_static_for_enum(
856 &self,
857 type_name: &str,
858 variant_info: &[(String, EnumVariantKind)],
859 has_lifetime: bool,
860 is_open: bool,
861 ) -> TokenStream {
862 let ident = syn::Ident::new(type_name, proc_macro2::Span::call_site());
863
864 if has_lifetime {
865 let variant_conversions: Vec<_> = variant_info
866 .iter()
867 .map(|(variant_name, kind)| {
868 let variant_ident = syn::Ident::new(variant_name, proc_macro2::Span::call_site());
869 match kind {
870 EnumVariantKind::Unit => {
871 quote! {
872 #ident::#variant_ident => #ident::#variant_ident
873 }
874 }
875 EnumVariantKind::Tuple => {
876 quote! {
877 #ident::#variant_ident(v) => #ident::#variant_ident(v.into_static())
878 }
879 }
880 EnumVariantKind::Struct(fields) => {
881 let field_idents: Vec<_> = fields
882 .iter()
883 .map(|f| make_ident(&f.to_snake_case()))
884 .collect();
885 let field_conversions: Vec<_> = field_idents
886 .iter()
887 .map(|f| quote! { #f: #f.into_static() })
888 .collect();
889 quote! {
890 #ident::#variant_ident { #(#field_idents,)* } => #ident::#variant_ident {
891 #(#field_conversions,)*
892 }
893 }
894 }
895 }
896 })
897 .collect();
898
899 let unknown_conversion = if is_open {
900 quote! {
901 #ident::Unknown(v) => #ident::Unknown(v.into_static()),
902 }
903 } else {
904 quote! {}
905 };
906
907 quote! {
908 impl jacquard_common::IntoStatic for #ident<'_> {
909 type Output = #ident<'static>;
910
911 fn into_static(self) -> Self::Output {
912 match self {
913 #(#variant_conversions,)*
914 #unknown_conversion
915 }
916 }
917 }
918 }
919 } else {
920 quote! {
921 impl jacquard_common::IntoStatic for #ident {
922 type Output = #ident;
923
924 fn into_static(self) -> Self::Output {
925 self
926 }
927 }
928 }
929 }
930 }
931}