Something went wrong. Try again.
atproto made easy crates.io/crates/jacquard
atproto rust
Something went wrong. Try again.
10 kB · 322 lines
Rust
at bos-beta
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323//! Builder struct generation//!//! Generates the builder struct with State generic parameter and constructor methods.
use crate::codegen::builder_gen::BuilderSchema;use heck::ToSnakeCase;use proc_macro2::TokenStream;use quote::{format_ident, quote};
/// Generate the complete builder struct including constructorspub fn generate_builder_struct( codegen: &crate::codegen::CodeGenerator, nsid: &str, type_name: &str, schema: &BuilderSchema, has_lifetime: bool, has_type_param: bool, resolved: &crate::codegen::prettify::ResolvedImports,) -> TokenStream { let builder_name = format_ident!("{}Builder", type_name); let state_mod_name = format_ident!("{}_state", type_name.to_snake_case()); let type_ident = format_ident!("{}", type_name);
// Generate field declarations let field_decls = generate_field_declarations(codegen, nsid, type_name, schema, resolved);
// When has_type_param is true, the actual type (not the builder) carries S. // The builder still carries 'a for borrow-scoping via PhantomData. let bosstr_path = resolved.external_type_tokens(&crate::codegen::prettify::ExternalImport::BosStr); let default_str_path = resolved.external_type_tokens(&crate::codegen::prettify::ExternalImport::DefaultStr);
let lifetime_param = if has_lifetime { quote! { 'a, } } else { quote! {} };
// The S type parameter for the builder struct (when has_type_param) let builder_s_param = if has_type_param { quote! {, S: #bosstr_path = #default_str_path } } else { quote! {} };
// The S arg for instantiating the builder (bare, no bounds) let builder_s_arg = if has_type_param { quote! {, S } } else { quote! {} };
let phantom = resolved.phantom_data(); let phantom_lifetime_field = if has_lifetime { quote! { _lifetime: #phantom<&'a ()>, } } else { quote! {} }; let phantom_s_field = if has_type_param { quote! { _type: #phantom<fn() -> S>, } } else { quote! {} };
// Generate Struct::new() constructor on original type. // // When has_type_param: the type is `Foo<S>` (no 'a lifetime), so the impl // is `impl<S: BosStr> Foo<S>`, and it returns a builder with // an elided lifetime for the borrow scope: `FooBuilder<'_, S, Empty>`. // // When !has_type_param: fall back to the old `impl<'a> Foo<'a>` pattern if // has_lifetime, or `impl Foo` if neither. let struct_constructor = if has_type_param { let type_s_impl = quote! { <S: #bosstr_path> }; let lifetime = if has_lifetime { quote! { '_ } } else { quote! {} }; quote! { impl #type_ident<#default_str_path> { /// Create a new builder for this type, using the default string type (DefaultStr = SmolStr) if needed pub fn new() -> #builder_name<#lifetime #state_mod_name::Empty, #default_str_path> { #builder_name::new() } }
impl #type_s_impl #type_ident<S> { /// Create a new builder for this type pub fn builder() -> #builder_name<#lifetime #state_mod_name::Empty #builder_s_arg> { #builder_name::builder() } } } } else { let lifetime_generic = if has_lifetime { quote! { <'a> } } else { quote! {} }; quote! { impl #lifetime_generic #type_ident #lifetime_generic { /// Create a new builder for this type. pub fn new() -> #builder_name<#lifetime_param #state_mod_name::Empty> { #builder_name::new() } } } };
// Generate Builder::new() constructor let builder_constructor = generate_builder_constructor( &builder_name, schema, has_lifetime, has_type_param, &state_mod_name, resolved, );
quote! { /// Builder for constructing an instance of this type. pub struct #builder_name<#lifetime_param St: #state_mod_name::State #builder_s_param> { #field_decls #phantom_lifetime_field #phantom_s_field }
#struct_constructor #builder_constructor }}
/// Generate field declarations for the builder struct/// All fields are stored in a single tuple of Optionsfn generate_field_declarations( codegen: &crate::codegen::CodeGenerator, nsid: &str, type_name: &str, schema: &BuilderSchema, resolved: &crate::codegen::prettify::ResolvedImports,) -> TokenStream { let property_names = schema.property_names(); let field_types: Vec<_> = property_names .iter() .map(|field_name| { let field_name_str: &str = field_name.as_ref(); let rust_type = match schema { BuilderSchema::Object(obj) => { let field_type = &obj.properties[field_name_str]; codegen .property_to_rust_type( nsid, type_name, field_name_str, field_type, &resolved, ) .unwrap_or_else(|_| quote! { () }) } BuilderSchema::Parameters(params) => { let field_type = ¶ms.properties[field_name_str]; get_params_rust_type(codegen, field_type, &resolved) } };
{ let opt = resolved.option_type(rust_type); quote! { #opt, } } }) .collect();
let phantom = resolved.phantom_data(); if field_types.is_empty() { // No fields - empty tuple quote! {} } else { quote! { _state: #phantom<fn() -> St>, _fields: ( #(#field_types)* ), } }}
/// Get Rust type for XRPC parameter propertypub(super) fn get_params_rust_type( codegen: &crate::codegen::CodeGenerator, field_type: &crate::lexicon::LexXrpcParametersProperty<'static>, resolved: &crate::codegen::prettify::ResolvedImports,) -> TokenStream { use crate::codegen::prettify::CommonType; use crate::lexicon::LexXrpcParametersProperty;
match field_type { LexXrpcParametersProperty::Boolean(_) => quote! { bool }, LexXrpcParametersProperty::Integer(_) => quote! { i64 }, LexXrpcParametersProperty::String(s) => codegen.string_to_rust_type(s, resolved), LexXrpcParametersProperty::Unknown(_) => resolved.type_tokens(&CommonType::Data), LexXrpcParametersProperty::Array(arr) => { let item_type = match &arr.items { crate::lexicon::LexPrimitiveArrayItem::Boolean(_) => quote! { bool }, crate::lexicon::LexPrimitiveArrayItem::Integer(_) => quote! { i64 }, crate::lexicon::LexPrimitiveArrayItem::String(s) => { codegen.string_to_rust_type(s, resolved) } crate::lexicon::LexPrimitiveArrayItem::Unknown(_) => { resolved.type_tokens(&CommonType::Data) } }; quote! { Vec<#item_type> } } }}
/// Generate Builder::new() constructor with field initializationfn generate_builder_constructor( builder_name: &syn::Ident, schema: &BuilderSchema, has_lifetime: bool, has_type_param: bool, state_mod_name: &syn::Ident, resolved: &crate::codegen::prettify::ResolvedImports,) -> TokenStream { let phantom = resolved.phantom_data(); let bosstr_path = resolved.external_type_tokens(&crate::codegen::prettify::ExternalImport::BosStr); let default_str_path = resolved.external_type_tokens(&crate::codegen::prettify::ExternalImport::DefaultStr);
let lifetime_param = if has_lifetime { quote! { 'a, } } else { quote! {} };
// S type parameter for the impl block (with bounds) let s_param = if has_type_param && !has_lifetime { quote! {S: #bosstr_path} } else if has_type_param { quote! {, S: #bosstr_path} } else { quote! {} };
// S arg for the builder instantiation path (bare, no bounds) let s_arg = if has_type_param { quote! {, S } } else { quote! {} };
let default_str_param = if has_type_param { quote! {, #default_str_path } } else { quote! {} };
// Initialize all fields as None in the tuple let property_names = schema.property_names(); let none_values = property_names.iter().map(|_| quote! { None, });
let (phantom_init, tuple_init) = if property_names.is_empty() { (quote! {}, quote! {}) } else { ( quote! { _state: #phantom, }, quote! { _fields: ( #(#none_values)* ), }, ) };
let phantom_lifetime_init = if has_lifetime { quote! { _lifetime: #phantom, } } else { quote! {} };
let phantom_s_init = if has_type_param { quote! { _type: #phantom, } } else { quote! {} };
quote! { impl<#lifetime_param> #builder_name<#lifetime_param #state_mod_name::Empty #default_str_param> { /// Create a new builder with all fields unset, using the default string type, if needed pub fn new() -> Self { #builder_name { #phantom_init #tuple_init #phantom_lifetime_init #phantom_s_init } } }
impl<#lifetime_param #s_param> #builder_name<#lifetime_param #state_mod_name::Empty #s_arg> { /// Create a new builder with all fields unset pub fn builder() -> Self { #builder_name { #phantom_init #tuple_init #phantom_lifetime_init #phantom_s_init } } } }}