A better Rust ATProto crate
at main 110 lines 3.9 kB view raw
1//! Build method generation for builders 2//! 3//! Generates the final build() method that constructs the target type 4//! once all required fields have been set. 5 6use crate::codegen::builder_gen::BuilderSchema; 7use crate::codegen::builder_gen::state_mod::RequiredField; 8use crate::codegen::utils::make_ident; 9use heck::ToSnakeCase; 10use jacquard_common::smol_str::SmolStr; 11use proc_macro2::TokenStream; 12use quote::{format_ident, quote}; 13 14/// Generate the build() method for a builder 15pub fn generate_build_method( 16 type_name: &str, 17 schema: &BuilderSchema, 18 required_fields: &[RequiredField], 19 has_lifetime: bool, 20) -> TokenStream { 21 let builder_name = format_ident!("{}Builder", type_name); 22 let state_mod_name = format_ident!("{}_state", type_name.to_snake_case()); 23 let type_ident = format_ident!("{}", type_name); 24 25 let lifetime_param = if has_lifetime { 26 quote! { 'a, } 27 } else { 28 quote! {} 29 }; 30 31 let lifetime_generic = if has_lifetime { 32 quote! { <'a> } 33 } else { 34 quote! {} 35 }; 36 37 // Generate where clauses for all required fields being IsSet 38 let where_clauses = required_fields.iter().map(|field| { 39 let field_pascal = format_ident!("{}", field.name_pascal.as_str()); 40 quote! { S::#field_pascal: #state_mod_name::IsSet, } 41 }); 42 43 let required_set: std::collections::HashSet<&SmolStr> = 44 required_fields.iter().map(|f| &f.name_snake).collect(); 45 46 // Generate field extractions for struct construction 47 let property_names = schema.property_names(); 48 let field_extractions: Vec<_> = property_names 49 .iter() 50 .enumerate() 51 .map(|(index, field_name)| { 52 let field_snake = make_ident(&field_name.to_snake_case()); 53 let index = syn::Index::from(index); 54 let is_required = required_set.contains(&SmolStr::new(field_name.to_snake_case())); 55 56 if is_required { 57 // Required field: unwrap from Option (guaranteed Some by IsSet constraint) 58 quote! { #field_snake: self.__unsafe_private_named.#index.unwrap(), } 59 } else { 60 // Optional field: use directly 61 quote! { #field_snake: self.__unsafe_private_named.#index, } 62 } 63 }) 64 .collect(); 65 66 // For LexObject (records/objects), add extra_data field and build_with_data method 67 // LexXrpcParameters don't have extra_data 68 let (extra_data_field, build_with_data_method) = match schema { 69 BuilderSchema::Object(_) => { 70 let default_field = quote! { extra_data: Default::default(), }; 71 72 let with_data_method = quote! { 73 /// Build the final struct with custom extra_data 74 pub fn build_with_data( 75 self, 76 extra_data: std::collections::BTreeMap< 77 jacquard_common::smol_str::SmolStr, 78 jacquard_common::types::value::Data #lifetime_generic 79 >, 80 ) -> #type_ident #lifetime_generic { 81 #type_ident { 82 #(#field_extractions)* 83 extra_data: Some(extra_data), 84 } 85 } 86 }; 87 88 (default_field, with_data_method) 89 } 90 BuilderSchema::Parameters(_) => (quote! {}, quote! {}), 91 }; 92 93 quote! { 94 impl<#lifetime_param S> #builder_name<#lifetime_param S> 95 where 96 S: #state_mod_name::State, 97 #(#where_clauses)* 98 { 99 /// Build the final struct 100 pub fn build(self) -> #type_ident #lifetime_generic { 101 #type_ident { 102 #(#field_extractions)* 103 #extra_data_field 104 } 105 } 106 107 #build_with_data_method 108 } 109 } 110}