A better Rust ATProto crate
at main 341 lines 12 kB view raw
1//! Attribute parsing functions 2 3use super::types::*; 4use syn::{Attribute, DeriveInput, LitStr}; 5 6/// Parse type-level lexicon attributes 7pub fn parse_type_attrs(attrs: &[Attribute]) -> syn::Result<LexiconTypeAttrs> { 8 let mut result = LexiconTypeAttrs::default(); 9 10 for attr in attrs { 11 if !attr.path().is_ident("lexicon") { 12 continue; 13 } 14 15 attr.parse_nested_meta(|meta| { 16 if meta.path.is_ident("nsid") { 17 let value = meta.value()?; 18 let lit: LitStr = value.parse()?; 19 result.nsid = Some(lit.value()); 20 Ok(()) 21 } else if meta.path.is_ident("fragment") { 22 if meta.input.peek(syn::Token![=]) { 23 let value = meta.value()?; 24 let lit: LitStr = value.parse()?; 25 result.fragment = Some(lit.value()); 26 } else { 27 result.fragment = Some(String::new()); 28 } 29 Ok(()) 30 } else if meta.path.is_ident("record") { 31 result.kind = Some(LexiconTypeKind::Record); 32 Ok(()) 33 } else if meta.path.is_ident("query") { 34 result.kind = Some(LexiconTypeKind::Query); 35 Ok(()) 36 } else if meta.path.is_ident("procedure") { 37 result.kind = Some(LexiconTypeKind::Procedure); 38 Ok(()) 39 } else if meta.path.is_ident("subscription") { 40 result.kind = Some(LexiconTypeKind::Subscription); 41 Ok(()) 42 } else if meta.path.is_ident("key") { 43 let value = meta.value()?; 44 let lit: LitStr = value.parse()?; 45 result.key = Some(lit.value()); 46 Ok(()) 47 } else { 48 Err(meta.error("unknown lexicon attribute")) 49 } 50 })?; 51 } 52 53 Ok(result) 54} 55 56/// Parse field-level lexicon attributes 57pub fn parse_field_attrs(attrs: &[Attribute]) -> syn::Result<LexiconFieldAttrs> { 58 let mut result = LexiconFieldAttrs::default(); 59 60 for attr in attrs { 61 if !attr.path().is_ident("lexicon") { 62 continue; 63 } 64 65 attr.parse_nested_meta(|meta| { 66 if meta.path.is_ident("max_length") { 67 let value = meta.value()?; 68 let lit: syn::LitInt = value.parse()?; 69 result.max_length = Some(lit.base10_parse()?); 70 Ok(()) 71 } else if meta.path.is_ident("max_graphemes") { 72 let value = meta.value()?; 73 let lit: syn::LitInt = value.parse()?; 74 result.max_graphemes = Some(lit.base10_parse()?); 75 Ok(()) 76 } else if meta.path.is_ident("min_length") { 77 let value = meta.value()?; 78 let lit: syn::LitInt = value.parse()?; 79 result.min_length = Some(lit.base10_parse()?); 80 Ok(()) 81 } else if meta.path.is_ident("min_graphemes") { 82 let value = meta.value()?; 83 let lit: syn::LitInt = value.parse()?; 84 result.min_graphemes = Some(lit.base10_parse()?); 85 Ok(()) 86 } else if meta.path.is_ident("minimum") { 87 let value = meta.value()?; 88 let lit: syn::LitInt = value.parse()?; 89 result.minimum = Some(lit.base10_parse()?); 90 Ok(()) 91 } else if meta.path.is_ident("maximum") { 92 let value = meta.value()?; 93 let lit: syn::LitInt = value.parse()?; 94 result.maximum = Some(lit.base10_parse()?); 95 Ok(()) 96 } else if meta.path.is_ident("max_items") { 97 let value = meta.value()?; 98 let lit: syn::LitInt = value.parse()?; 99 result.max_items = Some(lit.base10_parse()?); 100 Ok(()) 101 } else if meta.path.is_ident("min_items") { 102 let value = meta.value()?; 103 let lit: syn::LitInt = value.parse()?; 104 result.min_items = Some(lit.base10_parse()?); 105 Ok(()) 106 } else if meta.path.is_ident("item_max_length") { 107 let value = meta.value()?; 108 let lit: syn::LitInt = value.parse()?; 109 result.item_max_length = Some(lit.base10_parse()?); 110 Ok(()) 111 } else if meta.path.is_ident("item_max_graphemes") { 112 let value = meta.value()?; 113 let lit: syn::LitInt = value.parse()?; 114 result.item_max_graphemes = Some(lit.base10_parse()?); 115 Ok(()) 116 } else if meta.path.is_ident("item_min_length") { 117 let value = meta.value()?; 118 let lit: syn::LitInt = value.parse()?; 119 result.item_min_length = Some(lit.base10_parse()?); 120 Ok(()) 121 } else if meta.path.is_ident("item_min_graphemes") { 122 let value = meta.value()?; 123 let lit: syn::LitInt = value.parse()?; 124 result.item_min_graphemes = Some(lit.base10_parse()?); 125 Ok(()) 126 } else if meta.path.is_ident("ref") { 127 let value = meta.value()?; 128 let lit: LitStr = value.parse()?; 129 result.explicit_ref = Some(lit.value()); 130 Ok(()) 131 } else if meta.path.is_ident("format") { 132 let value = meta.value()?; 133 let lit: LitStr = value.parse()?; 134 result.format = Some(lit.value()); 135 Ok(()) 136 } else if meta.path.is_ident("union") { 137 // Check if there's a value (explicit type name) 138 if meta.input.peek(syn::Token![=]) { 139 let value = meta.value()?; 140 let lit: LitStr = value.parse()?; 141 result.union_type = Some(Some(lit.value())); 142 } else { 143 // Just #[lexicon(union)] - infer from field type 144 result.union_type = Some(None); 145 } 146 Ok(()) 147 } else { 148 Err(meta.error("unknown lexicon field attribute")) 149 } 150 })?; 151 } 152 153 Ok(result) 154} 155 156/// Parse serde attributes for a field 157pub fn parse_serde_attrs(attrs: &[Attribute]) -> syn::Result<SerdeAttrs> { 158 let mut result = SerdeAttrs::default(); 159 160 for attr in attrs { 161 if !attr.path().is_ident("serde") { 162 continue; 163 } 164 165 // Use parse_args instead of parse_nested_meta to avoid consuming unknown attributes incorrectly 166 let _ = attr.parse_nested_meta(|meta| { 167 if meta.path.is_ident("rename") { 168 let value = meta.value()?; 169 let lit: LitStr = value.parse()?; 170 result.rename = Some(lit.value()); 171 Ok(()) 172 } else if meta.path.is_ident("skip") { 173 result.skip = true; 174 Ok(()) 175 } else { 176 // Skip unknown attributes - but we need to consume the value if present 177 if meta.input.peek(syn::Token![=]) { 178 let _ = meta.value()?; 179 let _ = meta.input.parse::<proc_macro2::TokenTree>(); 180 } 181 Ok(()) 182 } 183 }); 184 } 185 186 Ok(result) 187} 188 189/// Parse container-level serde rename_all 190pub fn parse_serde_rename_all(attrs: &[Attribute]) -> syn::Result<Option<RenameRule>> { 191 for attr in attrs { 192 if !attr.path().is_ident("serde") { 193 continue; 194 } 195 196 let mut found_rule = None; 197 attr.parse_nested_meta(|meta| { 198 if meta.path.is_ident("rename_all") { 199 let value = meta.value()?; 200 let lit: LitStr = value.parse()?; 201 found_rule = RenameRule::from_str(&lit.value()); 202 Ok(()) 203 } else { 204 Ok(()) 205 } 206 })?; 207 208 if found_rule.is_some() { 209 return Ok(found_rule); 210 } 211 } 212 213 // Default to camelCase (lexicon standard) 214 Ok(Some(RenameRule::CamelCase)) 215} 216 217/// Determine NSID from attributes and context 218pub fn determine_nsid(attrs: &LexiconTypeAttrs, input: &DeriveInput) -> syn::Result<String> { 219 if let Some(nsid) = &attrs.nsid { 220 return Ok(nsid.clone()); 221 } 222 223 if attrs.fragment.is_some() { 224 return Err(syn::Error::new_spanned( 225 input, 226 "fragments require explicit nsid or module-level primary type (not yet implemented)", 227 )); 228 } 229 230 // Check for XrpcRequest derive with NSID 231 if let Some(nsid) = extract_xrpc_nsid(&input.attrs)? { 232 return Ok(nsid); 233 } 234 235 Err(syn::Error::new_spanned( 236 input, 237 "missing required `nsid` attribute (use #[lexicon(nsid = \"...\")] or #[xrpc(nsid = \"...\")])", 238 )) 239} 240 241/// Extract NSID from XrpcRequest attributes (cross-derive coordination) 242fn extract_xrpc_nsid(attrs: &[Attribute]) -> syn::Result<Option<String>> { 243 for attr in attrs { 244 if !attr.path().is_ident("xrpc") { 245 continue; 246 } 247 248 let mut nsid = None; 249 attr.parse_nested_meta(|meta| { 250 if meta.path.is_ident("nsid") { 251 let value = meta.value()?; 252 let lit: LitStr = value.parse()?; 253 nsid = Some(lit.value()); 254 } 255 Ok(()) 256 })?; 257 258 if let Some(nsid) = nsid { 259 return Ok(Some(nsid)); 260 } 261 } 262 Ok(None) 263} 264 265/// Extract T from Option<T>, return (type, is_required) 266pub fn extract_option_inner(ty: &syn::Type) -> (&syn::Type, bool) { 267 if let syn::Type::Path(type_path) = ty { 268 if let Some(segment) = type_path.path.segments.last() { 269 if segment.ident == "Option" { 270 if let syn::PathArguments::AngleBracketed(args) = &segment.arguments { 271 if let Some(syn::GenericArgument::Type(inner)) = args.args.first() { 272 return (inner, false); 273 } 274 } 275 } 276 } 277 } 278 (ty, true) 279} 280 281/// Check if type has #[open_union] attribute 282pub fn has_open_union_attr(attrs: &[Attribute]) -> bool { 283 attrs.iter().any(|attr| attr.path().is_ident("open_union")) 284} 285 286/// Extract NSID ref for a variant 287pub fn extract_variant_ref(variant: &syn::Variant, base_nsid: &str) -> syn::Result<String> { 288 use heck::ToLowerCamelCase; 289 290 // Priority 1: Check for #[nsid = "..."] attribute 291 for attr in &variant.attrs { 292 if attr.path().is_ident("nsid") { 293 if let syn::Meta::NameValue(meta) = &attr.meta { 294 if let syn::Expr::Lit(expr_lit) = &meta.value { 295 if let syn::Lit::Str(lit_str) = &expr_lit.lit { 296 return Ok(lit_str.value()); 297 } 298 } 299 } 300 } 301 } 302 303 // Priority 2: Check for #[serde(rename = "...")] attribute 304 for attr in &variant.attrs { 305 if !attr.path().is_ident("serde") { 306 continue; 307 } 308 309 let mut rename = None; 310 let _ = attr.parse_nested_meta(|meta| { 311 if meta.path.is_ident("rename") { 312 let value = meta.value()?; 313 let lit: LitStr = value.parse()?; 314 rename = Some(lit.value()); 315 } 316 Ok(()) 317 }); 318 319 if let Some(rename) = rename { 320 return Ok(rename); 321 } 322 } 323 324 // Priority 3: Generate fragment ref for unit variants 325 match &variant.fields { 326 syn::Fields::Unit => { 327 let variant_name = variant.ident.to_string().to_lower_camel_case(); 328 Ok(format!("{}#{}", base_nsid, variant_name)) 329 } 330 syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { 331 Err(syn::Error::new_spanned( 332 variant, 333 "union variants with non-primitive types must use #[nsid] or #[serde(rename)] attribute to specify the ref", 334 )) 335 } 336 _ => Err(syn::Error::new_spanned( 337 variant, 338 "union variants must be unit variants or have single unnamed field", 339 )), 340 } 341}