diff --git a/src/analyzer.rs b/src/analyzer.rs index e487fa9..71d89fe 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -165,6 +165,7 @@ fn analyze_enum_properties( type_: rust_type, name: name.to_string(), serde_annot: vec![], + extra_annot: vec![], docs: member_doc, }) } @@ -308,6 +309,7 @@ fn analyze_object_properties( type_: rust_type, name: key.to_string(), serde_annot: vec![], + extra_annot: vec![], docs: member_doc, }) } else { @@ -320,6 +322,7 @@ fn analyze_object_properties( "default".into(), "skip_serializing_if = \"Option::is_none\"".into(), ], + extra_annot: vec![], docs: member_doc, }) // TODO: must capture `default` key here instead of blindly using serde default diff --git a/src/main.rs b/src/main.rs index cfcf667..03283f2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -184,11 +184,10 @@ impl Kopium { if let Some(schema) = data { log::debug!("schema: {}", serde_json::to_string_pretty(&schema)?); - let mut output = analyze(schema, &kind)?; - if self.rust_case { - output = output.rename(); - } - let structs = output.0; + let structs = analyze(schema, &kind)? + .rename(self.rust_case) + .builder_fields(self.builders) + .0; if !self.hide_prelude { self.print_prelude(&structs); @@ -243,16 +242,10 @@ impl Kopium { } else { format_ident!("{}", m.name) }; - let spec_trimmed_type = m.type_.as_str().replace(&format!("{}Spec", kind), &kind); - if self.builders { - if spec_trimmed_type.starts_with("Option<") { - println!("#[builder(default, setter(strip_option))]"); - } else if spec_trimmed_type.starts_with("Vec<") - || spec_trimmed_type.starts_with("BTreeMap<") - { - println!("#[builder(default)]"); - } + for annot in m.extra_annot { + println!(" {}", annot); } + let spec_trimmed_type = m.type_.as_str().replace(&format!("{}Spec", kind), &kind); if s.is_enum { // NB: only supporting plain enumerations atm, not oneOf println!(" {},", safe_name); diff --git a/src/output.rs b/src/output.rs index 920c411..c4550b5 100644 --- a/src/output.rs +++ b/src/output.rs @@ -36,6 +36,11 @@ pub struct Member { /// /// The `rename` attribute is only set if `Container::rename` is called. pub serde_annot: Vec, + /// Additional field level annotations + /// + /// This is currently used by optional builders. + pub extra_annot: Vec, + /// Documentation properties extracted from the property pub docs: Option, } @@ -77,6 +82,18 @@ impl Container { } } } + + /// Add builder annotations + pub fn builder_fields(&mut self) { + for m in &mut self.members { + if m.type_.starts_with("Option<") { + m.extra_annot + .push("#[builder(default, setter(strip_option))]".to_string()); + } else if m.type_.starts_with("Vec<") || m.type_.starts_with("BTreeMap<") { + m.extra_annot.push("#[builder(default)]".to_string()); + } + } + } } impl Output { @@ -84,9 +101,24 @@ impl Output { /// /// Converts [*].members[*].name to snake_case for structs, PascalCase for enums, /// and adds a serde(rename = "orig_name") annotation to `serde_annot`. - pub fn rename(mut self) -> Self { - for c in &mut self.0 { - c.rename() + pub fn rename(mut self, rust_case: bool) -> Self { + if rust_case { + for c in &mut self.0 { + c.rename() + } + } + self + } + + /// Add builders to all output members + /// + /// Adds #[builder(default, setter(strip_option))] to all option types. + /// Adds #[builder(default)] to required vec and btreemaps. + pub fn builder_fields(mut self, builders: bool) -> Self { + if builders { + for c in &mut self.0 { + c.builder_fields() + } } self }