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