A better Rust ATProto crate
at main 149 lines 4.5 kB view raw
1//! Custom builder generation for lexicon types 2//! 3//! This module generates type-safe builders following bon's layered typestate pattern, 4//! but at lexicon codegen time rather than via proc macros. This eliminates proc macro 5//! overhead in the API crate while maintaining identical API surface. 6 7use crate::lexicon::{LexObject, LexXrpcParameters}; 8use proc_macro2::TokenStream; 9use quote::quote; 10 11/// Schema type that can have a builder generated 12#[derive(Debug, Clone)] 13pub(crate) enum BuilderSchema<'a> { 14 /// XRPC query parameters or nested objects 15 Object(&'a LexObject<'static>), 16 /// XRPC query parameters 17 Parameters(&'a LexXrpcParameters<'static>), 18} 19 20impl<'a> BuilderSchema<'a> { 21 /// Get the required fields list 22 pub fn required(&self) -> Option<&[jacquard_common::smol_str::SmolStr]> { 23 match self { 24 BuilderSchema::Object(obj) => obj.required.as_deref(), 25 BuilderSchema::Parameters(params) => params.required.as_deref(), 26 } 27 } 28 29 pub fn nullable(&self) -> Option<&[jacquard_common::smol_str::SmolStr]> { 30 match self { 31 BuilderSchema::Object(obj) => obj.nullable.as_deref(), 32 BuilderSchema::Parameters(_) => None, 33 } 34 } 35 36 /// Get the property names (field names) 37 pub fn property_names(&self) -> Vec<&jacquard_common::smol_str::SmolStr> { 38 match self { 39 BuilderSchema::Object(obj) => obj.properties.keys().collect(), 40 BuilderSchema::Parameters(params) => params.properties.keys().collect(), 41 } 42 } 43} 44 45pub(crate) mod build_method; 46pub(crate) mod builder_struct; 47pub(crate) mod common; 48pub(crate) mod setters; 49pub(crate) mod state_mod; 50 51#[cfg(test)] 52mod tests; 53 54/// Context for builder generation 55pub(crate) struct BuilderGenContext<'a, 'c> { 56 /// Code generator reference for type conversion 57 pub codegen: &'a crate::codegen::CodeGenerator<'c>, 58 /// NSID of the lexicon being processed 59 pub nsid: &'a str, 60 /// Type name for the struct 61 pub type_name: &'a str, 62 /// Schema to generate builder for 63 pub schema: BuilderSchema<'a>, 64 /// Whether the struct has a lifetime parameter 65 pub has_lifetime: bool, 66} 67 68impl<'a, 'c> BuilderGenContext<'a, 'c> { 69 /// Create a new builder generation context from a LexObject 70 pub fn from_object( 71 codegen: &'a crate::codegen::CodeGenerator<'c>, 72 nsid: &'a str, 73 type_name: &'a str, 74 object: &'a LexObject<'static>, 75 has_lifetime: bool, 76 ) -> Self { 77 Self { 78 codegen, 79 nsid, 80 type_name, 81 schema: BuilderSchema::Object(object), 82 has_lifetime, 83 } 84 } 85 86 /// Create a new builder generation context from LexXrpcParameters 87 pub fn from_parameters( 88 codegen: &'a crate::codegen::CodeGenerator<'c>, 89 nsid: &'a str, 90 type_name: &'a str, 91 parameters: &'a LexXrpcParameters<'static>, 92 has_lifetime: bool, 93 ) -> Self { 94 Self { 95 codegen, 96 nsid, 97 type_name, 98 schema: BuilderSchema::Parameters(parameters), 99 has_lifetime, 100 } 101 } 102 103 /// Generate the complete builder for this type 104 pub fn generate(&self) -> TokenStream { 105 // if we got literally no params, don't bother. 106 if self.schema.property_names().is_empty() { 107 return quote! {}; 108 } 109 // Collect required fields 110 let required_fields = state_mod::collect_required_fields(&self.schema); 111 112 // Generate state module 113 let state_module = state_mod::generate_state_module(self.type_name, &required_fields); 114 115 // Generate builder struct with constructors 116 let builder_struct = builder_struct::generate_builder_struct( 117 self.codegen, 118 self.nsid, 119 self.type_name, 120 &self.schema, 121 self.has_lifetime, 122 ); 123 124 // Generate setters 125 let setters = setters::generate_setters( 126 self.codegen, 127 self.nsid, 128 self.type_name, 129 &self.schema, 130 &required_fields, 131 self.has_lifetime, 132 ); 133 134 // Generate build method 135 let build_method = build_method::generate_build_method( 136 self.type_name, 137 &self.schema, 138 &required_fields, 139 self.has_lifetime, 140 ); 141 142 quote! { 143 #state_module 144 #builder_struct 145 #setters 146 #build_method 147 } 148 } 149}