···11+use crate::lexicon::{LexObject, LexObjectProperty};
22+33+/// Decision about whether to generate builder and/or Default impl for a struct.
44+#[derive(Debug, Clone, Copy)]
55+pub struct BuilderDecision {
66+ /// Whether to generate a bon::Builder derive
77+ pub has_builder: bool,
88+ /// Whether to generate a Default derive
99+ pub has_default: bool,
1010+}
1111+1212+/// Determine whether a struct should have builder and/or Default based on heuristics.
1313+///
1414+/// Rules:
1515+/// - 0 required fields → Default (no builder)
1616+/// - All required fields are bare strings → Default (no builder)
1717+/// - 1+ required fields (not all strings) → Builder (no Default)
1818+/// - Type name conflicts with bon::Builder → no builder regardless
1919+///
2020+/// # Parameters
2121+/// - `type_name`: Name of the generated struct (to check for conflicts)
2222+/// - `obj`: Lexicon object schema
2323+///
2424+/// # Returns
2525+/// Decision about builder and Default generation
2626+pub fn should_generate_builder(type_name: &str, obj: &LexObject<'static>) -> BuilderDecision {
2727+ let required_count = count_required_fields(obj);
2828+ let has_default = required_count == 0 || all_required_are_defaultable_strings(obj);
2929+ let has_builder =
3030+ required_count >= 1 && !has_default && !conflicts_with_builder_macro(type_name);
3131+3232+ BuilderDecision {
3333+ has_builder,
3434+ has_default,
3535+ }
3636+}
3737+3838+/// Check if a type name conflicts with types referenced by bon::Builder macro.
3939+/// bon::Builder generates code that uses unqualified `Option` and `Result`,
4040+/// so structs with these names cause compilation errors.
4141+///
4242+/// This is public for cases where a struct always wants a builder (like records)
4343+/// but needs to check for conflicts.
4444+pub fn conflicts_with_builder_macro(type_name: &str) -> bool {
4545+ matches!(type_name, "Option" | "Result")
4646+}
4747+4848+/// Count the number of required fields in a lexicon object.
4949+/// Used to determine whether to generate builders or Default impls.
5050+fn count_required_fields(obj: &LexObject<'static>) -> usize {
5151+ let required = obj.required.as_ref().map(|r| r.as_slice()).unwrap_or(&[]);
5252+ required.len()
5353+}
5454+5555+/// Check if a field property is a plain string that can default to empty.
5656+/// Returns true for bare CowStr fields (no format constraints).
5757+fn is_defaultable_string(prop: &LexObjectProperty<'static>) -> bool {
5858+ matches!(prop, LexObjectProperty::String(s) if s.format.is_none())
5959+}
6060+6161+/// Check if all required fields in an object are defaultable strings.
6262+fn all_required_are_defaultable_strings(obj: &LexObject<'static>) -> bool {
6363+ let required = obj.required.as_ref().map(|r| r.as_slice()).unwrap_or(&[]);
6464+6565+ if required.is_empty() {
6666+ return false; // Handled separately by count check
6767+ }
6868+6969+ required.iter().all(|field_name| {
7070+ let field_name_str: &str = field_name.as_ref();
7171+ obj.properties
7272+ .get(field_name_str)
7373+ .map(is_defaultable_string)
7474+ .unwrap_or(false)
7575+ })
7676+}
-5
crates/jacquard-lexicon/src/codegen/lifetime.rs
···132132 }
133133 }
134134135135- /// Check if a lexicon def needs a lifetime parameter
136136- pub(super) fn def_needs_lifetime(&self, def: &LexUserType<'_>) -> bool {
137137- def.needs_lifetime(self)
138138- }
139139-140135 /// Check if xrpc params need a lifetime parameter
141136 pub(super) fn params_need_lifetime(
142137 &self,