A better Rust ATProto crate
103
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 107 lines 4.2 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 whether a struct is eligible for a manual `impl Default` with schema values. 62/// 63/// Returns true when every required field has a schema default value and all other 64/// fields are optional (which naturally default to `None` or `Some(schema_default)`). 65pub fn eligible_for_schema_default(obj: &LexObject<'static>) -> bool { 66 let required = obj.required.as_ref().map(|r| r.as_slice()).unwrap_or(&[]); 67 let nullable = obj.nullable.as_ref().map(|n| n.as_slice()).unwrap_or(&[]); 68 69 required.iter().all(|field_name| { 70 // Nullable required fields are treated as optional — they default to None. 71 if nullable.contains(field_name) { 72 return true; 73 } 74 let field_name_str: &str = field_name.as_ref(); 75 obj.properties 76 .get(field_name_str) 77 .map(has_schema_default) 78 .unwrap_or(false) 79 }) 80} 81 82/// Check if a field property has a schema default value. 83pub fn has_schema_default(prop: &LexObjectProperty<'static>) -> bool { 84 match prop { 85 LexObjectProperty::Boolean(b) => b.default.is_some(), 86 LexObjectProperty::Integer(i) => i.default.is_some(), 87 LexObjectProperty::String(s) => s.default.is_some() && s.known_values.is_none(), 88 _ => false, 89 } 90} 91 92/// Check if all required fields in an object are defaultable strings. 93fn all_required_are_defaultable_strings(obj: &LexObject<'static>) -> bool { 94 let required = obj.required.as_ref().map(|r| r.as_slice()).unwrap_or(&[]); 95 96 if required.is_empty() { 97 return false; // Handled separately by count check 98 } 99 100 required.iter().all(|field_name| { 101 let field_name_str: &str = field_name.as_ref(); 102 obj.properties 103 .get(field_name_str) 104 .map(is_defaultable_string) 105 .unwrap_or(false) 106 }) 107}