From acc796e772666f7d7e123969869f950b57351686 Mon Sep 17 00:00:00 2001 From: Matthew Zeitlin <37011898+mzeitlin11@users.noreply.github.com> Date: Tue, 26 Dec 2023 14:24:59 -0500 Subject: [PATCH] Clean update some codegen, start adding back dedup --- openapi/src/codegen.rs | 13 +- openapi/src/components.rs | 29 +++-- openapi/src/deduplication.rs | 169 +++++++++++++++++++++++++ openapi/src/lib.rs | 1 + openapi/src/object_writing.rs | 52 ++------ openapi/src/overrides.rs | 16 ++- openapi/src/requests.rs | 14 +- openapi/src/rust_object.rs | 31 ++++- openapi/src/rust_type.rs | 16 +++ openapi/src/spec_inference.rs | 13 +- openapi/src/stripe_object.rs | 8 +- openapi/src/templates/derives.rs | 18 +-- openapi/src/templates/enums.rs | 19 +-- openapi/src/templates/object_writer.rs | 21 ++- openapi/src/templates/structs.rs | 11 +- openapi/src/templates/utils.rs | 2 +- openapi/src/webhook.rs | 6 +- stripe_webhook/src/generated/mod.rs | 2 +- 18 files changed, 309 insertions(+), 132 deletions(-) create mode 100644 openapi/src/deduplication.rs diff --git a/openapi/src/codegen.rs b/openapi/src/codegen.rs index 545a03d5c..0a7d721ff 100644 --- a/openapi/src/codegen.rs +++ b/openapi/src/codegen.rs @@ -7,8 +7,8 @@ use indoc::formatdoc; use crate::components::{get_components, Components}; use crate::crate_table::write_crate_table; use crate::crates::{get_crate_doc_comment, Crate, ALL_CRATES}; -use crate::object_writing::{gen_obj, gen_requests, ObjectGenInfo}; -use crate::rust_object::ObjectMetadata; +use crate::object_writing::{gen_obj, gen_requests}; +use crate::rust_object::{ObjectKind, ObjectMetadata}; use crate::spec::Spec; use crate::spec_inference::infer_doc_comment; use crate::stripe_object::StripeObject; @@ -63,11 +63,7 @@ impl CodeGen { let crate_mod_path = crate_path.join("mod.rs"); for (ident, typ_info) in &self.components.extra_types { let mut out = String::new(); - let mut metadata = ObjectMetadata::new(ident.clone(), typ_info.gen_info); - if let Some(doc) = &typ_info.doc { - metadata = metadata.doc(doc.clone()); - } - self.components.write_object(&typ_info.obj, &metadata, &mut out); + self.components.write_object(&typ_info.obj, &typ_info.metadata, &mut out); write_to_file(out, crate_path.join(format!("{}.rs", typ_info.mod_path)))?; append_to_file( format!("pub mod {0}; pub use {0}::{1};", typ_info.mod_path, ident), @@ -198,8 +194,7 @@ impl CodeGen { let base_obj = comp.rust_obj(); let schema = self.spec.get_component_schema(comp.path()); let doc_comment = infer_doc_comment(schema, comp.stripe_doc_url.as_deref()); - let meta = - ObjectMetadata::new(comp.ident().clone(), ObjectGenInfo::new_deser()).doc(doc_comment); + let meta = ObjectMetadata::new(comp.ident().clone(), ObjectKind::Type).doc(doc_comment); gen_obj(base_obj, &meta, comp, &self.components) } diff --git a/openapi/src/components.rs b/openapi/src/components.rs index 3209d4580..b3369a5fb 100644 --- a/openapi/src/components.rs +++ b/openapi/src/components.rs @@ -8,11 +8,10 @@ use tracing::{debug, info}; use crate::crate_inference::validate_crate_info; use crate::crates::{infer_crate_by_package, maybe_infer_crate_by_path, Crate}; -use crate::object_writing::ObjectGenInfo; use crate::overrides::Overrides; use crate::printable::{PrintableContainer, PrintableType}; use crate::requests::parse_requests; -use crate::rust_object::RustObject; +use crate::rust_object::{ObjectMetadata, RustObject}; use crate::rust_type::{Container, PathToType, RustType}; use crate::spec::Spec; use crate::stripe_object::{ @@ -24,9 +23,8 @@ use crate::visitor::Visit; #[derive(Clone, Debug)] pub struct TypeSpec { - pub doc: Option, - pub gen_info: ObjectGenInfo, pub obj: RustObject, + pub metadata: ObjectMetadata, pub mod_path: String, } @@ -119,6 +117,9 @@ impl Components { ident: ident.clone(), } } + RustType::Path { path: PathToType::Dedupped { .. }, is_ref: _ } => { + todo!() + } RustType::Simple(typ) => PrintableType::Simple(*typ), RustType::Container(typ) => { let inner = Box::new(self.construct_printable_type(typ.value_typ())); @@ -189,6 +190,15 @@ impl Components { None } + // fn run_deduplication_pass(&mut self) { + // for comp in self.components.values_mut() { + // let extra_typs = deduplicate_types(comp); + // for (ident, typ) in extra_typs { + // comp.deduplicated_objects.insert(ident, typ); + // } + // } + // } + #[tracing::instrument(level = "debug", skip(self))] fn apply_overrides(&mut self) -> anyhow::Result<()> { let mut overrides = Overrides::new(self)?; @@ -197,12 +207,11 @@ impl Components { } for (obj, override_meta) in overrides.overrides { self.extra_types.insert( - override_meta.ident, + override_meta.metadata.ident.clone(), TypeSpec { - doc: Some(override_meta.doc), - mod_path: override_meta.mod_path, - gen_info: ObjectGenInfo::new_deser(), obj, + metadata: override_meta.metadata, + mod_path: override_meta.mod_path, }, ); } @@ -270,6 +279,7 @@ pub fn get_components(spec: &Spec) -> anyhow::Result { data, krate: inferred_krate.map(CrateInfo::new), stripe_doc_url: None, + deduplicated_objects: IndexMap::default(), }, ); } @@ -284,6 +294,9 @@ pub fn get_components(spec: &Spec) -> anyhow::Result { components.apply_overrides()?; debug!("Finished applying overrides"); + // components.run_deduplication_pass(); + // info!("Finished deduplication pass"); + Ok(components) } diff --git a/openapi/src/deduplication.rs b/openapi/src/deduplication.rs new file mode 100644 index 000000000..5fa4bb38e --- /dev/null +++ b/openapi/src/deduplication.rs @@ -0,0 +1,169 @@ +use heck::ToLowerCamelCase; +use indexmap::map::Entry; +use indexmap::IndexMap; +use tracing::debug; + +use crate::components::TypeSpec; +use crate::rust_object::{ObjectMetadata, RustObject}; +use crate::rust_type::{PathToType, RustType}; +use crate::stripe_object::StripeObject; +use crate::types::{ComponentPath, RustIdent}; +use crate::visitor::{Visit, VisitMut}; + +#[derive(Debug, Default)] +struct CollectDuplicateObjects { + objs: IndexMap>, +} + +impl Visit<'_> for CollectDuplicateObjects { + fn visit_obj(&mut self, obj: &RustObject, meta: Option<&ObjectMetadata>) { + if let Some(meta) = meta { + match self.objs.entry(obj.clone()) { + Entry::Occupied(mut occ) => { + occ.get_mut().push(meta.clone()); + } + Entry::Vacant(entry) => { + entry.insert(vec![meta.clone()]); + } + } + }; + obj.visit(self); + } +} + +#[derive(Debug)] +struct DeduppedObjectInfo { + ident: RustIdent, + gen_info: ObjectGenInfo, +} + +#[derive(Debug)] +struct DeduplicateObjects { + objs: IndexMap, + component_path: ComponentPath, +} + +impl DeduplicateObjects { + pub fn new(path: ComponentPath) -> Self { + Self { objs: Default::default(), component_path: path } + } +} + +impl VisitMut for DeduplicateObjects { + fn visit_typ_mut(&mut self, typ: &mut RustType) + where + Self: Sized, + { + if let Some((obj, _)) = typ.as_object_mut() { + if let Some(dedup_spec) = self.objs.get(obj) { + *typ = RustType::Path { + path: PathToType::Dedupped { + path: self.component_path.clone(), + ident: dedup_spec.ident.clone(), + }, + is_ref: false, + } + } + } + typ.visit_mut(self); + } +} + +fn doc_implied_name(doc: &str) -> Option<&str> { + let mut words = doc.split_ascii_whitespace(); + if words.next() != Some("The") { + return None; + } + let second_word = words.next(); + if words.next() != Some("of") { + return None; + } + second_word +} + +/// Try to infer an identifier given metadata about identical objects +fn infer_ident_for_duplicate_objects(meta: &[ObjectMetadata]) -> Option { + let first = meta.first().unwrap(); + if let Some(title) = &first.title { + // `param` is used very generally and will not be a helpful name to infer + if title != "param" && meta.iter().all(|m| m.title.as_ref() == Some(title)) { + return Some(RustIdent::create(title)); + } + } + if let Some(field) = &first.field_name { + if meta.iter().all(|m| m.field_name.as_ref() == Some(field)) { + return Some(RustIdent::create(field)); + } + } + + if let Some(desc) = &first.doc { + if let Some(doc_name) = doc_implied_name(desc) { + if meta + .iter() + .all(|m| m.doc.as_ref().and_then(|m| doc_implied_name(m)) == Some(doc_name)) + { + return Some(RustIdent::create(doc_name)); + } + } + } + None +} + +#[tracing::instrument(level = "debug", skip(comp), fields(path = %comp.path()))] +pub fn deduplicate_types(comp: &mut StripeObject) -> IndexMap { + let mut objs = IndexMap::new(); + let comp_path = comp.path().clone(); + + // We run deduplication passes until there are no further changes since one round + // of deduplicating can enable another + loop { + let mut collector = CollectDuplicateObjects::default(); + comp.visit(&mut collector); + + let mut dedupper = DeduplicateObjects::new(comp_path.clone()); + for (obj, meta) in collector.objs { + // Nothing to deduplicate + if meta.len() < 2 { + continue; + } + if let Some(inferred) = infer_ident_for_duplicate_objects(&meta) { + // Don't add another deduplicated type with the same name as an existing one + if dedupper.objs.values().all(|o| o.ident != inferred) + && !objs.contains_key(&inferred) + { + // Make sure we respect all requirements, e.g. if one item to generate required `Deserialize`, + // make sure not to forget that when deduplicating + let gen_info = meta.iter().fold(ObjectGenInfo::new(), |prev_meta, meta| { + prev_meta.with_shared_requirements(&meta.gen_info) + }); + + dedupper + .objs + .insert(obj.clone(), DeduppedObjectInfo { ident: inferred, gen_info }); + } + } + } + // If we weren't able to deduplicate anything new, we're done + if dedupper.objs.is_empty() { + break; + } + + comp.visit_mut(&mut dedupper); + for (obj, info) in dedupper.objs { + let mod_path = info.ident.to_lower_camel_case(); + if objs + .insert( + info.ident.clone(), + TypeSpec { doc: None, gen_info: info.gen_info, obj, mod_path }, + ) + .is_some() + { + panic!("Tried to add duplicate ident {}", info.ident); + } + } + } + if !objs.is_empty() { + debug!("Deduplicated {} types", objs.len()); + } + objs +} diff --git a/openapi/src/lib.rs b/openapi/src/lib.rs index 8ed9256bf..149521d82 100644 --- a/openapi/src/lib.rs +++ b/openapi/src/lib.rs @@ -3,6 +3,7 @@ mod components; mod crate_inference; mod crate_table; pub mod crates; +// mod deduplication; mod graph; mod ids; mod object_writing; diff --git a/openapi/src/object_writing.rs b/openapi/src/object_writing.rs index cf48db0ca..eb04e75e2 100644 --- a/openapi/src/object_writing.rs +++ b/openapi/src/object_writing.rs @@ -1,4 +1,4 @@ -use std::fmt::{Debug, Write}; +use std::fmt::Write; use crate::components::Components; use crate::ids::write_object_id; @@ -6,44 +6,12 @@ use crate::printable::Lifetime; use crate::rust_object::{as_enum_of_objects, ObjectMetadata, RustObject}; use crate::rust_type::RustType; use crate::stripe_object::{RequestSpec, StripeObject}; -use crate::templates::derives::Derives; use crate::templates::object_trait::{write_object_trait, write_object_trait_for_enum}; use crate::templates::utils::write_doc_comment; use crate::templates::ObjectWriter; const ADD_UNKNOWN_VARIANT_THRESHOLD: usize = 12; -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub struct ObjectGenInfo { - pub derives: Derives, - pub include_constructor: bool, -} - -impl ObjectGenInfo { - pub const fn new() -> Self { - Self { derives: Derives::new(), include_constructor: false } - } - - pub fn new_deser() -> Self { - Self::new().deserialize(true).serialize(true) - } - - pub fn serialize(mut self, serialize: bool) -> Self { - self.derives.serialize(serialize); - self - } - - pub fn deserialize(mut self, deserialize: bool) -> Self { - self.derives.deserialize(deserialize); - self - } - - pub fn include_constructor(mut self) -> Self { - self.include_constructor = true; - self - } -} - impl Components { fn write_rust_type_objs(&self, typ: &RustType, out: &mut String) { let Some((obj, meta)) = typ.extract_object() else { @@ -53,24 +21,24 @@ impl Components { } pub fn write_object(&self, obj: &RustObject, metadata: &ObjectMetadata, out: &mut String) { - let info = metadata.gen_info; + if let Some(doc) = &metadata.doc { + let comment = write_doc_comment(doc, 0); + let _ = write!(out, "{comment}"); + } // If the object contains any references, we'll need to print with a lifetime let has_ref = obj.has_reference(self); let lifetime = if has_ref { Some(Lifetime::new()) } else { None }; let ident = &metadata.ident; - if let Some(doc) = &metadata.doc { - let comment = write_doc_comment(doc, 0); - let _ = write!(out, "{comment}"); - } - let mut writer = ObjectWriter::new(self, ident); - writer.lifetime(lifetime).derives(info.derives).derives_mut().copy(obj.is_copy(self)); + let mut writer = ObjectWriter::new(self, ident, metadata.kind); + writer.lifetime(lifetime).derive_copy(obj.is_copy(self)); + match obj { RustObject::Struct(fields) => { let should_derive_default = fields.iter().all(|field| field.rust_type.is_option()); - writer.derives_mut().default(should_derive_default); - writer.write_struct(out, fields, info.include_constructor); + writer.derive_default(should_derive_default); + writer.write_struct(out, fields); for field in fields { if let Some((obj, meta)) = field.rust_type.extract_object() { diff --git a/openapi/src/overrides.rs b/openapi/src/overrides.rs index 8ee6c6824..8964e3c93 100644 --- a/openapi/src/overrides.rs +++ b/openapi/src/overrides.rs @@ -2,7 +2,7 @@ use anyhow::Context; use indexmap::IndexMap; use crate::components::{Components, RequestSource}; -use crate::rust_object::RustObject; +use crate::rust_object::{ObjectKind, ObjectMetadata, RustObject}; use crate::rust_type::{PathToType, RustType}; use crate::stripe_object::OperationType; use crate::types::RustIdent; @@ -11,8 +11,7 @@ use crate::visitor::VisitMut; #[derive(Debug, Clone, Eq, PartialEq)] pub struct OverrideMetadata { - pub ident: RustIdent, - pub doc: String, + pub metadata: ObjectMetadata, pub mod_path: String, } @@ -64,8 +63,13 @@ fn get_override_object( Ok(( obj.clone(), OverrideMetadata { - ident: RustIdent::unchanged(data.ident), - doc: data.doc.to_string(), + metadata: ObjectMetadata { + ident: RustIdent::unchanged(data.ident), + doc: Some(data.doc.to_string()), + title: None, + field_name: None, + kind: ObjectKind::Type, + }, mod_path: data.mod_path.to_string(), }, )) @@ -75,7 +79,7 @@ impl VisitMut for Overrides { fn visit_typ_mut(&mut self, typ: &mut RustType) { if let Some((obj, _)) = typ.as_object_mut() { if let Some(meta) = self.overrides.get(obj) { - *typ = RustType::path(PathToType::Shared(meta.ident.clone()), false); + *typ = RustType::path(PathToType::Shared(meta.metadata.ident.clone()), false); } } typ.visit_mut(self); diff --git a/openapi/src/requests.rs b/openapi/src/requests.rs index 68835e245..7f56e7c31 100644 --- a/openapi/src/requests.rs +++ b/openapi/src/requests.rs @@ -5,8 +5,7 @@ use heck::ToSnakeCase; use openapiv3::{Parameter, ParameterData, ParameterSchemaOrContent, ReferenceOr, Schema}; use tracing::debug; -use crate::object_writing::ObjectGenInfo; -use crate::rust_object::{ObjectMetadata, RustObject}; +use crate::rust_object::{ObjectKind, ObjectMetadata, RustObject}; use crate::rust_type::{RustType, SimpleType}; use crate::spec::{get_ok_response_schema, get_request_form_parameters, Spec}; use crate::spec_inference::Inference; @@ -185,14 +184,13 @@ fn build_request( path_id_map: &HashMap, ) -> anyhow::Result { let return_ident = RustIdent::joined(method_name, "returned"); - let return_type = Inference::new(&return_ident, ObjectGenInfo::new_deser()) + let return_type = Inference::new(&return_ident, ObjectKind::RequestReturned) .required(true) .infer_schema_or_ref_type(req.returned); let params_ident = RustIdent::joined(method_name, parent_ident); - let params_gen_info = ObjectGenInfo::new().include_constructor().serialize(true); let param_inference = - Inference::new(¶ms_ident, params_gen_info).can_borrow(true).required(true); + Inference::new(¶ms_ident, ObjectKind::RequestParam).can_borrow(true).required(true); let param_typ = match &req.params { RequestParams::Form(schema) => schema.map(|s| param_inference.infer_schema_or_ref_type(s)), @@ -214,7 +212,7 @@ fn build_request( } Some(RustType::Object( RustObject::Struct(struct_fields), - ObjectMetadata::new(params_ident.clone(), params_gen_info), + ObjectMetadata::new(params_ident.clone(), ObjectKind::RequestParam), )) } }, @@ -222,7 +220,7 @@ fn build_request( .unwrap_or_else(|| { RustType::Object( RustObject::Struct(vec![]), - ObjectMetadata::new(params_ident.clone(), params_gen_info), + ObjectMetadata::new(params_ident.clone(), ObjectKind::RequestParam), ) }); @@ -233,7 +231,7 @@ fn build_request( let ParameterSchemaOrContent::Schema(schema) = ¶m.format else { bail!("Expected path parameter to follow schema format"); }; - let base_param_typ = Inference::new(¶ms_ident, ObjectGenInfo::new()) + let base_param_typ = Inference::new(¶ms_ident, ObjectKind::RequestParam) .can_borrow(true) .required(param.required) .maybe_description(param.description.as_deref()) diff --git a/openapi/src/rust_object.rs b/openapi/src/rust_object.rs index cb6c246db..d477accb7 100644 --- a/openapi/src/rust_object.rs +++ b/openapi/src/rust_object.rs @@ -3,7 +3,6 @@ use std::fmt::{Debug, Display, Formatter}; use indexmap::IndexMap; use crate::components::Components; -use crate::object_writing::ObjectGenInfo; use crate::rust_type::RustType; use crate::types::{ComponentPath, RustIdent}; use crate::visitor::{Visit, VisitMut}; @@ -30,12 +29,12 @@ pub struct ObjectMetadata { pub title: Option, /// The name of the field in the OpenAPI schema pub field_name: Option, - pub gen_info: ObjectGenInfo, + pub kind: ObjectKind, } impl ObjectMetadata { - pub fn new(ident: RustIdent, gen_info: ObjectGenInfo) -> Self { - Self { ident, doc: None, title: None, field_name: None, gen_info } + pub fn new(ident: RustIdent, kind: ObjectKind) -> Self { + Self { ident, doc: None, title: None, field_name: None, kind } } /// Attach a doc comment. @@ -239,3 +238,27 @@ pub fn as_enum_of_objects<'a>( } Some(object_map) } + +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] +pub enum ObjectKind { + /// Only used as a request parameter + RequestParam, + /// Only returned from a request + RequestReturned, + /// A core type potentially referenced anywhere + Type, +} + +impl ObjectKind { + pub fn is_request_param(self) -> bool { + matches!(self, Self::RequestParam) + } + + pub fn should_impl_serialize(self) -> bool { + true + } + + pub fn should_impl_deserialize(self) -> bool { + matches!(self, Self::RequestReturned | Self::Type) + } +} diff --git a/openapi/src/rust_type.rs b/openapi/src/rust_type.rs index 1ea946604..30ae4bd44 100644 --- a/openapi/src/rust_type.rs +++ b/openapi/src/rust_type.rs @@ -5,6 +5,12 @@ use crate::rust_object::{ObjectMetadata, RustObject}; use crate::types::{ComponentPath, RustIdent}; use crate::visitor::{Visit, VisitMut}; +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum DeduppedLocation { + Request, + Types, +} + /// A path to a type defined elsewhere. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum PathToType { @@ -14,6 +20,10 @@ pub enum PathToType { ObjectId(ComponentPath), /// A type defined in `stripe_shared` Shared(RustIdent), + Dedupped { + path: ComponentPath, + ident: RustIdent, + }, } impl PathToType { @@ -30,6 +40,9 @@ impl PathToType { PathToType::Shared(ident) => { components.get_extra_type(ident).obj.has_reference(components) } + PathToType::Dedupped { .. } => { + todo!() + } } } @@ -44,6 +57,9 @@ impl PathToType { // Always either backed by `String` or `smol_str::SmolStr` PathToType::ObjectId(_) => false, PathToType::Shared(ident) => components.get_extra_type(ident).obj.is_copy(components), + PathToType::Dedupped { .. } => { + todo!() + } } } } diff --git a/openapi/src/spec_inference.rs b/openapi/src/spec_inference.rs index 818d341a7..cd51473f1 100644 --- a/openapi/src/spec_inference.rs +++ b/openapi/src/spec_inference.rs @@ -8,8 +8,9 @@ use openapiv3::{ Type, VariantOrUnknownOrEmpty, }; -use crate::object_writing::ObjectGenInfo; -use crate::rust_object::{EnumVariant, FieldlessVariant, ObjectMetadata, RustObject, StructField}; +use crate::rust_object::{ + EnumVariant, FieldlessVariant, ObjectKind, ObjectMetadata, RustObject, StructField, +}; use crate::rust_type::{ExtType, IntType, RustType, SimpleType}; use crate::spec::{ as_data_array_item, as_object_enum_name, is_enum_with_just_empty_string, ExpansionResources, @@ -25,11 +26,11 @@ pub struct Inference<'a> { description: Option<&'a str>, title: Option<&'a str>, required: bool, - gen_info: ObjectGenInfo, + kind: ObjectKind, } impl<'a> Inference<'a> { - pub fn new(ident: &'a RustIdent, gen_info: ObjectGenInfo) -> Self { + pub fn new(ident: &'a RustIdent, kind: ObjectKind) -> Self { Self { can_borrow: false, field_name: None, @@ -38,7 +39,7 @@ impl<'a> Inference<'a> { curr_ident: ident, id_path: None, title: None, - gen_info, + kind, } } @@ -93,7 +94,7 @@ impl<'a> Inference<'a> { doc: self.description.map(|d| d.to_string()), title: self.title.map(|t| t.to_string()), field_name: self.field_name.map(|t| t.to_string()), - gen_info: self.gen_info, + kind: self.kind, }, ) } diff --git a/openapi/src/stripe_object.rs b/openapi/src/stripe_object.rs index 3f961b062..87836cef9 100644 --- a/openapi/src/stripe_object.rs +++ b/openapi/src/stripe_object.rs @@ -1,13 +1,14 @@ use std::collections::HashMap; use heck::ToSnakeCase; +use indexmap::IndexMap; use lazy_static::lazy_static; use openapiv3::Schema; use serde::{Deserialize, Serialize}; +use crate::components::TypeSpec; use crate::crates::Crate; -use crate::object_writing::ObjectGenInfo; -use crate::rust_object::RustObject; +use crate::rust_object::{ObjectKind, RustObject}; use crate::rust_type::RustType; use crate::spec_inference::Inference; use crate::types::{ComponentPath, RustIdent}; @@ -52,6 +53,7 @@ pub struct StripeObject { pub data: StripeObjectData, pub krate: Option, pub stripe_doc_url: Option, + pub deduplicated_objects: IndexMap, } impl StripeObject { @@ -139,7 +141,7 @@ pub fn parse_stripe_schema_as_rust_object( ) -> StripeObjectData { let not_deleted_path = path.as_not_deleted(); let infer_ctx = - Inference::new(ident, ObjectGenInfo::new_deser()).id_path(¬_deleted_path).required(true); + Inference::new(ident, ObjectKind::Type).id_path(¬_deleted_path).required(true); let typ = infer_ctx.infer_schema_type(schema); let Some((mut rust_obj, _)) = typ.into_object() else { panic!("Unexpected top level schema type for {}", path); diff --git a/openapi/src/templates/derives.rs b/openapi/src/templates/derives.rs index a693c3046..fc837ba84 100644 --- a/openapi/src/templates/derives.rs +++ b/openapi/src/templates/derives.rs @@ -20,33 +20,23 @@ impl Derives { } } - pub fn debug(&mut self, debug: bool) -> &mut Self { + pub fn debug(mut self, debug: bool) -> Self { self.debug = debug; self } - pub fn copy(&mut self, copy: bool) -> &mut Self { + pub fn copy(mut self, copy: bool) -> Self { self.copy = copy; self } - pub fn default(&mut self, default: bool) -> &mut Self { + pub fn default(mut self, default: bool) -> Self { self.default = default; self } - pub fn eq(&mut self, eq: bool) -> &mut Self { + pub fn eq(mut self, eq: bool) -> Self { self.eq = eq; self } - - pub fn serialize(&mut self, serialize: bool) -> &mut Self { - self.serialize = serialize; - self - } - - pub fn deserialize(&mut self, deserialize: bool) -> &mut Self { - self.deserialize = deserialize; - self - } } diff --git a/openapi/src/templates/enums.rs b/openapi/src/templates/enums.rs index c1c503a90..580a1ded4 100644 --- a/openapi/src/templates/enums.rs +++ b/openapi/src/templates/enums.rs @@ -24,7 +24,7 @@ impl<'a> ObjectWriter<'a> { let _ = writeln!(enum_body, "{variant},"); } } - self.write_derives_line(out); + self.write_automatic_derives(out); let lifetime_str = self.lifetime_param(); let _ = writedoc!( out, @@ -65,7 +65,7 @@ impl<'a> ObjectWriter<'a> { let _ = writeln!(enum_body, r"Unknown"); } - self.write_derives_line(out); + self.write_automatic_derives(out); self.write_nonexhaustive_attr(out); let _ = writedoc!( out, @@ -113,17 +113,12 @@ impl<'a> ObjectWriter<'a> { } let _ = writeln!(from_str_body, "_ => Err(())"); - let mut derives = self.derives; - let derive_deser = derives.deserialize; - let derive_serialize = derives.serialize; - - // NB: we unset the derive flags for serialize + deserialize + debug to avoid duplicating - // the (potentially many) strings in `as_str` and `from_str` through the default derive. + // NB: we manually implement Debug, Serialize, Deserialize to avoid duplicating + // the (potentially many) strings in `as_str` and `from_str` used with the default derive. // These derived implementations often show up running `llvm-lines`, so easy // binary size + compile time win by doing this. - derives.copy(true).eq(true).serialize(false).deserialize(false).debug(false); - write_derives_line(out, derives); + write_derives_line(out, self.derives.eq(true).debug(false)); self.write_nonexhaustive_attr(out); let _ = writedoc!( out, @@ -168,7 +163,7 @@ impl<'a> ObjectWriter<'a> { "# ); - if derive_serialize { + if self.obj_kind.should_impl_serialize() { let _ = writedoc!( out, r#" @@ -181,7 +176,7 @@ impl<'a> ObjectWriter<'a> { ); } - if derive_deser { + if self.obj_kind.should_impl_deserialize() { let ret_line = if self.provide_unknown_variant { format!("Ok(Self::from_str(&s).unwrap_or({enum_name}::Unknown))") } else { diff --git a/openapi/src/templates/object_writer.rs b/openapi/src/templates/object_writer.rs index dc5dfe8db..a23621eb2 100644 --- a/openapi/src/templates/object_writer.rs +++ b/openapi/src/templates/object_writer.rs @@ -2,6 +2,7 @@ use std::fmt::Write; use crate::components::Components; use crate::printable::{Lifetime, PrintableType}; +use crate::rust_object::ObjectKind; use crate::rust_type::RustType; use crate::templates::derives::Derives; use crate::types::RustIdent; @@ -13,16 +14,18 @@ pub struct ObjectWriter<'a> { pub lifetime: Option, pub ident: &'a RustIdent, pub provide_unknown_variant: bool, + pub obj_kind: ObjectKind, } impl<'a> ObjectWriter<'a> { - pub fn new(components: &'a Components, ident: &'a RustIdent) -> Self { + pub fn new(components: &'a Components, ident: &'a RustIdent, obj_kind: ObjectKind) -> Self { Self { components, derives: Derives::new(), lifetime: None, ident, provide_unknown_variant: false, + obj_kind, } } @@ -36,13 +39,14 @@ impl<'a> ObjectWriter<'a> { self } - pub fn derives(&mut self, derives: Derives) -> &mut Self { - self.derives = derives; + pub fn derive_copy(&mut self, derive_copy: bool) -> &mut Self { + self.derives = self.derives.copy(derive_copy); self } - pub fn derives_mut(&mut self) -> &mut Derives { - &mut self.derives + pub fn derive_default(&mut self, derive_default: bool) -> &mut Self { + self.derives = self.derives.default(derive_default); + self } pub fn get_printable(&self, typ: &RustType) -> PrintableType { @@ -59,8 +63,11 @@ impl<'a> ObjectWriter<'a> { } } - pub fn write_derives_line(&self, out: &mut String) { - write_derives_line(out, self.derives); + pub fn write_automatic_derives(&self, out: &mut String) { + let mut derives = self.derives; + derives.serialize = self.obj_kind.should_impl_serialize(); + derives.deserialize = self.obj_kind.should_impl_deserialize(); + write_derives_line(out, derives) } } diff --git a/openapi/src/templates/structs.rs b/openapi/src/templates/structs.rs index 010b64fc0..fc6553b74 100644 --- a/openapi/src/templates/structs.rs +++ b/openapi/src/templates/structs.rs @@ -8,12 +8,7 @@ use crate::templates::utils::{write_doc_comment, write_serde_rename}; use crate::templates::ObjectWriter; impl<'a> ObjectWriter<'a> { - pub fn write_struct( - &self, - out: &mut String, - fields: &[StructField], - include_constructor: bool, - ) { + pub fn write_struct(&self, out: &mut String, fields: &[StructField]) { let name = self.ident; let mut fields_str = String::with_capacity(64); @@ -22,7 +17,7 @@ impl<'a> ObjectWriter<'a> { } let lifetime_str = self.lifetime_param(); - self.write_derives_line(out); + self.write_automatic_derives(out); let _ = writedoc!( out, r" @@ -32,7 +27,7 @@ impl<'a> ObjectWriter<'a> { " ); - if include_constructor { + if self.obj_kind.is_request_param() { let cons_body = if self.derives.default { r" pub fn new() -> Self { diff --git a/openapi/src/templates/utils.rs b/openapi/src/templates/utils.rs index 98fb4406b..de1d2e706 100644 --- a/openapi/src/templates/utils.rs +++ b/openapi/src/templates/utils.rs @@ -20,7 +20,7 @@ pub fn write_doc_comment(description: &str, depth: u8) -> String { if description.trim().is_empty() { return String::new(); } - let mut out = String::with_capacity(32); + let mut out = String::with_capacity(description.len()); let doc = format_doc_comment(description); for (i, line) in doc.split('\n').enumerate() { diff --git a/openapi/src/webhook.rs b/openapi/src/webhook.rs index 4ac3458be..a4b3f7571 100644 --- a/openapi/src/webhook.rs +++ b/openapi/src/webhook.rs @@ -4,7 +4,7 @@ use indexmap::IndexMap; use crate::components::Components; use crate::crates::Crate; -use crate::rust_object::ObjectRef; +use crate::rust_object::{ObjectKind, ObjectRef}; use crate::templates::ObjectWriter; use crate::types::{ComponentPath, RustIdent}; use crate::utils::append_to_file; @@ -84,8 +84,8 @@ fn write_event_object(components: &Components, out_path: &Path) -> anyhow::Resul } let mut out = String::new(); let ident = RustIdent::unchanged("EventObject"); - let mut writer = ObjectWriter::new(components, &ident); - writer.provide_unknown_variant(true).derives_mut().deserialize(true); + let mut writer = ObjectWriter::new(components, &ident, ObjectKind::Type); + writer.provide_unknown_variant(true); writer.write_enum_of_objects(&mut out, components, &objects); append_to_file(out, out_path.join("mod.rs"))?; Ok(()) diff --git a/stripe_webhook/src/generated/mod.rs b/stripe_webhook/src/generated/mod.rs index 155051d55..abb5a7016 100644 --- a/stripe_webhook/src/generated/mod.rs +++ b/stripe_webhook/src/generated/mod.rs @@ -1,4 +1,4 @@ -#[derive(Clone, Debug, serde::Deserialize)] +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] #[non_exhaustive] #[serde(tag = "object")] pub enum EventObject {