Something went wrong. Try again.
atproto made easy crates.io/crates/jacquard
atproto rust
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190use crate::corpus::LexiconCorpus;use crate::error::Result;use crate::ref_utils::{NsidPath, RefPath};use heck::ToPascalCase;use jacquard_common::CowStr;use proc_macro2::TokenStream;use quote::quote;use std::cell::RefCell;use std::collections::{HashMap, HashSet};
/// Information about a union variant#[derive(Debug, Clone)]pub struct UnionVariant { /// The original ref string (normalized) pub ref_str: String, /// The variant name (may be disambiguated) pub variant_name: String, /// The Rust type for this variant pub rust_type: TokenStream,}
/// Context for tracking namespace dependencies during union generationpub struct UnionGenContext<'a> { pub corpus: &'a LexiconCorpus, pub namespace_deps: &'a RefCell<HashMap<String, HashSet<String>>>, pub current_nsid: &'a str,}
impl<'a> UnionGenContext<'a> { /// Build variants for a union with collision detection and disambiguation pub fn build_union_variants( &self, refs: &[CowStr<'static>], ref_to_rust_type: impl Fn(&str) -> Result<TokenStream>, ) -> Result<Vec<UnionVariant>> { let current_nsid_path = NsidPath::parse(self.current_nsid); let current_namespace = current_nsid_path.namespace();
// First pass: collect all variant names and detect collisions #[derive(Debug)] struct VariantInfo { ref_str: String, ref_nsid: String, simple_name: String, is_current_namespace: bool, }
let mut seen_refs = HashSet::new(); let mut variant_infos = Vec::new(); for ref_str in refs { let normalized_ref = RefPath::normalize(ref_str, self.current_nsid); if !seen_refs.insert(normalized_ref.clone()) { continue; } let ref_path = RefPath::parse(&normalized_ref, None); let ref_nsid_str = ref_path.nsid(); let ref_def = ref_path.def();
// Skip unknown refs if !self.corpus.ref_exists(&normalized_ref) { continue; }
let is_current_namespace = ref_nsid_str.starts_with(¤t_namespace); let is_same_module = ref_nsid_str == self.current_nsid;
// Generate simple variant name let last_segment = ref_nsid_str.split('.').last().unwrap(); let simple_name = if ref_def == "main" { last_segment.to_pascal_case() } else if last_segment == "defs" { ref_def.to_pascal_case() } else if is_same_module { ref_def.to_pascal_case() } else { format!( "{}{}", last_segment.to_pascal_case(), ref_def.to_pascal_case() ) };
variant_infos.push(VariantInfo { ref_str: normalized_ref.clone(), ref_nsid: ref_nsid_str.to_string(), simple_name, is_current_namespace, }); }
// Second pass: detect collisions and disambiguate let mut name_counts: HashMap<String, usize> = HashMap::new(); for info in &variant_infos { *name_counts.entry(info.simple_name.clone()).or_insert(0) += 1; }
let mut variants = Vec::new(); for info in variant_infos { let has_collision = name_counts.get(&info.simple_name).copied().unwrap_or(0) > 1;
// Track namespace dependency for foreign refs if !info.is_current_namespace { let ref_nsid_path = NsidPath::parse(&info.ref_nsid); let foreign_namespace = ref_nsid_path.namespace(); self.namespace_deps .borrow_mut() .entry(current_namespace.clone()) .or_default() .insert(foreign_namespace); }
// Disambiguate: add second NSID segment prefix only to foreign refs when there's a collision let variant_name = if has_collision && !info.is_current_namespace { let ref_nsid_path = NsidPath::parse(&info.ref_nsid); let segments = ref_nsid_path.segments(); let prefix = if segments.len() >= 2 { segments[1].to_pascal_case() } else { segments[0].to_pascal_case() }; format!("{}{}", prefix, info.simple_name) } else { info.simple_name.clone() };
let rust_type = ref_to_rust_type(&info.ref_str)?;
variants.push(UnionVariant { ref_str: info.ref_str, variant_name, rust_type, }); }
Ok(variants) }
/// Build variants for a union without collision detection (simple mode) pub fn build_simple_union_variants( &self, refs: &[CowStr<'static>], ref_to_rust_type: impl Fn(&str) -> Result<TokenStream>, ) -> Result<Vec<UnionVariant>> { let mut variants = Vec::new();
for ref_str in refs { let ref_str_s = ref_str.as_ref(); let normalized_ref = RefPath::normalize(ref_str, self.current_nsid); let ref_path = RefPath::parse(&normalized_ref, None); let ref_nsid = ref_path.nsid(); let ref_def = ref_path.def();
let variant_name = if ref_def == "main" { let ref_nsid_path = NsidPath::parse(ref_nsid); ref_nsid_path.last_segment().to_pascal_case() } else { ref_def.to_pascal_case() };
let rust_type = ref_to_rust_type(&normalized_ref)?;
variants.push(UnionVariant { ref_str: ref_str_s.to_string(), variant_name, rust_type, }); }
Ok(variants) }}
/// Generate variant tokens for a union enumpub fn generate_variant_tokens(variants: &[UnionVariant]) -> Vec<TokenStream> { variants .iter() .map(|variant| { let variant_ident = syn::Ident::new(&variant.variant_name, proc_macro2::Span::call_site()); let ref_str_literal = &variant.ref_str; let rust_type = &variant.rust_type;
quote! { #[serde(rename = #ref_str_literal)] #variant_ident(Box<#rust_type>) } }) .collect()}