Skip to main content

rusty_cdk_core/s3/
builder.rs

1use crate::custom_resource::{BucketNotificationBuilder, BUCKET_NOTIFICATION_HANDLER_CODE};
2use crate::iam::{
3    CustomPermission, Effect, Permission, PolicyDocument, PolicyDocumentBuilder, PrincipalBuilder, Statement, StatementBuilder,
4};
5use crate::intrinsic::join;
6use crate::lambda::{Architecture, Runtime};
7use crate::lambda::{Code, FunctionBuilder, FunctionRef, PermissionBuilder};
8use crate::s3::{dto, AccelerateConfiguration, BucketPolicyType, BucketType, IntelligentTieringConfiguration, InventoryTableConfiguration, JournalTableConfiguration, MetadataConfiguration, MetadataDestination, RecordExpiration, TagFilter, Tiering};
9use crate::s3::{
10    Bucket, BucketEncryption, BucketPolicy, BucketPolicyRef, BucketProperties, BucketRef, CorsConfiguration, CorsRule,
11    LifecycleConfiguration, LifecycleRule, LifecycleRuleTransition, NonCurrentVersionTransition, PublicAccessBlockConfiguration,
12    RedirectAllRequestsTo, S3BucketPolicyProperties, ServerSideEncryptionByDefault, ServerSideEncryptionRule, WebsiteConfiguration,
13};
14use crate::shared::{DeletionPolicy, Id, UpdateDeletePolicyDTO, UpdateReplacePolicy, QUEUE_POLICY_ID_SUFFIX, TOPIC_POLICY_ID_SUFFIX};
15use crate::shared::{HttpMethod, Protocol};
16use crate::sns::{TopicPolicyBuilder, TopicRef};
17use crate::sqs::{QueuePolicyBuilder, QueueRef};
18use crate::stack::{Resource, StackBuilder};
19use crate::type_state;
20use crate::wrappers::{
21    BucketName, BucketTiering, IamAction, LambdaPermissionAction, LifecycleTransitionInDays, Memory, PolicyName, RecordExpirationDays,
22    S3LifecycleObjectSizes, Timeout,
23};
24use serde_json::{json, Value};
25use std::marker::PhantomData;
26use std::time::Duration;
27
28/// Builder for S3 bucket policies.
29///
30/// Creates a policy document that controls access to an S3 bucket.
31///
32/// # Example
33///
34/// ```rust,no_run
35/// use serde_json::Value;
36/// use rusty_cdk_core::stack::StackBuilder;
37/// use rusty_cdk_core::s3::BucketPolicyBuilder;
38/// use rusty_cdk_core::iam::{PolicyDocumentBuilder, StatementBuilder, Effect, PrincipalBuilder};
39/// use rusty_cdk_core::wrappers::*;
40/// use rusty_cdk_core::s3::BucketBuilder;
41/// use rusty_cdk_macros::iam_action;
42///
43/// let mut stack_builder = StackBuilder::new();
44/// let bucket = unimplemented!("create a bucket");
45///
46/// let resources = vec![Value::String("*".to_string())];
47/// let statement = StatementBuilder::new(
48///         vec![iam_action!("s3:GetObject")],
49///         Effect::Allow
50///     )
51///     .principal(PrincipalBuilder::new().normal("*").build())
52///     .resources(resources)
53///     .build();
54///
55/// let policy_doc = PolicyDocumentBuilder::new(vec![statement]).build();
56/// let policy = BucketPolicyBuilder::new("bucket-policy", &bucket, policy_doc)
57///     .build(&mut stack_builder);
58/// ```
59/// See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3-bucketpolicy.html
60pub struct BucketPolicyBuilder {
61    id: Id,
62    bucket_name: Value,
63    policy_document: PolicyDocument,
64}
65
66impl BucketPolicyBuilder {
67    /// Creates a new S3 bucket policy builder.
68    ///
69    /// # Arguments
70    /// * `id` - Unique identifier for the bucket policy
71    /// * `bucket` - Reference to the S3 bucket
72    /// * `policy_document` - IAM policy document controlling access
73    pub fn new(id: &str, bucket: &BucketRef, policy_document: PolicyDocument) -> Self {
74        Self {
75            id: Id(id.to_string()),
76            bucket_name: bucket.get_ref(),
77            policy_document,
78        }
79    }
80
81    pub(crate) fn internal_new(id: &str, bucket_name: Value, policy_document: PolicyDocument) -> Self {
82        Self {
83            id: Id(id.to_string()),
84            bucket_name,
85            policy_document,
86        }
87    }
88
89    pub(crate) fn raw_build(self) -> (String, BucketPolicy) {
90        let resource_id = Resource::generate_id("S3BucketPolicy");
91        let policy = BucketPolicy {
92            id: self.id,
93            resource_id: resource_id.to_string(),
94            r#type: BucketPolicyType::BucketPolicyType,
95            properties: S3BucketPolicyProperties {
96                bucket_name: self.bucket_name,
97                policy_document: self.policy_document,
98            },
99        };
100        (resource_id, policy)
101    }
102
103    pub fn build(self, stack_builder: &mut StackBuilder) -> BucketPolicyRef {
104        let (resource_id, policy) = self.raw_build();
105        stack_builder.add_resource(policy);
106        BucketPolicyRef::internal_new(resource_id)
107    }
108}
109
110#[derive(Debug, Clone)]
111pub enum VersioningConfiguration {
112    Enabled,
113    Suspended,
114}
115
116impl From<VersioningConfiguration> for String {
117    fn from(value: VersioningConfiguration) -> Self {
118        match value {
119            VersioningConfiguration::Enabled => "Enabled".to_string(),
120            VersioningConfiguration::Suspended => "Suspended".to_string(),
121        }
122    }
123}
124
125#[derive(Debug, Clone)]
126pub enum Encryption {
127    S3Managed,
128    KmsManaged,
129    DsseManaged,
130    // KMS, => add, this requires creating a kms key and passing it to the bucket
131    // DSSE, => add, similar
132}
133
134impl From<Encryption> for String {
135    fn from(value: Encryption) -> Self {
136        match value {
137            Encryption::S3Managed => "AES256".to_string(),
138            Encryption::KmsManaged => "aws:kms".to_string(),
139            Encryption::DsseManaged => "aws:kms:dsse".to_string(),
140        }
141    }
142}
143
144pub enum NotificationDestination<'a> {
145    Lambda(&'a FunctionRef, NotificationEventType),
146    Sns(&'a TopicRef, NotificationEventType),
147    Sqs(&'a QueueRef, NotificationEventType),
148}
149
150#[derive(Debug, Clone)]
151pub enum NotificationEventType {
152    ObjectCreated,
153    ObjectCreatedPut,
154    ObjectCreatedPost,
155    ObjectCreatedCopy,
156    ObjectCreatedCompleteMultipartUpload,
157    ObjectRemoved,
158    ObjectRemovedDelete,
159    ObjectRemovedDeleteMarkerCreated,
160    ObjectRestorePost,
161    ObjectRestoreCompleted,
162    ObjectRestoreDelete,
163    ReducedRedundancyLostObject,
164    ReplicationOperationFailedReplication,
165    ReplicationOperationMissedThreshold,
166    ReplicationOperationReplicatedAfterThreshold,
167    ReplicationOperationNotTracked,
168    LifecycleExpiration,
169    LifecycleExpirationDelete,
170    LifecycleExpirationDeleteMarkerCreated,
171    LifecycleTransition,
172    IntelligentTiering,
173    ObjectTagging,
174    ObjectTaggingPut,
175    ObjectTaggingDelete,
176    ObjectAclPut,
177    ObjectRestore,
178    REPLICATION,
179}
180
181#[derive(Debug, Clone)]
182pub enum AbacStatus {
183    Enabled,
184    Disabled,
185}
186
187impl From<AbacStatus> for String {
188    fn from(value: AbacStatus) -> Self {
189        match value {
190            AbacStatus::Enabled => "Enabled".to_string(),
191            AbacStatus::Disabled => "Disabled".to_string(),
192        }
193    }
194}
195#[derive(Debug, Clone)]
196pub enum AccelerationStatus {
197    Enabled,
198    Suspended,
199}
200
201impl From<AccelerationStatus> for String {
202    fn from(value: AccelerationStatus) -> Self {
203        match value {
204            AccelerationStatus::Enabled => "Enabled".to_string(),
205            AccelerationStatus::Suspended => "Suspended".to_string(),
206        }
207    }
208}
209
210impl From<NotificationEventType> for String {
211    fn from(value: NotificationEventType) -> Self {
212        match value {
213            NotificationEventType::ObjectCreated => "s3:ObjectCreated:*".to_string(),
214            NotificationEventType::ObjectCreatedPut => "s3:ObjectCreated:Put".to_string(),
215            NotificationEventType::ObjectCreatedPost => "s3:ObjectCreated:Post".to_string(),
216            NotificationEventType::ObjectCreatedCopy => "s3:ObjectCreated:Copy".to_string(),
217            NotificationEventType::ObjectCreatedCompleteMultipartUpload => "s3:ObjectCreated:CompleteMultipartUpload".to_string(),
218            NotificationEventType::ObjectRemoved => "s3:ObjectRemoved:*".to_string(),
219            NotificationEventType::ObjectRemovedDelete => "s3:ObjectRemoved:Delete".to_string(),
220            NotificationEventType::ObjectRemovedDeleteMarkerCreated => "s3:ObjectRemoved:DeleteMarkerCreated".to_string(),
221            NotificationEventType::ObjectRestorePost => "s3:ObjectRestore:Post".to_string(),
222            NotificationEventType::ObjectRestoreCompleted => "s3:ObjectRestore:Completed".to_string(),
223            NotificationEventType::ObjectRestoreDelete => "s3:ObjectRestore:Delete".to_string(),
224            NotificationEventType::ReducedRedundancyLostObject => "s3:ReducedRedundancyLostObject".to_string(),
225            NotificationEventType::ReplicationOperationFailedReplication => "s3:Replication:OperationFailedReplication".to_string(),
226            NotificationEventType::ReplicationOperationMissedThreshold => "s3:Replication:OperationMissedThreshold".to_string(),
227            NotificationEventType::ReplicationOperationReplicatedAfterThreshold => {
228                "s3:Replication:OperationReplicatedAfterThreshold".to_string()
229            }
230            NotificationEventType::ReplicationOperationNotTracked => "s3:Replication:OperationNotTracked".to_string(),
231            NotificationEventType::LifecycleExpiration => "s3:LifecycleExpiration:*".to_string(),
232            NotificationEventType::LifecycleExpirationDelete => "s3:LifecycleExpiration:Delete".to_string(),
233            NotificationEventType::LifecycleExpirationDeleteMarkerCreated => "s3:LifecycleExpiration:DeleteMarkerCreated".to_string(),
234            NotificationEventType::LifecycleTransition => "s3:LifecycleTransition".to_string(),
235            NotificationEventType::IntelligentTiering => "s3:IntelligentTiering".to_string(),
236            NotificationEventType::ObjectTagging => "s3:ObjectTagging:*".to_string(),
237            NotificationEventType::ObjectTaggingPut => "s3:ObjectTagging:Put".to_string(),
238            NotificationEventType::ObjectTaggingDelete => "s3:ObjectTagging:Delete".to_string(),
239            NotificationEventType::ObjectAclPut => "s3:ObjectAcl:Put".to_string(),
240            NotificationEventType::ObjectRestore => "s3:ObjectRestore:*".to_string(),
241            NotificationEventType::REPLICATION => "s3:Replication:*".to_string(),
242        }
243    }
244}
245
246type_state!(BucketBuilderState, StartState, WebsiteState,);
247
248/// Builder for S3 buckets.
249///
250/// Provides configuration for S3 buckets including versioning, lifecycle rules, encryption, CORS, and static website hosting.
251///
252/// # Example
253///
254/// ```rust,compile_fail
255/// use rusty_cdk_core::stack::StackBuilder;
256/// use rusty_cdk_core::s3::{BucketBuilder, VersioningConfig, Encryption, VersioningConfiguration};
257/// use rusty_cdk_core::wrappers::*;
258/// use rusty_cdk_macros::bucket_name;
259///
260/// let mut stack_builder = StackBuilder::new();
261///
262/// // Create a simple bucket
263/// let bucket = BucketBuilder::new("my-bucket")
264///     .name(bucket_name!("my-unique-bucket"))
265///     .versioning_configuration(VersioningConfiguration::Enabled)
266///     .encryption(Encryption::S3Managed)
267///     .build(&mut stack_builder);
268///
269/// // Create a website bucket
270/// let (website_bucket, policy) = BucketBuilder::new("website-bucket")
271///     .website("index.html")
272///     .error_document("error.html")
273///     .build(&mut stack_builder);
274/// ```
275/// See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3-bucket.html
276pub struct BucketBuilder<T: BucketBuilderState> {
277    phantom_data: PhantomData<T>,
278    id: Id,
279    abac_status: Option<String>,
280    acceleration_status: Option<String>,
281    name: Option<String>,
282    access: Option<PublicAccessBlockConfiguration>,
283    intelligent_tiering_configurations: Option<Vec<IntelligentTieringConfiguration>>,
284    metadata_configuration: Option<MetadataConfiguration>,
285    versioning_configuration: Option<VersioningConfiguration>,
286    lifecycle_configuration: Option<LifecycleConfiguration>,
287    index_document: Option<String>,
288    error_document: Option<String>,
289    redirect_all_requests_to: Option<(String, Option<Protocol>)>,
290    cors_config: Option<CorsConfiguration>,
291    bucket_encryption: Option<Encryption>,
292    bucket_notification_lambda_destinations: Vec<(Value, String)>,
293    bucket_notification_sns_destinations: Vec<(Id, Value, String)>,
294    bucket_notification_sqs_destinations: Vec<(Id, Value, Value, String)>,
295    deletion_policy: Option<String>,
296    update_replace_policy: Option<String>,
297    additional_website_policy_statements: Option<Vec<Statement>>,
298}
299
300impl BucketBuilder<StartState> {
301    /// Creates a new S3 bucket builder.
302    ///
303    /// # Arguments
304    /// * `id` - Unique identifier for the bucket
305    pub fn new(id: &str) -> Self {
306        Self {
307            id: Id(id.to_string()),
308            phantom_data: Default::default(),
309            abac_status: None,
310            acceleration_status: None,
311            name: None,
312            access: None,
313            intelligent_tiering_configurations: None,
314            metadata_configuration: None,
315            versioning_configuration: None,
316            lifecycle_configuration: None,
317            index_document: None,
318            error_document: None,
319            redirect_all_requests_to: None,
320            cors_config: None,
321            bucket_encryption: None,
322            bucket_notification_lambda_destinations: vec![],
323            bucket_notification_sns_destinations: vec![],
324            bucket_notification_sqs_destinations: vec![],
325            deletion_policy: None,
326            update_replace_policy: None,
327            additional_website_policy_statements: None,
328        }
329    }
330
331    pub fn build(self, stack_builder: &mut StackBuilder) -> BucketRef {
332        let (bucket, _) = self.build_internal(false, stack_builder);
333        bucket
334    }
335}
336
337impl<T: BucketBuilderState> BucketBuilder<T> {
338    /// Sets the name of the bucket.
339    ///
340    /// The bucket name must be globally unique.
341    pub fn name(self, name: BucketName) -> Self {
342        Self {
343            name: Some(name.0),
344            ..self
345        }
346    }
347
348    /// Enables or disables attribute-based access control (ABAC) for the bucket.
349    pub fn abac_status(self, abac_status: AbacStatus) -> Self {
350        Self {
351            abac_status: Some(abac_status.into()),
352            ..self
353        }
354    }
355
356    /// Enables or disables S3 Transfer Acceleration for the bucket.
357    pub fn acceleration_status(self, acceleration_status: AccelerationStatus) -> Self {
358        Self {
359            acceleration_status: Some(acceleration_status.into()),
360            ..self
361        }
362    }
363
364    /// Configures metadata for the bucket.
365    pub fn metadata_configuration(self, config: MetadataConfiguration) -> Self {
366        Self {
367            metadata_configuration: Some(config),
368            ..self
369        }
370    }
371
372    /// Configures versioning for the bucket.
373    ///
374    /// Once enabled, versioning cannot be disabled, only suspended.
375    pub fn versioning_configuration(self, config: VersioningConfiguration) -> Self {
376        Self {
377            versioning_configuration: Some(config),
378            ..self
379        }
380    }
381
382    /// Configures lifecycle rules for the bucket.
383    pub fn lifecycle_configuration(self, config: LifecycleConfiguration) -> Self {
384        Self {
385            lifecycle_configuration: Some(config),
386            ..self
387        }
388    }
389
390    /// Configures the public access block for the bucket.
391    pub fn public_access_block_configuration(self, access: PublicAccessBlockConfiguration) -> Self {
392        Self {
393            access: Some(access),
394            ..self
395        }
396    }
397
398    /// Configures server-side encryption for the bucket.
399    pub fn encryption(self, encryption: Encryption) -> Self {
400        Self {
401            bucket_encryption: Some(encryption),
402            ..self
403        }
404    }
405
406    /// Sets the update replace policy and deletion policy for the bucket
407    pub fn update_replace_and_deletion_policy(self, update_replace_policy: UpdateReplacePolicy, deletion_policy: DeletionPolicy) -> Self {
408        Self {
409            deletion_policy: Some(deletion_policy.into()),
410            update_replace_policy: Some(update_replace_policy.into()),
411            ..self
412        }
413    }
414
415    /// Adds an intelligent tiering configuration to the S3 bucket
416    pub fn add_intelligent_tiering(mut self, tiering: IntelligentTieringConfiguration) -> Self {
417        if let Some(mut config) = self.intelligent_tiering_configurations {
418            config.push(tiering);
419            self.intelligent_tiering_configurations = Some(config);
420        } else {
421            self.intelligent_tiering_configurations = Some(vec![tiering]);
422        }
423        self
424    }
425
426    /// Adds a notification destination to the S3 bucket
427    pub fn add_notification(mut self, destination: NotificationDestination) -> Self {
428        match destination {
429            NotificationDestination::Lambda(l, e) => self.bucket_notification_lambda_destinations.push((l.get_arn(), e.into())),
430            NotificationDestination::Sns(s, e) => {
431                self.bucket_notification_sns_destinations
432                    .push((s.get_id().clone(), s.get_ref(), e.into()))
433            }
434            NotificationDestination::Sqs(q, e) => {
435                self.bucket_notification_sqs_destinations
436                    .push((q.get_id().clone(), q.get_ref(), q.get_arn(), e.into()))
437            }
438        }
439        self
440    }
441
442    /// Configures the bucket for static website hosting.
443    ///
444    /// Automatically disables public access blocks and creates a bucket policy
445    /// allowing public GetObject access.
446    pub fn website<I: Into<String>>(self, index_document: I) -> BucketBuilder<WebsiteState> {
447        BucketBuilder {
448            phantom_data: Default::default(),
449            id: self.id,
450            abac_status: self.abac_status,
451            acceleration_status: self.acceleration_status,
452            name: self.name,
453            access: self.access,
454            intelligent_tiering_configurations: self.intelligent_tiering_configurations,
455            metadata_configuration: self.metadata_configuration,
456            versioning_configuration: self.versioning_configuration,
457            lifecycle_configuration: self.lifecycle_configuration,
458            index_document: Some(index_document.into()),
459            error_document: self.error_document,
460            redirect_all_requests_to: self.redirect_all_requests_to,
461            cors_config: self.cors_config,
462            bucket_encryption: self.bucket_encryption,
463            bucket_notification_lambda_destinations: self.bucket_notification_lambda_destinations,
464            bucket_notification_sns_destinations: self.bucket_notification_sns_destinations,
465            bucket_notification_sqs_destinations: self.bucket_notification_sqs_destinations,
466            deletion_policy: self.deletion_policy,
467            update_replace_policy: self.update_replace_policy,
468            additional_website_policy_statements: self.additional_website_policy_statements,
469        }
470    }
471
472    fn build_internal(self, website: bool, stack_builder: &mut StackBuilder) -> (BucketRef, Option<BucketPolicyRef>) {
473        let resource_id = Resource::generate_id("S3Bucket");
474
475        let versioning_configuration = self.versioning_configuration.map(|c| dto::VersioningConfig { status: c.into() });
476
477        let website_configuration = if website {
478            let redirect_all_requests_to = self.redirect_all_requests_to.map(|r| RedirectAllRequestsTo {
479                host_name: r.0,
480                protocol: r.1.map(Into::into),
481            });
482
483            Some(WebsiteConfiguration {
484                index_document: self.index_document,
485                error_document: self.error_document,
486                redirect_all_requests_to,
487            })
488        } else {
489            None
490        };
491
492        let access = if self.access.is_none() && website {
493            // required for an S3 website
494            Some(PublicAccessBlockConfiguration {
495                block_public_policy: Some(false),
496                restrict_public_buckets: Some(false),
497                block_public_acls: Some(true),
498                ignore_public_acls: Some(true),
499            })
500        } else {
501            self.access
502        };
503
504        let encryption = self.bucket_encryption.map(|v| {
505            let rule = ServerSideEncryptionRule {
506                server_side_encryption_by_default: ServerSideEncryptionByDefault {
507                    sse_algorithm: v.into(),
508                    kms_master_key_id: None,
509                },
510                bucket_key_enabled: None,
511            };
512
513            BucketEncryption {
514                server_side_encryption_configuration: vec![rule],
515            }
516        });
517
518        let properties = BucketProperties {
519            abac_status: self.abac_status,
520            accelerate_configuration: self.acceleration_status.map(|v| AccelerateConfiguration { acceleration_status: v }),
521            bucket_name: self.name,
522            cors_configuration: self.cors_config,
523            intelligent_tiering_configurations: self.intelligent_tiering_configurations,
524            lifecycle_configuration: self.lifecycle_configuration,
525            public_access_block_configuration: access,
526            versioning_configuration,
527            website_configuration,
528            bucket_encryption: encryption,
529            metadata_configuration: self.metadata_configuration,
530        };
531
532        stack_builder.add_resource(Bucket {
533            id: self.id.clone(),
534            resource_id: resource_id.clone(),
535            r#type: BucketType::BucketType,
536            properties,
537            update_delete_policy_dto: UpdateDeletePolicyDTO {
538                deletion_policy: self.deletion_policy,
539                update_replace_policy: self.update_replace_policy,
540            },
541        });
542
543        let bucket = BucketRef::internal_new(resource_id);
544
545        let policy = if website {
546            // website needs a policy to allow GETs
547            let bucket_resource = vec![join("", vec![bucket.get_arn(), Value::String("/*".to_string())])];
548            let statement = StatementBuilder::new(vec![IamAction("s3:GetObject".to_string())], Effect::Allow)
549                .resources(bucket_resource)
550                .principal(PrincipalBuilder::new().normal("*").build())
551                .build();
552            let mut statements = vec![statement];
553
554            if let Some(additional) = self.additional_website_policy_statements {
555                statements.extend(additional);
556            }
557
558            let policy_doc = PolicyDocumentBuilder::new(statements).build();
559            let bucket_policy_id = Id::generate_id(&self.id, "S3Policy");
560            let s3_policy = BucketPolicyBuilder::new(&bucket_policy_id, &bucket, policy_doc).build(stack_builder);
561            Some(s3_policy)
562        } else {
563            None
564        };
565
566        for (i, (arn, event)) in self.bucket_notification_lambda_destinations.into_iter().enumerate() {
567            let permission_id = Id::generate_id(&self.id, format!("LambdaDestPerm{}", i).as_str());
568            let permission = PermissionBuilder::new(
569                &permission_id,
570                LambdaPermissionAction("lambda:InvokeFunction".to_string()),
571                arn.clone(),
572                "s3.amazonaws.com",
573            )
574            .source_arn(bucket.get_arn())
575            .current_account()
576            .build(stack_builder);
577            let handler = Self::notification_handler(&self.id, "Lambda", i, stack_builder);
578            let notification_id = Id::generate_id(&self.id, &format!("LambdaNotification{}", i));
579            BucketNotificationBuilder::new(
580                &notification_id,
581                handler.get_arn(),
582                bucket.get_ref(),
583                event,
584                Some(permission.get_id().clone()),
585            )
586            .lambda(arn)
587            .build(stack_builder);
588        }
589
590        for (i, (id, reference, event)) in self.bucket_notification_sns_destinations.into_iter().enumerate() {
591            let handler = Self::notification_handler(&self.id, "SNS", i, stack_builder);
592
593            let bucket_arn = bucket.get_arn();
594            let condition = json!({
595                "ArnLike": {
596                    "aws:SourceArn": bucket_arn
597                }
598            });
599            let principal = PrincipalBuilder::new().service("s3.amazonaws.com".to_string()).build();
600            let statement = StatementBuilder::new(vec![IamAction("sns:Publish".to_string())], Effect::Allow)
601                .principal(principal)
602                .condition(condition)
603                .resources(vec![reference.clone()])
604                .build();
605
606            let topic_policy_id = Id::generate_id(&id, TOPIC_POLICY_ID_SUFFIX);
607            let topic_policy_ref_id = match stack_builder.get_resource(&topic_policy_id) {
608                None => {
609                    // there's no queue policy. add ours
610                    let doc = PolicyDocumentBuilder::new(vec![statement]).build();
611                    let topic_policy_id = Id::generate_id(&self.id, &format!("SNSDestinationPolicy{}", i));
612                    TopicPolicyBuilder::new_with_values(topic_policy_id.clone(), doc, vec![reference.clone()]).build(stack_builder);
613                    topic_policy_id
614                }
615                Some(Resource::TopicPolicy(pol)) => {
616                    // there's a policy, add the required permissions
617                    pol.properties.doc.statements.push(statement);
618                    topic_policy_id
619                }
620                _ => unreachable!("topic policy id should point to optional topic policy"),
621            };
622
623            let notification_id = Id::generate_id(&self.id, &format!("SNSNotification{}", i));
624            BucketNotificationBuilder::new(
625                &notification_id,
626                handler.get_arn(),
627                bucket.get_ref(),
628                event,
629                Some(topic_policy_ref_id),
630            )
631            .sns(reference)
632            .build(stack_builder);
633        }
634
635        for (i, (id, reference, arn, event)) in self.bucket_notification_sqs_destinations.into_iter().enumerate() {
636            let handler = Self::notification_handler(&self.id, "SQS", i, stack_builder);
637
638            let bucket_arn = bucket.get_arn();
639            let condition = json!({
640                "ArnLike": {
641                    "aws:SourceArn": bucket_arn
642                }
643            });
644            let principal = PrincipalBuilder::new().service("s3.amazonaws.com".to_string()).build();
645            let statement = StatementBuilder::new(
646                vec![
647                    IamAction("sqs:GetQueueAttributes".to_string()),
648                    IamAction("sqs:GetQueueUrl".to_string()),
649                    IamAction("sqs:SendMessage".to_string()),
650                ],
651                Effect::Allow,
652            )
653            .principal(principal)
654            .condition(condition)
655            .resources(vec![arn.clone()])
656            .build();
657
658            let queue_policy_id = Id::generate_id(&id, QUEUE_POLICY_ID_SUFFIX);
659
660            let queue_policy_ref_id = match stack_builder.get_resource(&queue_policy_id) {
661                None => {
662                    // there's no queue policy. add ours
663                    let doc = PolicyDocumentBuilder::new(vec![statement]).build();
664                    let queue_policy_id = Id::generate_id(&self.id, &format!("SQSDestinationPolicy{}", i));
665                    QueuePolicyBuilder::new_with_values(queue_policy_id.clone(), doc, vec![reference.clone()]).build(stack_builder);
666                    queue_policy_id
667                }
668                Some(Resource::QueuePolicy(pol)) => {
669                    // there's a policy, add the required permissions
670                    pol.properties.doc.statements.push(statement);
671                    queue_policy_id
672                }
673                _ => unreachable!("queue policy id should point to optional queue policy"),
674            };
675
676            let notification_id = Id::generate_id(&self.id, format!("SQSNotification{}", i).as_str());
677            BucketNotificationBuilder::new(
678                &notification_id,
679                handler.get_arn(),
680                bucket.get_ref(),
681                event,
682                Some(queue_policy_ref_id),
683            )
684            .sqs(arn)
685            .build(stack_builder);
686        }
687
688        (bucket, policy)
689    }
690
691    fn notification_handler(id: &Id, target: &str, num: usize, stack_builder: &mut StackBuilder) -> FunctionRef {
692        let handler_id = Id::generate_id(id, &format!("{}Handler{}", target, num));
693        let (handler, ..) = FunctionBuilder::new(&handler_id, Architecture::X86_64, Memory(128), Timeout(300))
694            .code(Code::Inline(BUCKET_NOTIFICATION_HANDLER_CODE.to_string()))
695            .handler("index.handler")
696            .runtime(Runtime::Python313)
697            .add_permission(Permission::Custom(CustomPermission::new(
698                PolicyName("NotificationPermission".to_string()),
699                StatementBuilder::new(vec![IamAction("s3:PutBucketNotification".to_string())], Effect::Allow)
700                    .all_resources()
701                    .build(),
702            )))
703            .build(stack_builder);
704        handler
705    }
706}
707
708impl BucketBuilder<WebsiteState> {
709    pub fn error_document<I: Into<String>>(self, error: I) -> Self {
710        Self {
711            error_document: Some(error.into()),
712            ..self
713        }
714    }
715
716    pub fn redirect_all<I: Into<String>>(self, hostname: I, protocol: Option<Protocol>) -> Self {
717        Self {
718            redirect_all_requests_to: Some((hostname.into(), protocol)),
719            ..self
720        }
721    }
722
723    pub fn cors_config(self, config: CorsConfiguration) -> Self {
724        Self {
725            cors_config: Some(config),
726            ..self
727        }
728    }
729
730    /// Additional statements that will be added to the bucket policy.
731    ///
732    /// The bucket policy will by default allow GETs from anywhere.
733    pub fn custom_bucket_policy_statements(self, statements: Vec<Statement>) -> Self {
734        Self {
735            additional_website_policy_statements: Some(statements),
736            ..self
737        }
738    }
739
740    /// Builds the website bucket and adds it to the stack.
741    ///
742    /// Returns both the bucket and the automatically created bucket policy that allows public read access.
743    pub fn build(self, stack_builder: &mut StackBuilder) -> (BucketRef, BucketPolicyRef) {
744        let (bucket, policy) = self.build_internal(true, stack_builder);
745        (bucket, policy.expect("for website, bucket policy should always be present"))
746    }
747}
748
749/// Builder for S3 CORS configuration 
750/// 
751/// See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-corsconfiguration.html
752pub struct CorsConfigurationBuilder {
753    rules: Vec<CorsRule>,
754}
755
756impl CorsConfigurationBuilder {
757    pub fn new(rules: Vec<CorsRule>) -> CorsConfigurationBuilder {
758        CorsConfigurationBuilder { rules }
759    }
760
761    pub fn build(self) -> CorsConfiguration {
762        CorsConfiguration { cors_rules: self.rules }
763    }
764}
765
766/// Builder for individual CORS rules.
767/// 
768/// See https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-s3-bucket-corsrule.html
769pub struct CorsRuleBuilder {
770    allow_origins: Vec<String>,
771    allow_methods: Vec<HttpMethod>,
772    allow_headers: Option<Vec<String>>,
773    expose_headers: Option<Vec<String>>,
774    max_age: Option<u64>,
775}
776
777impl CorsRuleBuilder {
778    pub fn new<T: Into<String>>(allow_origins: Vec<T>, allow_methods: Vec<HttpMethod>) -> Self {
779        Self {
780            allow_origins: allow_origins.into_iter().map(Into::into).collect(),
781            allow_methods,
782            allow_headers: None,
783            expose_headers: None,
784            max_age: None,
785        }
786    }
787
788    pub fn allow_headers(self, headers: Vec<String>) -> Self {
789        Self {
790            allow_headers: Some(headers),
791            ..self
792        }
793    }
794
795    pub fn expose_headers(self, headers: Vec<String>) -> Self {
796        Self {
797            expose_headers: Some(headers),
798            ..self
799        }
800    }
801
802    pub fn max_age(self, age: Duration) -> Self {
803        Self {
804            max_age: Some(age.as_secs()),
805            ..self
806        }
807    }
808
809    #[must_use]
810    pub fn build(self) -> CorsRule {
811        CorsRule {
812            allowed_headers: self.allow_headers,
813            allowed_methods: self.allow_methods.into_iter().map(Into::into).collect(),
814            allowed_origins: self.allow_origins,
815            exposed_headers: self.expose_headers,
816            max_age: self.max_age,
817        }
818    }
819}
820
821#[derive(Debug, Clone)]
822pub enum TransitionDefaultMinimumObjectSize {
823    VariesByStorageClass,
824    AllStorageClasses128k,
825}
826
827impl From<TransitionDefaultMinimumObjectSize> for String {
828    fn from(value: TransitionDefaultMinimumObjectSize) -> Self {
829        match value {
830            TransitionDefaultMinimumObjectSize::VariesByStorageClass => "varies_by_storage_class".to_string(),
831            TransitionDefaultMinimumObjectSize::AllStorageClasses128k => "all_storage_classes_128K".to_string(),
832        }
833    }
834}
835
836#[derive(Debug, Clone)]
837pub enum LifecycleStorageClass {
838    IntelligentTiering,
839    OneZoneIA,
840    StandardIA,
841    GlacierDeepArchive,
842    Glacier,
843    GlacierInstantRetrieval,
844}
845
846impl From<LifecycleStorageClass> for String {
847    fn from(value: LifecycleStorageClass) -> Self {
848        match value {
849            LifecycleStorageClass::GlacierDeepArchive => "DEEP_ARCHIVE".to_string(),
850            LifecycleStorageClass::Glacier => "GLACIER".to_string(),
851            LifecycleStorageClass::GlacierInstantRetrieval => "GLACIER_IR".to_string(),
852            LifecycleStorageClass::IntelligentTiering => "INTELLIGENT_TIERING".to_string(),
853            LifecycleStorageClass::OneZoneIA => "ONEZONE_IA".to_string(),
854            LifecycleStorageClass::StandardIA => "STANDARD_IA".to_string(),
855        }
856    }
857}
858
859/// Builder for S3 lifecycle rule transitions.
860///
861/// Configures automatic transitions of objects to different storage classes.
862pub struct LifecycleRuleTransitionBuilder {
863    storage_class: LifecycleStorageClass,
864    transition_in_days: Option<u16>,
865}
866
867impl LifecycleRuleTransitionBuilder {
868    pub fn new(storage_class: LifecycleStorageClass) -> Self {
869        Self {
870            storage_class,
871            transition_in_days: None,
872        }
873    }
874
875    pub fn transition_in_days(self, days: LifecycleTransitionInDays) -> Self {
876        Self {
877            transition_in_days: Some(days.0),
878            ..self
879        }
880    }
881
882    #[must_use]
883    pub fn build(self) -> LifecycleRuleTransition {
884        LifecycleRuleTransition {
885            storage_class: self.storage_class.into(),
886            transition_in_days: self.transition_in_days.unwrap_or(0),
887        }
888    }
889}
890
891/// Builder for non-current version transitions in versioned buckets.
892///
893/// Configures automatic transitions for previous versions of objects.
894pub struct NonCurrentVersionTransitionBuilder {
895    storage_class: LifecycleStorageClass,
896    transition_in_days: u32,
897    newer_non_current_versions: Option<u32>,
898}
899
900impl NonCurrentVersionTransitionBuilder {
901    pub fn new(storage_class: LifecycleStorageClass, transition_in_days: u32) -> Self {
902        Self {
903            storage_class,
904            transition_in_days,
905            newer_non_current_versions: None,
906        }
907    }
908
909    pub fn newer_non_current_versions(self, versions: u32) -> Self {
910        Self {
911            newer_non_current_versions: Some(versions),
912            ..self
913        }
914    }
915
916    #[must_use]
917    pub fn build(self) -> NonCurrentVersionTransition {
918        NonCurrentVersionTransition {
919            storage_class: self.storage_class.into(),
920            transition_in_days: self.transition_in_days,
921            newer_non_current_versions: self.newer_non_current_versions,
922        }
923    }
924}
925
926#[derive(Debug, Clone)]
927pub enum LifecycleRuleStatus {
928    Enabled,
929    Disabled,
930}
931
932impl From<LifecycleRuleStatus> for String {
933    fn from(value: LifecycleRuleStatus) -> Self {
934        match value {
935            LifecycleRuleStatus::Enabled => "Enabled".to_string(),
936            LifecycleRuleStatus::Disabled => "Disabled".to_string(),
937        }
938    }
939}
940
941/// Builder for S3 lifecycle rules.
942///
943/// Defines rules for automatic object expiration and transitions between storage classes.
944pub struct LifecycleRuleBuilder {
945    id: Option<String>,
946    status: LifecycleRuleStatus,
947    expiration_in_days: Option<u16>, // expiration must be > than expiration in transition (ow boy...)
948    prefix: Option<String>,
949    object_size_greater_than: Option<u32>,
950    object_size_less_than: Option<u32>,
951    abort_incomplete_multipart_upload: Option<u16>,
952    non_current_version_expiration: Option<u16>,
953    transitions: Option<Vec<LifecycleRuleTransition>>,
954    non_current_version_transitions: Option<Vec<NonCurrentVersionTransition>>,
955}
956
957impl LifecycleRuleBuilder {
958    pub fn new(status: LifecycleRuleStatus) -> Self {
959        Self {
960            status,
961            id: None,
962            expiration_in_days: None,
963            prefix: None,
964            object_size_greater_than: None,
965            object_size_less_than: None,
966            abort_incomplete_multipart_upload: None,
967            non_current_version_expiration: None,
968            transitions: None,
969            non_current_version_transitions: None,
970        }
971    }
972
973    pub fn id<T: Into<String>>(self, id: T) -> Self {
974        Self {
975            id: Some(id.into()),
976            ..self
977        }
978    }
979
980    pub fn expiration_in_days(self, days: u16) -> Self {
981        Self {
982            expiration_in_days: Some(days),
983            ..self
984        }
985    }
986
987    pub fn prefix<T: Into<String>>(self, prefix: T) -> Self {
988        Self {
989            prefix: Some(prefix.into()),
990            ..self
991        }
992    }
993
994    pub fn object_size(self, sizes: S3LifecycleObjectSizes) -> Self {
995        Self {
996            object_size_less_than: sizes.0,
997            object_size_greater_than: sizes.1,
998            ..self
999        }
1000    }
1001
1002    pub fn abort_incomplete_multipart_upload(self, days: u16) -> Self {
1003        Self {
1004            abort_incomplete_multipart_upload: Some(days),
1005            ..self
1006        }
1007    }
1008
1009    pub fn non_current_version_expiration(self, days: u16) -> Self {
1010        Self {
1011            non_current_version_expiration: Some(days),
1012            ..self
1013        }
1014    }
1015
1016    pub fn add_transition(mut self, transition: LifecycleRuleTransition) -> Self {
1017        if let Some(mut transitions) = self.transitions {
1018            transitions.push(transition);
1019            self.transitions = Some(transitions);
1020        } else {
1021            self.transitions = Some(vec![transition]);
1022        }
1023
1024        Self { ..self }
1025    }
1026
1027    pub fn add_non_current_version_transitions(mut self, transition: NonCurrentVersionTransition) -> Self {
1028        if let Some(mut transitions) = self.non_current_version_transitions {
1029            transitions.push(transition);
1030            self.non_current_version_transitions = Some(transitions);
1031        } else {
1032            self.non_current_version_transitions = Some(vec![transition]);
1033        }
1034
1035        Self { ..self }
1036    }
1037
1038    pub fn build(self) -> LifecycleRule {
1039        LifecycleRule {
1040            id: self.id,
1041            status: self.status.into(),
1042            expiration_in_days: self.expiration_in_days,
1043            prefix: self.prefix,
1044            object_size_greater_than: self.object_size_greater_than,
1045            object_size_less_than: self.object_size_less_than,
1046            transitions: self.transitions,
1047            abort_incomplete_multipart_upload: self.abort_incomplete_multipart_upload,
1048            non_current_version_expiration: self.non_current_version_expiration,
1049            non_current_version_transitions: self.non_current_version_transitions,
1050        }
1051    }
1052}
1053
1054/// Builder for S3 lifecycle configuration.
1055///
1056/// Combines multiple lifecycle rules into a configuration for a bucket.
1057/// See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-lifecycleconfiguration.html
1058pub struct LifecycleConfigurationBuilder {
1059    rules: Vec<LifecycleRule>,
1060    transition_minimum_size: Option<TransitionDefaultMinimumObjectSize>,
1061}
1062
1063impl Default for LifecycleConfigurationBuilder {
1064    fn default() -> Self {
1065        Self::new()
1066    }
1067}
1068
1069impl LifecycleConfigurationBuilder {
1070    pub fn new() -> Self {
1071        Self {
1072            rules: vec![],
1073            transition_minimum_size: None,
1074        }
1075    }
1076
1077    pub fn transition_minimum_size(self, size: TransitionDefaultMinimumObjectSize) -> Self {
1078        Self {
1079            transition_minimum_size: Some(size),
1080            ..self
1081        }
1082    }
1083
1084    pub fn add_rule(mut self, rule: LifecycleRule) -> Self {
1085        self.rules.push(rule);
1086        self
1087    }
1088
1089    #[must_use]
1090    pub fn build(self) -> LifecycleConfiguration {
1091        LifecycleConfiguration {
1092            rules: self.rules,
1093            transition_minimum_size: self.transition_minimum_size.map(|v| v.into()),
1094        }
1095    }
1096}
1097
1098/// Builder for S3 public access block configuration.
1099///
1100/// Controls public access to the bucket at the bucket level.
1101/// See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-publicaccessblockconfiguration.html
1102pub struct PublicAccessBlockConfigurationBuilder {
1103    block_public_acls: Option<bool>,
1104    block_public_policy: Option<bool>,
1105    ignore_public_acls: Option<bool>,
1106    restrict_public_buckets: Option<bool>,
1107}
1108
1109impl Default for PublicAccessBlockConfigurationBuilder {
1110    fn default() -> Self {
1111        Self::new()
1112    }
1113}
1114
1115impl PublicAccessBlockConfigurationBuilder {
1116    pub fn new() -> Self {
1117        Self {
1118            block_public_acls: None,
1119            block_public_policy: None,
1120            ignore_public_acls: None,
1121            restrict_public_buckets: None,
1122        }
1123    }
1124
1125    pub fn block_public_acls(self, config: bool) -> Self {
1126        Self {
1127            block_public_acls: Some(config),
1128            ..self
1129        }
1130    }
1131
1132    pub fn block_public_policy(self, config: bool) -> Self {
1133        Self {
1134            block_public_policy: Some(config),
1135            ..self
1136        }
1137    }
1138
1139    pub fn ignore_public_acls(self, config: bool) -> Self {
1140        Self {
1141            ignore_public_acls: Some(config),
1142            ..self
1143        }
1144    }
1145
1146    pub fn restrict_public_buckets(self, config: bool) -> Self {
1147        Self {
1148            restrict_public_buckets: Some(config),
1149            ..self
1150        }
1151    }
1152
1153    #[must_use]
1154    pub fn build(self) -> PublicAccessBlockConfiguration {
1155        PublicAccessBlockConfiguration {
1156            block_public_acls: self.block_public_acls,
1157            block_public_policy: self.block_public_policy,
1158            ignore_public_acls: self.ignore_public_acls,
1159            restrict_public_buckets: self.restrict_public_buckets,
1160        }
1161    }
1162}
1163
1164pub enum IntelligentTieringStatus {
1165    Enabled,
1166    Disabled,
1167}
1168
1169impl From<IntelligentTieringStatus> for String {
1170    fn from(value: IntelligentTieringStatus) -> String {
1171        match value {
1172            IntelligentTieringStatus::Enabled => "Enabled".to_string(),
1173            IntelligentTieringStatus::Disabled => "Disabled".to_string(),
1174        }
1175    }
1176}
1177
1178/// Builder for TagFilters of IntelligentTiering
1179/// 
1180/// See https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-s3-bucket-tagfilter.html
1181pub struct TagFilterBuilder {
1182    key: String,
1183    value: String,
1184}
1185
1186impl TagFilterBuilder {
1187    pub fn new<T: Into<String>>(key: T, value: T) -> Self {
1188        Self {
1189            key: key.into(),
1190            value: value.into(),
1191        }
1192    }
1193
1194    pub fn build(self) -> TagFilter {
1195        TagFilter {
1196            key: self.key,
1197            value: self.value,
1198        }
1199    }
1200}
1201
1202/// Builder for IntelligentTiering Configuration
1203/// 
1204/// See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-intelligenttieringconfiguration.html
1205pub struct IntelligentTieringConfigurationBuilder {
1206    id: String,
1207    status: String,
1208    prefix: Option<String>,
1209    tag_filters: Option<Vec<TagFilter>>,
1210    tierings: Vec<Tiering>,
1211}
1212
1213impl IntelligentTieringConfigurationBuilder {
1214    pub fn new(id: &str, status: IntelligentTieringStatus, tierings: Vec<BucketTiering>) -> Self {
1215        IntelligentTieringConfigurationBuilder {
1216            id: id.to_string(),
1217            status: status.into(),
1218            prefix: None,
1219            tag_filters: None,
1220            tierings: tierings
1221                .into_iter()
1222                .map(|t| Tiering {
1223                    access_tier: t.0,
1224                    days: t.1,
1225                })
1226                .collect(),
1227        }
1228    }
1229
1230    pub fn prefix<T: Into<String>>(self, prefix: T) -> Self {
1231        Self {
1232            prefix: Some(prefix.into()),
1233            ..self
1234        }
1235    }
1236
1237    pub fn add_tag_filter(mut self, tag_filter: TagFilter) -> Self {
1238        if let Some(mut filters) = self.tag_filters {
1239            filters.push(tag_filter);
1240            self.tag_filters = Some(filters);
1241        } else {
1242            self.tag_filters = Some(vec![tag_filter]);
1243        }
1244
1245        self
1246    }
1247
1248    pub fn build(self) -> IntelligentTieringConfiguration {
1249        IntelligentTieringConfiguration {
1250            id: self.id,
1251            prefix: self.prefix,
1252            status: self.status,
1253            tag_filters: self.tag_filters,
1254            tierings: self.tierings,
1255        }
1256    }
1257}
1258
1259pub enum TableBucketType {
1260    Aws,
1261    Customer,
1262}
1263
1264impl From<TableBucketType> for String {
1265    fn from(value: TableBucketType) -> String {
1266        match value {
1267            TableBucketType::Aws => "aws".to_string(),
1268            TableBucketType::Customer => "customer".to_string(),
1269        }
1270    }
1271}
1272
1273pub enum Expiration {
1274    Enabled,
1275    Disabled,
1276}
1277
1278impl From<Expiration> for String {
1279    fn from(value: Expiration) -> String {
1280        match value {
1281            Expiration::Enabled => "ENABLED".to_string(),
1282            Expiration::Disabled => "DISABLED".to_string(),
1283        }
1284    }
1285}
1286
1287/// Builder for the Record Expiration of Bucket Metadataconfiguration
1288/// 
1289/// See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-metadataconfiguration-journaltableconfiguration.html
1290pub struct RecordExpirationBuilder {
1291    days: Option<u32>,
1292    expiration: String,
1293}
1294
1295impl RecordExpirationBuilder {
1296    pub fn new(expiration: Expiration) -> Self {
1297        Self {
1298            expiration: expiration.into(),
1299            days: None,
1300        }
1301    }
1302
1303    pub fn days(self, days: RecordExpirationDays) -> Self {
1304        Self {
1305            days: Some(days.0),
1306            ..self
1307        }
1308    }
1309
1310    pub fn build(self) -> RecordExpiration {
1311        RecordExpiration {
1312            days: self.days,
1313            expiration: self.expiration,
1314        }
1315    }
1316}
1317
1318pub struct JournalTableConfigurationBuilder {
1319    record_expiration: RecordExpiration,
1320    table_arn: Option<Value>,
1321    table_name: Option<String>,
1322}
1323
1324impl JournalTableConfigurationBuilder {
1325    pub fn new(record_expiration: RecordExpiration) -> Self {
1326        Self {
1327            record_expiration,
1328            table_arn: None,
1329            table_name: None,
1330        }
1331    }
1332
1333    pub fn table_name<T: Into<String>>(self, name: T) -> Self {
1334        Self {
1335            table_name: Some(name.into()),
1336            ..self
1337        }
1338    }
1339
1340    pub fn table_arn(self, arn: Value) -> Self {
1341        Self {
1342            table_arn: Some(arn),
1343            ..self
1344        }
1345    }
1346
1347    pub fn build(self) -> JournalTableConfiguration {
1348        JournalTableConfiguration {
1349            record_expiration: self.record_expiration,
1350            table_arn: self.table_arn,
1351            table_name: self.table_name,
1352        }
1353    }
1354}
1355
1356/// Builder for the metadata destination of Bucket Metadataconfiguration
1357/// 
1358/// See https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-s3-bucket-metadatadestination.html
1359pub struct MetadataDestinationBuilder {
1360    table_bucket_type: String,
1361    table_bucket_arn: Option<Value>,
1362    table_namespace: Option<String>,
1363}
1364
1365impl MetadataDestinationBuilder {
1366    pub fn new(table_bucket_type: TableBucketType) -> Self {
1367        Self {
1368            table_bucket_type: table_bucket_type.into(),
1369            table_bucket_arn: None,
1370            table_namespace: None,
1371        }
1372    }
1373
1374    pub fn table_bucket_arn(self, table_bucket_arn: Value) -> Self {
1375        Self {
1376            table_bucket_arn: Some(table_bucket_arn),
1377            ..self
1378        }
1379    }
1380
1381    pub fn table_namespace<T: Into<String>>(self, table_namespace: T) -> Self {
1382        Self {
1383            table_namespace: Some(table_namespace.into()),
1384            ..self
1385        }
1386    }
1387
1388    pub fn build(self) -> MetadataDestination {
1389        MetadataDestination {
1390            table_bucket_arn: self.table_bucket_arn,
1391            table_bucket_type: self.table_bucket_type,
1392            table_namespace: self.table_namespace,
1393        }
1394    }
1395}
1396
1397pub enum ConfigurationState {
1398    Enabled,
1399    Disabled,
1400}
1401
1402impl From<ConfigurationState> for String {
1403    fn from(value: ConfigurationState) -> String {
1404        match value {
1405            ConfigurationState::Enabled => "ENABLED".to_string(),
1406            ConfigurationState::Disabled => "DISABLED".to_string(),
1407        }
1408    }
1409}
1410
1411/// Builder for the InventoryTable Config
1412/// 
1413/// See https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-s3-bucket-inventorytableconfiguration.html
1414pub struct InventoryTableConfigurationBuilder {
1415    configuration_state: String,
1416    table_arn: Option<Value>,
1417    table_name: Option<String>,
1418}
1419
1420impl InventoryTableConfigurationBuilder {
1421    pub fn new(configuration_state: ConfigurationState) -> Self {
1422        Self {
1423            configuration_state: configuration_state.into(),
1424            table_arn: None,
1425            table_name: None,
1426        }
1427    }
1428
1429    pub fn table_arn(self, table_arn: Value) -> Self {
1430        Self {
1431            table_arn: Some(table_arn),
1432            ..self
1433        }
1434    }
1435
1436    pub fn table_name<T: Into<String>>(self, table_name: T) -> Self {
1437        Self {
1438            table_name: Some(table_name.into()),
1439            ..self
1440        }
1441    }
1442
1443    pub fn build(self) -> InventoryTableConfiguration {
1444        InventoryTableConfiguration {
1445            configuration_state: self.configuration_state,
1446            table_arn: self.table_arn,
1447            table_name: self.table_name,
1448        }
1449    }
1450}
1451
1452pub struct MetadataConfigurationBuilder {
1453    destination: Option<MetadataDestination>,
1454    inventory_table_configuration: Option<InventoryTableConfiguration>,
1455    journal_table_configuration: JournalTableConfiguration,
1456}
1457
1458impl MetadataConfigurationBuilder {
1459    pub fn new(journal_table_configuration: JournalTableConfiguration) -> Self {
1460        MetadataConfigurationBuilder {
1461            journal_table_configuration,
1462            destination: None,
1463            inventory_table_configuration: None,
1464        }
1465    }
1466
1467    pub fn destination(self, destination: MetadataDestination) -> Self {
1468        Self {
1469            destination: Some(destination),
1470            ..self
1471        }
1472    }
1473
1474    pub fn inventory_table_configuration(self, inventory_table_configuration: InventoryTableConfiguration) -> Self {
1475        Self {
1476            inventory_table_configuration: Some(inventory_table_configuration),
1477            ..self
1478        }
1479    }
1480
1481    pub fn build(self) -> MetadataConfiguration {
1482        MetadataConfiguration {
1483            destination: self.destination,
1484            inventory_table_configuration: self.inventory_table_configuration,
1485            journal_table_configuration: self.journal_table_configuration,
1486        }
1487    }
1488}