Commit 1a80f9df authored by Nugine's avatar Nugine
Browse files

feat(codegen/minio): RuleFilter cachedTags

parent ecd2e65c
Loading
Loading
Loading
Loading
+8 −2
Original line number Diff line number Diff line
@@ -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;
                    }

@@ -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())");
+62 −13
Original line number Diff line number Diff line
@@ -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()) {
@@ -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)
                    };

@@ -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) {
@@ -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);

@@ -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;
        }
@@ -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;
        }
@@ -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 {
@@ -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 {" //
+148 −2
Original line number Diff line number Diff line
use codegen_writer::g;

use super::smithy;

fn git_branch() -> String {
@@ -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;
    }

@@ -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}");
}
+10 −1
Original line number Diff line number Diff line
@@ -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()];
@@ -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);
+4 −0
Original line number Diff line number Diff line
@@ -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