A better Rust ATProto crate
at main 36 kB view raw
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(&current_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}