Loading codegen/src/v1/aws_conv.rs +8 −2 Original line number Diff line number Diff line Loading @@ -79,7 +79,11 @@ pub fn codegen(ops: &Operations, rust_types: &RustTypes) { }; if field.is_custom_extension { if field.position == "sealed" { g!("{s3s_field_name}: s3s::dto::{}::default(),", field.type_); } else { g!("{s3s_field_name}: None,"); } continue; } Loading Loading @@ -192,7 +196,9 @@ pub fn codegen(ops: &Operations, rust_types: &RustTypes) { if has_unconditional_builder(&ty.name) { g!("Ok(y.build())"); } else if is_op_input(&ty.name, ops) || ty.fields.iter().any(|field| field.is_required) { } else if is_op_input(&ty.name, ops) || ty.fields.iter().any(|field| field.is_required && field.position != "sealed") { g!("y.build().map_err(S3Error::internal_error)"); } else { g!("Ok(y.build())"); Loading codegen/src/v1/dto.rs +62 −13 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ pub fn collect_rust_types(model: &smithy::Model, ops: &Operations) -> RustTypes "Range", // "ContentType", // "Event", // "CachedTags", // ]; if provided_types.contains(&rs_shape_name.as_str()) { Loading Loading @@ -183,6 +184,10 @@ pub fn collect_rust_types(model: &smithy::Model, ops: &Operations) -> RustTypes position = "metadata"; } if field.traits.sealed() { position = "sealed"; } o(position) }; Loading Loading @@ -419,6 +424,8 @@ pub fn codegen(rust_types: &RustTypes, ops: &Operations) { codegen_builders(rust_types, ops); codegen_dto_ext(rust_types); super::minio::codegen_in_dto(); } fn codegen_struct(ty: &rust::Struct, rust_types: &RustTypes, ops: &Operations) { Loading Loading @@ -462,6 +469,37 @@ fn codegen_struct(ty: &rust::Struct, rust_types: &RustTypes, ops: &Operations) { g!("}}"); g!(); if ty.fields.iter().any(|field| field.position == "sealed") { g!("#[allow(clippy::clone_on_copy)]"); g!("impl Clone for {} {{", ty.name); g!("fn clone(&self) -> Self {{"); g!("Self {{"); for field in &ty.fields { if field.position == "sealed" { g!("{}: default(),", field.name); } else { g!("{}: self.{}.clone(),", field.name, field.name); } } g!("}}"); g!("}}"); g!("}}"); g!("impl PartialEq for {} {{", ty.name); g!("fn eq(&self, other: &Self) -> bool {{"); for field in &ty.fields { if field.position == "sealed" { continue; } g!("if self.{} != other.{} {{", field.name, field.name); g!("return false;"); g!("}}"); } g!("true"); g!("}}"); g!("}}"); } if is_op_input(&ty.name, ops) { g!("impl {} {{", ty.name); Loading Loading @@ -581,6 +619,9 @@ fn struct_derives(ty: &rust::Struct, rust_types: &RustTypes) -> Vec<&'static str fn can_derive_clone(ty: &rust::Struct, _rust_types: &RustTypes) -> bool { ty.fields.iter().all(|field| { if field.position == "sealed" { return false; } if matches!(field.type_.as_str(), "StreamingBlob" | "SelectObjectContentEventStream") { return false; } Loading @@ -590,6 +631,9 @@ fn can_derive_clone(ty: &rust::Struct, _rust_types: &RustTypes) -> bool { fn can_derive_partial_eq(ty: &rust::Struct, _rust_types: &RustTypes) -> bool { ty.fields.iter().all(|field| { if field.position == "sealed" { return false; } if matches!(field.type_.as_str(), "StreamingBlob" | "SelectObjectContentEventStream") { return false; } Loading @@ -598,7 +642,24 @@ fn can_derive_partial_eq(ty: &rust::Struct, _rust_types: &RustTypes) -> bool { } fn can_derive_default(ty: &rust::Struct, rust_types: &RustTypes) -> bool { ty.fields.iter().all(|field| is_default_field(field, rust_types)) ty.fields.iter().all(|field| { if field.option_type { return true; } match &rust_types[&field.type_] { rust::Type::Provided(ty) => { if ty.name == "CachedTags" { return true; } } rust::Type::List(_) => return true, rust::Type::Map(_) => return true, _ => {} } field.default_value.as_ref().is_some_and(is_rust_default) }) } fn is_rust_default(v: &Value) -> bool { Loading @@ -610,18 +671,6 @@ fn is_rust_default(v: &Value) -> bool { } } fn is_default_field(field: &rust::StructField, rust_types: &RustTypes) -> bool { if field.option_type { return true; } if matches!(&rust_types[&field.type_], rust::Type::List(_)) { return true; } field.default_value.as_ref().is_some_and(is_rust_default) } fn codegen_builders(rust_types: &RustTypes, ops: &Operations) { glines!( "pub mod builders {" // Loading codegen/src/v1/minio.rs +148 −2 Original line number Diff line number Diff line use codegen_writer::g; use super::smithy; fn git_branch() -> String { Loading @@ -9,10 +11,14 @@ fn git_branch() -> String { stdout.trim().to_owned() } fn is_minio_branch() -> bool { let branch_name = git_branch(); matches!(branch_name.as_str(), "minio" | "feat/minio") } /// <https://github.com/Nugine/s3s/issues/192> pub fn patch(model: &mut smithy::Model) { let branch_name = git_branch(); if !matches!(branch_name.as_str(), "minio" | "feat/minio") { if !is_minio_branch() { return; } Loading @@ -35,3 +41,143 @@ pub fn patch(model: &mut smithy::Model) { } } } #[allow(clippy::too_many_lines)] pub fn codegen_in_dto() { if !is_minio_branch() { return; } let code = r#" #[derive(Debug, Default)] pub struct CachedTags(std::sync::OnceLock<Map<ObjectKey, Value>>); impl CachedTags { pub fn reset(&mut self) { self.0.take(); } fn test<'a>( &self, get_and_tags: impl FnOnce() -> Option<&'a [Tag]>, get_tag: impl FnOnce() -> Option<&'a Tag>, object_tags: &Map<String, String>, ) -> bool { let cached_tags = self.0.get_or_init(|| { let mut map = Map::new(); if let Some(tags) = get_and_tags() { for tag in tags { let (Some(k), Some(v)) = (&tag.key, &tag.value) else {continue}; if !k.is_empty() { map.insert(k.clone(), v.clone()); } } } if let Some(tag) = get_tag() { let (Some(k), Some(v)) = (&tag.key, &tag.value) else { return map }; if !k.is_empty() { map.insert(k.clone(), v.clone()); } } map }); if cached_tags.is_empty() { return true; } if object_tags.is_empty() { return false; } let (mut lhs, mut rhs) = (cached_tags, object_tags); if lhs.len() > rhs.len() { std::mem::swap(&mut lhs, &mut rhs); } for (k, v) in lhs { if rhs.get(k) == Some(v) { return true; } } false } } impl super::LifecycleRuleFilter { pub fn test_tags(&self, object_tags: &Map<String, String>) -> bool { self.cached_tags.test( || self.and.as_ref().and_then(|and| and.tags.as_deref()), || self.tag.as_ref(), object_tags, ) } } impl super::ReplicationRuleFilter { pub fn test_tags(&self, object_tags: &Map<String, String>) -> bool { self.cached_tags.test( || self.and.as_ref().and_then(|and| and.tags.as_deref()), || self.tag.as_ref(), object_tags, ) } } #[cfg(test)] mod minio_tests { use super::*; use std::ops::Not; #[test] fn cached_tags() { let filter = ReplicationRuleFilter { and: Some(ReplicationRuleAndOperator { tags: Some(vec![ Tag { key: Some("key1".to_owned()), value: Some("value1".to_owned()), }, Tag { key: Some("key2".to_owned()), value: Some("value2".to_owned()), }, ]), ..default() }), tag: Some(Tag { key: Some("key3".to_owned()), value: Some("value3".to_owned()), }), ..default() }; let object_tags = Map::from_iter(vec![ ("key1".to_owned(), "value1".to_owned()), ("key4".to_owned(), "value4".to_owned()), ("key5".to_owned(), "value5".to_owned()), ]); assert!(filter.test_tags(&object_tags)); assert!(filter.test_tags(&object_tags)); assert!(filter.test_tags(&object_tags)); let object_tags = Map::from_iter(vec![ ("key4".to_owned(), "value4".to_owned()), ("key5".to_owned(), "value5".to_owned()), ]); assert!(filter.test_tags(&object_tags).not()); assert!(filter.test_tags(&object_tags).not()); assert!(filter.test_tags(&object_tags).not()); } } "#; g!("{code}"); } codegen/src/v1/xml.rs +10 −1 Original line number Diff line number Diff line Loading @@ -376,12 +376,19 @@ fn codegen_xml_serde_content_struct(_ops: &Operations, rust_types: &RustTypes, t ); for field in &ty.fields { if field.position == "sealed" { continue; } g!("let mut {}: Option<{}> = None;", field.name, field.type_); } if ty.fields.is_empty().not() { g!("d.for_each_element(|d, x| match x {{"); for field in &ty.fields { if field.position == "sealed" { continue; } let xml_name = field.xml_name.as_ref().unwrap_or(&field.camel_name); let field_name = field.name.as_str(); let field_type = &rust_types[field.type_.as_str()]; Loading Loading @@ -424,7 +431,9 @@ fn codegen_xml_serde_content_struct(_ops: &Operations, rust_types: &RustTypes, t continue; } if field.option_type { if field.position == "sealed" { g!("{}: {}::default(),", field.name, field.type_); } else if field.option_type { g!("{},", field.name); } else { // g!("{0}: {0}.ok_or_else(||dbg!(DeError::MissingField))?,", field.name); Loading codegen/src/v2/smithy.rs +4 −0 Original line number Diff line number Diff line Loading @@ -258,4 +258,8 @@ impl Traits { pub fn minio(&self) -> bool { self.get("s3s#minio").is_some() } pub fn sealed(&self) -> bool { self.get("s3s#sealed").is_some() } } Loading
codegen/src/v1/aws_conv.rs +8 −2 Original line number Diff line number Diff line Loading @@ -79,7 +79,11 @@ pub fn codegen(ops: &Operations, rust_types: &RustTypes) { }; if field.is_custom_extension { if field.position == "sealed" { g!("{s3s_field_name}: s3s::dto::{}::default(),", field.type_); } else { g!("{s3s_field_name}: None,"); } continue; } Loading Loading @@ -192,7 +196,9 @@ pub fn codegen(ops: &Operations, rust_types: &RustTypes) { if has_unconditional_builder(&ty.name) { g!("Ok(y.build())"); } else if is_op_input(&ty.name, ops) || ty.fields.iter().any(|field| field.is_required) { } else if is_op_input(&ty.name, ops) || ty.fields.iter().any(|field| field.is_required && field.position != "sealed") { g!("y.build().map_err(S3Error::internal_error)"); } else { g!("Ok(y.build())"); Loading
codegen/src/v1/dto.rs +62 −13 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ pub fn collect_rust_types(model: &smithy::Model, ops: &Operations) -> RustTypes "Range", // "ContentType", // "Event", // "CachedTags", // ]; if provided_types.contains(&rs_shape_name.as_str()) { Loading Loading @@ -183,6 +184,10 @@ pub fn collect_rust_types(model: &smithy::Model, ops: &Operations) -> RustTypes position = "metadata"; } if field.traits.sealed() { position = "sealed"; } o(position) }; Loading Loading @@ -419,6 +424,8 @@ pub fn codegen(rust_types: &RustTypes, ops: &Operations) { codegen_builders(rust_types, ops); codegen_dto_ext(rust_types); super::minio::codegen_in_dto(); } fn codegen_struct(ty: &rust::Struct, rust_types: &RustTypes, ops: &Operations) { Loading Loading @@ -462,6 +469,37 @@ fn codegen_struct(ty: &rust::Struct, rust_types: &RustTypes, ops: &Operations) { g!("}}"); g!(); if ty.fields.iter().any(|field| field.position == "sealed") { g!("#[allow(clippy::clone_on_copy)]"); g!("impl Clone for {} {{", ty.name); g!("fn clone(&self) -> Self {{"); g!("Self {{"); for field in &ty.fields { if field.position == "sealed" { g!("{}: default(),", field.name); } else { g!("{}: self.{}.clone(),", field.name, field.name); } } g!("}}"); g!("}}"); g!("}}"); g!("impl PartialEq for {} {{", ty.name); g!("fn eq(&self, other: &Self) -> bool {{"); for field in &ty.fields { if field.position == "sealed" { continue; } g!("if self.{} != other.{} {{", field.name, field.name); g!("return false;"); g!("}}"); } g!("true"); g!("}}"); g!("}}"); } if is_op_input(&ty.name, ops) { g!("impl {} {{", ty.name); Loading Loading @@ -581,6 +619,9 @@ fn struct_derives(ty: &rust::Struct, rust_types: &RustTypes) -> Vec<&'static str fn can_derive_clone(ty: &rust::Struct, _rust_types: &RustTypes) -> bool { ty.fields.iter().all(|field| { if field.position == "sealed" { return false; } if matches!(field.type_.as_str(), "StreamingBlob" | "SelectObjectContentEventStream") { return false; } Loading @@ -590,6 +631,9 @@ fn can_derive_clone(ty: &rust::Struct, _rust_types: &RustTypes) -> bool { fn can_derive_partial_eq(ty: &rust::Struct, _rust_types: &RustTypes) -> bool { ty.fields.iter().all(|field| { if field.position == "sealed" { return false; } if matches!(field.type_.as_str(), "StreamingBlob" | "SelectObjectContentEventStream") { return false; } Loading @@ -598,7 +642,24 @@ fn can_derive_partial_eq(ty: &rust::Struct, _rust_types: &RustTypes) -> bool { } fn can_derive_default(ty: &rust::Struct, rust_types: &RustTypes) -> bool { ty.fields.iter().all(|field| is_default_field(field, rust_types)) ty.fields.iter().all(|field| { if field.option_type { return true; } match &rust_types[&field.type_] { rust::Type::Provided(ty) => { if ty.name == "CachedTags" { return true; } } rust::Type::List(_) => return true, rust::Type::Map(_) => return true, _ => {} } field.default_value.as_ref().is_some_and(is_rust_default) }) } fn is_rust_default(v: &Value) -> bool { Loading @@ -610,18 +671,6 @@ fn is_rust_default(v: &Value) -> bool { } } fn is_default_field(field: &rust::StructField, rust_types: &RustTypes) -> bool { if field.option_type { return true; } if matches!(&rust_types[&field.type_], rust::Type::List(_)) { return true; } field.default_value.as_ref().is_some_and(is_rust_default) } fn codegen_builders(rust_types: &RustTypes, ops: &Operations) { glines!( "pub mod builders {" // Loading
codegen/src/v1/minio.rs +148 −2 Original line number Diff line number Diff line use codegen_writer::g; use super::smithy; fn git_branch() -> String { Loading @@ -9,10 +11,14 @@ fn git_branch() -> String { stdout.trim().to_owned() } fn is_minio_branch() -> bool { let branch_name = git_branch(); matches!(branch_name.as_str(), "minio" | "feat/minio") } /// <https://github.com/Nugine/s3s/issues/192> pub fn patch(model: &mut smithy::Model) { let branch_name = git_branch(); if !matches!(branch_name.as_str(), "minio" | "feat/minio") { if !is_minio_branch() { return; } Loading @@ -35,3 +41,143 @@ pub fn patch(model: &mut smithy::Model) { } } } #[allow(clippy::too_many_lines)] pub fn codegen_in_dto() { if !is_minio_branch() { return; } let code = r#" #[derive(Debug, Default)] pub struct CachedTags(std::sync::OnceLock<Map<ObjectKey, Value>>); impl CachedTags { pub fn reset(&mut self) { self.0.take(); } fn test<'a>( &self, get_and_tags: impl FnOnce() -> Option<&'a [Tag]>, get_tag: impl FnOnce() -> Option<&'a Tag>, object_tags: &Map<String, String>, ) -> bool { let cached_tags = self.0.get_or_init(|| { let mut map = Map::new(); if let Some(tags) = get_and_tags() { for tag in tags { let (Some(k), Some(v)) = (&tag.key, &tag.value) else {continue}; if !k.is_empty() { map.insert(k.clone(), v.clone()); } } } if let Some(tag) = get_tag() { let (Some(k), Some(v)) = (&tag.key, &tag.value) else { return map }; if !k.is_empty() { map.insert(k.clone(), v.clone()); } } map }); if cached_tags.is_empty() { return true; } if object_tags.is_empty() { return false; } let (mut lhs, mut rhs) = (cached_tags, object_tags); if lhs.len() > rhs.len() { std::mem::swap(&mut lhs, &mut rhs); } for (k, v) in lhs { if rhs.get(k) == Some(v) { return true; } } false } } impl super::LifecycleRuleFilter { pub fn test_tags(&self, object_tags: &Map<String, String>) -> bool { self.cached_tags.test( || self.and.as_ref().and_then(|and| and.tags.as_deref()), || self.tag.as_ref(), object_tags, ) } } impl super::ReplicationRuleFilter { pub fn test_tags(&self, object_tags: &Map<String, String>) -> bool { self.cached_tags.test( || self.and.as_ref().and_then(|and| and.tags.as_deref()), || self.tag.as_ref(), object_tags, ) } } #[cfg(test)] mod minio_tests { use super::*; use std::ops::Not; #[test] fn cached_tags() { let filter = ReplicationRuleFilter { and: Some(ReplicationRuleAndOperator { tags: Some(vec![ Tag { key: Some("key1".to_owned()), value: Some("value1".to_owned()), }, Tag { key: Some("key2".to_owned()), value: Some("value2".to_owned()), }, ]), ..default() }), tag: Some(Tag { key: Some("key3".to_owned()), value: Some("value3".to_owned()), }), ..default() }; let object_tags = Map::from_iter(vec![ ("key1".to_owned(), "value1".to_owned()), ("key4".to_owned(), "value4".to_owned()), ("key5".to_owned(), "value5".to_owned()), ]); assert!(filter.test_tags(&object_tags)); assert!(filter.test_tags(&object_tags)); assert!(filter.test_tags(&object_tags)); let object_tags = Map::from_iter(vec![ ("key4".to_owned(), "value4".to_owned()), ("key5".to_owned(), "value5".to_owned()), ]); assert!(filter.test_tags(&object_tags).not()); assert!(filter.test_tags(&object_tags).not()); assert!(filter.test_tags(&object_tags).not()); } } "#; g!("{code}"); }
codegen/src/v1/xml.rs +10 −1 Original line number Diff line number Diff line Loading @@ -376,12 +376,19 @@ fn codegen_xml_serde_content_struct(_ops: &Operations, rust_types: &RustTypes, t ); for field in &ty.fields { if field.position == "sealed" { continue; } g!("let mut {}: Option<{}> = None;", field.name, field.type_); } if ty.fields.is_empty().not() { g!("d.for_each_element(|d, x| match x {{"); for field in &ty.fields { if field.position == "sealed" { continue; } let xml_name = field.xml_name.as_ref().unwrap_or(&field.camel_name); let field_name = field.name.as_str(); let field_type = &rust_types[field.type_.as_str()]; Loading Loading @@ -424,7 +431,9 @@ fn codegen_xml_serde_content_struct(_ops: &Operations, rust_types: &RustTypes, t continue; } if field.option_type { if field.position == "sealed" { g!("{}: {}::default(),", field.name, field.type_); } else if field.option_type { g!("{},", field.name); } else { // g!("{0}: {0}.ok_or_else(||dbg!(DeError::MissingField))?,", field.name); Loading
codegen/src/v2/smithy.rs +4 −0 Original line number Diff line number Diff line Loading @@ -258,4 +258,8 @@ impl Traits { pub fn minio(&self) -> bool { self.get("s3s#minio").is_some() } pub fn sealed(&self) -> bool { self.get("s3s#sealed").is_some() } }