A better Rust ATProto crate
at main 76 lines 2.9 kB view raw
1use crate::lexicon::{LexObject, LexObjectProperty}; 2 3/// Decision about whether to generate builder and/or Default impl for a struct. 4#[derive(Debug, Clone, Copy)] 5pub struct BuilderDecision { 6 /// Whether to generate a bon::Builder derive 7 pub has_builder: bool, 8 /// Whether to generate a Default derive 9 pub has_default: bool, 10} 11 12/// Determine whether a struct should have builder and/or Default based on heuristics. 13/// 14/// Rules: 15/// - 0 required fields → Default (no builder) 16/// - All required fields are bare strings → Default (no builder) 17/// - 1+ required fields (not all strings) → Builder (no Default) 18/// - Type name conflicts with bon::Builder → no builder regardless 19/// 20/// # Parameters 21/// - `type_name`: Name of the generated struct (to check for conflicts) 22/// - `obj`: Lexicon object schema 23/// 24/// # Returns 25/// Decision about builder and Default generation 26pub fn should_generate_builder(type_name: &str, obj: &LexObject<'static>) -> BuilderDecision { 27 let required_count = count_required_fields(obj); 28 let has_default = required_count == 0 || all_required_are_defaultable_strings(obj); 29 let has_builder = 30 required_count >= 1 && !has_default && !conflicts_with_builder_macro(type_name); 31 32 BuilderDecision { 33 has_builder, 34 has_default, 35 } 36} 37 38/// Check if a type name conflicts with types referenced by bon::Builder macro. 39/// bon::Builder generates code that uses unqualified `Option` and `Result`, 40/// so structs with these names cause compilation errors. 41/// 42/// This is public for cases where a struct always wants a builder (like records) 43/// but needs to check for conflicts. 44pub fn conflicts_with_builder_macro(type_name: &str) -> bool { 45 matches!(type_name, "Option" | "Result") 46} 47 48/// Count the number of required fields in a lexicon object. 49/// Used to determine whether to generate builders or Default impls. 50fn count_required_fields(obj: &LexObject<'static>) -> usize { 51 let required = obj.required.as_ref().map(|r| r.as_slice()).unwrap_or(&[]); 52 required.len() 53} 54 55/// Check if a field property is a plain string that can default to empty. 56/// Returns true for bare CowStr fields (no format constraints). 57fn is_defaultable_string(prop: &LexObjectProperty<'static>) -> bool { 58 matches!(prop, LexObjectProperty::String(s) if s.format.is_none()) 59} 60 61/// Check if all required fields in an object are defaultable strings. 62fn all_required_are_defaultable_strings(obj: &LexObject<'static>) -> bool { 63 let required = obj.required.as_ref().map(|r| r.as_slice()).unwrap_or(&[]); 64 65 if required.is_empty() { 66 return false; // Handled separately by count check 67 } 68 69 required.iter().all(|field_name| { 70 let field_name_str: &str = field_name.as_ref(); 71 obj.properties 72 .get(field_name_str) 73 .map(is_defaultable_string) 74 .unwrap_or(false) 75 }) 76}