rusty_cdk_core/s3/
builder.rs

1use crate::iam::{Effect, PolicyDocument, PolicyDocumentBuilder, PrincipalBuilder, StatementBuilder};
2use crate::intrinsic::join;
3use crate::s3::dto;
4use crate::s3::{
5    Bucket, BucketEncryption, BucketPolicy, BucketPolicyRef, BucketProperties, BucketRef, CorsConfiguration, CorsRule,
6    LifecycleConfiguration, LifecycleRule, LifecycleRuleTransition, NonCurrentVersionTransition, PublicAccessBlockConfiguration,
7    RedirectAllRequestsTo, S3BucketPolicyProperties, ServerSideEncryptionByDefault, ServerSideEncryptionRule, WebsiteConfiguration,
8};
9use crate::shared::http::{HttpMethod, Protocol};
10use crate::shared::Id;
11use crate::stack::{Resource, StackBuilder};
12use crate::wrappers::{BucketName, IamAction, LifecycleTransitionInDays, S3LifecycleObjectSizes};
13use serde_json::Value;
14use std::marker::PhantomData;
15use std::time::Duration;
16use crate::type_state;
17
18// TODO notifications will require custom work to avoid circular dependencies
19//  CDK approach with custom resources is one way
20//  other way would be for the deploy to do extra work, but then the cloudformation template can only work correctly with our deploy method...
21
22/// Builder for S3 bucket policies.
23///
24/// Creates a policy document that controls access to an S3 bucket.
25///
26/// # Example
27///
28/// ```rust,no_run
29/// use serde_json::Value;
30/// use rusty_cdk_core::stack::StackBuilder;
31/// use rusty_cdk_core::s3::BucketPolicyBuilder;
32/// use rusty_cdk_core::iam::{PolicyDocumentBuilder, StatementBuilder, Effect, PrincipalBuilder};
33/// use rusty_cdk_core::wrappers::*;
34/// use rusty_cdk_core::s3::BucketBuilder;
35/// use rusty_cdk_macros::iam_action;
36///
37/// let mut stack_builder = StackBuilder::new();
38/// let bucket = unimplemented!("create a bucket");
39///
40/// let resources = vec![Value::String("*".to_string())];
41/// let statement = StatementBuilder::new(
42///         vec![iam_action!("s3:GetObject")],
43///         Effect::Allow
44///     )
45///     .principal(PrincipalBuilder::new().normal("*").build())
46///     .resources(resources)
47///     .build();
48///
49/// let policy_doc = PolicyDocumentBuilder::new(vec![statement]).build();
50/// let policy = BucketPolicyBuilder::new("bucket-policy", &bucket, policy_doc)
51///     .build(&mut stack_builder);
52/// ```
53pub struct BucketPolicyBuilder {
54    id: Id,
55    bucket_name: Value,
56    policy_document: PolicyDocument,
57}
58
59impl BucketPolicyBuilder {
60    /// Creates a new S3 bucket policy builder.
61    ///
62    /// # Arguments
63    /// * `id` - Unique identifier for the bucket policy
64    /// * `bucket` - Reference to the S3 bucket
65    /// * `policy_document` - IAM policy document controlling access
66    pub fn new(id: &str, bucket: &BucketRef, policy_document: PolicyDocument) -> Self {
67        Self {
68            id: Id(id.to_string()),
69            bucket_name: bucket.get_ref(),
70            policy_document,
71        }
72    }
73    
74    pub(crate) fn new_with_bucket_ref(id: &str, bucket_name: Value, policy_document: PolicyDocument) -> Self {
75        Self {
76            id: Id(id.to_string()),
77            bucket_name,
78            policy_document,
79        }
80    }
81    
82    pub(crate) fn raw_build(self) -> (String, BucketPolicy) {
83        let resource_id = Resource::generate_id("S3BucketPolicy");
84        let policy = BucketPolicy {
85            id: self.id,
86            resource_id: resource_id.to_string(),
87            r#type: "AWS::S3::BucketPolicy".to_string(),
88            properties: S3BucketPolicyProperties {
89                bucket_name: self.bucket_name,
90                policy_document: self.policy_document,
91            },
92        };
93        (resource_id, policy)
94    }
95
96    pub fn build(self, stack_builder: &mut StackBuilder) -> BucketPolicyRef {
97        let (resource_id, policy) = self.raw_build();
98        stack_builder.add_resource(policy);
99        BucketPolicyRef::new(resource_id)
100    }
101}
102
103pub enum VersioningConfiguration {
104    Enabled,
105    Suspended,
106}
107
108impl From<VersioningConfiguration> for String {
109    fn from(value: VersioningConfiguration) -> Self {
110        match value {
111            VersioningConfiguration::Enabled => "Enabled".to_string(),
112            VersioningConfiguration::Suspended => "Suspended".to_string(),
113        }
114    }
115}
116
117pub enum Encryption {
118    S3Managed,
119    KmsManaged,
120    DsseManaged,
121    // KMS, => add, this requires creating a kms key and passing it to the bucket
122    // DSSE, => add, similar
123}
124
125impl From<Encryption> for String {
126    fn from(value: Encryption) -> Self {
127        match value {
128            Encryption::S3Managed => "AES256".to_string(),
129            Encryption::KmsManaged => "aws:kms".to_string(),
130            Encryption::DsseManaged => "aws:kms:dsse".to_string(),
131        }
132    }
133}
134
135type_state!(
136    BucketBuilderState,
137    StartState,
138    WebsiteState,
139);
140
141/// Builder for S3 buckets.
142///
143/// Provides configuration for S3 buckets including versioning, lifecycle rules, encryption, CORS, and static website hosting.
144///
145/// # Example
146///
147/// ```rust,compile_fail
148/// use rusty_cdk_core::stack::StackBuilder;
149/// use rusty_cdk_core::s3::{BucketBuilder, VersioningConfig, Encryption, VersioningConfiguration};
150/// use rusty_cdk_core::wrappers::*;
151/// use rusty_cdk_macros::bucket_name;
152///
153/// let mut stack_builder = StackBuilder::new();
154///
155/// // Create a simple bucket
156/// let bucket = BucketBuilder::new("my-bucket")
157///     .name(bucket_name!("my-unique-bucket"))
158///     .versioning_configuration(VersioningConfiguration::Enabled)
159///     .encryption(Encryption::S3Managed)
160///     .build(&mut stack_builder);
161///
162/// // Create a website bucket
163/// let (website_bucket, policy) = BucketBuilder::new("website-bucket")
164///     .website("index.html")
165///     .error_document("error.html")
166///     .build(&mut stack_builder);
167/// ```
168pub struct BucketBuilder<T: BucketBuilderState> {
169    phantom_data: PhantomData<T>,
170    id: Id,
171    name: Option<String>,
172    access: Option<PublicAccessBlockConfiguration>,
173    versioning_configuration: Option<VersioningConfiguration>,
174    lifecycle_configuration: Option<LifecycleConfiguration>,
175    index_document: Option<String>,
176    error_document: Option<String>,
177    redirect_all_requests_to: Option<(String, Option<Protocol>)>,
178    cors_config: Option<CorsConfiguration>,
179    bucket_encryption: Option<Encryption>,
180}
181
182impl BucketBuilder<StartState> {
183    /// Creates a new S3 bucket builder.
184    ///
185    /// # Arguments
186    /// * `id` - Unique identifier for the bucket
187    pub fn new(id: &str) -> Self {
188        Self {
189            id: Id(id.to_string()),
190            phantom_data: Default::default(),
191            name: None,
192            access: None,
193            versioning_configuration: None,
194            lifecycle_configuration: None,
195            index_document: None,
196            error_document: None,
197            redirect_all_requests_to: None,
198            cors_config: None,
199            bucket_encryption: None,
200        }
201    }
202
203    pub fn build(self, stack_builder: &mut StackBuilder) -> BucketRef {
204        let (bucket, _) = self.build_internal(false, stack_builder);
205        bucket
206    }
207}
208
209impl<T: BucketBuilderState> BucketBuilder<T> {
210    pub fn name(self, name: BucketName) -> Self {
211        Self {
212            name: Some(name.0),
213            ..self
214        }
215    }
216
217    pub fn versioning_configuration(self, config: VersioningConfiguration) -> Self {
218        Self {
219            versioning_configuration: Some(config),
220            ..self
221        }
222    }
223
224    pub fn lifecycle_configuration(self, config: LifecycleConfiguration) -> Self {
225        Self {
226            lifecycle_configuration: Some(config),
227            ..self
228        }
229    }
230
231    pub fn public_access_block_configuration(self, access: PublicAccessBlockConfiguration) -> Self {
232        Self {
233            access: Some(access),
234            ..self
235        }
236    }
237
238    pub fn encryption(self, encryption: Encryption) -> Self {
239        Self {
240            bucket_encryption: Some(encryption),
241            ..self
242        }
243    }
244
245    /// Configures the bucket for static website hosting.
246    ///
247    /// Automatically disables public access blocks and creates a bucket policy
248    /// allowing public GetObject access.
249    pub fn website<I: Into<String>>(self, index_document: I) -> BucketBuilder<WebsiteState> {
250        BucketBuilder {
251            phantom_data: Default::default(),
252            id: self.id,
253            name: self.name,
254            access: self.access,
255            versioning_configuration: self.versioning_configuration,
256            lifecycle_configuration: self.lifecycle_configuration,
257            index_document: Some(index_document.into()),
258            error_document: self.error_document,
259            redirect_all_requests_to: self.redirect_all_requests_to,
260            cors_config: self.cors_config,
261            bucket_encryption: self.bucket_encryption,
262        }
263    }
264
265    fn build_internal(self, website: bool, stack_builder: &mut StackBuilder) -> (BucketRef, Option<BucketPolicyRef>) {
266        let resource_id = Resource::generate_id("S3Bucket");
267
268        let versioning_configuration = self
269            .versioning_configuration
270            .map(|c| dto::VersioningConfig { status: c.into() });
271
272        let website_configuration = if website {
273            let redirect_all_requests_to = self.redirect_all_requests_to.map(|r| RedirectAllRequestsTo {
274                host_name: r.0,
275                protocol: r.1.map(Into::into),
276            });
277
278            Some(WebsiteConfiguration {
279                index_document: self.index_document,
280                error_document: self.error_document,
281                redirect_all_requests_to,
282            })
283        } else {
284            None
285        };
286
287        let access = if self.access.is_none() && website {
288            // turning this off is required for an S3 website
289            Some(PublicAccessBlockConfiguration {
290                block_public_acls: Some(false),
291                block_public_policy: Some(false),
292                ignore_public_acls: Some(false),
293                restrict_public_buckets: Some(false),
294            })
295        } else {
296            self.access
297        };
298
299        let encryption = self.bucket_encryption.map(|v| {
300            let rule = ServerSideEncryptionRule {
301                server_side_encryption_by_default: ServerSideEncryptionByDefault {
302                    sse_algorithm: v.into(),
303                    kms_master_key_id: None,
304                },
305                bucket_key_enabled: None,
306            };
307
308            BucketEncryption {
309                server_side_encryption_configuration: vec![rule],
310            }
311        });
312
313        let properties = BucketProperties {
314            bucket_name: self.name,
315            cors_configuration: self.cors_config,
316            lifecycle_configuration: self.lifecycle_configuration,
317            public_access_block_configuration: access,
318            versioning_configuration,
319            website_configuration,
320            bucket_encryption: encryption,
321            notification_configuration: None,
322        };
323
324        stack_builder.add_resource(Bucket {
325            id: self.id.clone(),
326            resource_id: resource_id.clone(),
327            r#type: "AWS::S3::Bucket".to_string(),
328            properties,
329        });
330
331        let bucket = BucketRef::new(resource_id);
332
333        let policy = if website {
334            // website needs a policy to allow GETs
335            let bucket_resource = vec![join("", vec![bucket.get_arn(), Value::String("/*".to_string())])];
336            let statement = StatementBuilder::new(vec![IamAction("s3:GetObject".to_string())], Effect::Allow)
337                .resources(bucket_resource)
338                .principal(PrincipalBuilder::new().normal("*").build())
339                .build();
340            let policy_doc = PolicyDocumentBuilder::new(vec![statement]).build();
341            let bucket_policy_id = format!("{}-website-s3-policy", self.id);
342            let s3_policy = BucketPolicyBuilder::new(bucket_policy_id.as_str(), &bucket, policy_doc).build(stack_builder);
343            Some(s3_policy)
344        } else {
345            None
346        };
347
348        (bucket, policy)
349    }
350}
351
352impl BucketBuilder<WebsiteState> {
353    pub fn error_document<I: Into<String>>(self, error: I) -> Self {
354        Self {
355            error_document: Some(error.into()),
356            ..self
357        }
358    }
359
360    pub fn redirect_all<I: Into<String>>(self, hostname: I, protocol: Option<Protocol>) -> Self {
361        Self {
362            redirect_all_requests_to: Some((hostname.into(), protocol)),
363            ..self
364        }
365    }
366
367    pub fn cors_config(self, config: CorsConfiguration) -> Self {
368        Self {
369            cors_config: Some(config),
370            ..self
371        }
372    }
373
374    /// Builds the website bucket and adds it to the stack.
375    ///
376    /// Returns both the bucket and the automatically created bucket policy
377    /// that allows public read access.
378    pub fn build(self, stack_builder: &mut StackBuilder) -> (BucketRef, BucketPolicyRef) {
379        let (bucket, policy) = self.build_internal(true, stack_builder);
380        (bucket, policy.expect("for website, bucket policy should always be present"))
381    }
382}
383
384/// Builder for S3 CORS configuration.
385pub struct CorsConfigurationBuilder {
386    rules: Vec<CorsRule>
387}
388
389impl CorsConfigurationBuilder {
390    pub fn new(rules: Vec<CorsRule>) -> CorsConfigurationBuilder {
391        CorsConfigurationBuilder { rules }
392    }
393    
394    pub fn build(self) -> CorsConfiguration {
395        CorsConfiguration {
396            cors_rules: self.rules,
397        }
398    }
399}
400
401/// Builder for individual CORS rules.
402pub struct CorsRuleBuilder {
403    allow_origins: Vec<String>,
404    allow_methods: Vec<HttpMethod>,
405    allow_headers: Option<Vec<String>>,
406    expose_headers: Option<Vec<String>>,
407    max_age: Option<u64>,
408}
409
410impl CorsRuleBuilder {
411    pub fn new<T: Into<String>>(allow_origins: Vec<T>, allow_methods: Vec<HttpMethod>) -> Self {
412        Self {
413            allow_origins: allow_origins.into_iter().map(Into::into).collect(),
414            allow_methods,
415            allow_headers: None,
416            expose_headers: None,
417            max_age: None,
418        }
419    }
420
421    pub fn allow_headers(self, headers: Vec<String>) -> Self {
422        Self {
423            allow_headers: Some(headers),
424            ..self
425        }
426    }
427
428    pub fn expose_headers(self, headers: Vec<String>) -> Self {
429        Self {
430            expose_headers: Some(headers),
431            ..self
432        }
433    }
434
435    pub fn max_age(self, age: Duration) -> Self {
436        Self {
437            max_age: Some(age.as_secs()),
438            ..self
439        }
440    }
441
442    #[must_use]
443    pub fn build(self) -> CorsRule {
444        CorsRule {
445            allowed_headers: self.allow_headers,
446            allowed_methods: self.allow_methods.into_iter().map(Into::into).collect(),
447            allowed_origins: self.allow_origins,
448            exposed_headers: self.expose_headers,
449            max_age: self.max_age,
450        }
451    }
452}
453
454pub enum TransitionDefaultMinimumObjectSize {
455    VariesByStorageClass,
456    AllStorageClasses128k,
457}
458
459impl From<TransitionDefaultMinimumObjectSize> for String {
460    fn from(value: TransitionDefaultMinimumObjectSize) -> Self {
461        match value {
462            TransitionDefaultMinimumObjectSize::VariesByStorageClass => "varies_by_storage_class".to_string(),
463            TransitionDefaultMinimumObjectSize::AllStorageClasses128k => "all_storage_classes_128K".to_string(),
464        }
465    }
466}
467
468pub enum LifecycleStorageClass {
469    IntelligentTiering,
470    OneZoneIA,
471    StandardIA,
472    GlacierDeepArchive,
473    Glacier,
474    GlacierInstantRetrieval,
475}
476
477impl From<LifecycleStorageClass> for String {
478    fn from(value: LifecycleStorageClass) -> Self {
479        match value {
480            LifecycleStorageClass::GlacierDeepArchive => "DEEP_ARCHIVE".to_string(),
481            LifecycleStorageClass::Glacier => "GLACIER".to_string(),
482            LifecycleStorageClass::GlacierInstantRetrieval => "GLACIER_IR".to_string(),
483            LifecycleStorageClass::IntelligentTiering => "INTELLIGENT_TIERING".to_string(),
484            LifecycleStorageClass::OneZoneIA => "ONEZONE_IA".to_string(),
485            LifecycleStorageClass::StandardIA => "STANDARD_IA".to_string(),
486        }
487    }
488}
489
490/// Builder for S3 lifecycle rule transitions.
491///
492/// Configures automatic transitions of objects to different storage classes.
493pub struct LifecycleRuleTransitionBuilder {
494    storage_class: LifecycleStorageClass,
495    transition_in_days: Option<u16>,
496}
497
498impl LifecycleRuleTransitionBuilder {
499    pub fn new(storage_class: LifecycleStorageClass) -> Self {
500        Self {
501            storage_class,
502            transition_in_days: None,
503        }
504    }
505
506    pub fn transition_in_days(self, days: LifecycleTransitionInDays) -> Self {
507        Self {
508            transition_in_days: Some(days.0),
509            ..self
510        }
511    }
512
513    #[must_use]
514    pub fn build(self) -> LifecycleRuleTransition {
515        LifecycleRuleTransition {
516            storage_class: self.storage_class.into(),
517            transition_in_days: self.transition_in_days.unwrap_or(0),
518        }
519    }
520}
521
522/// Builder for non-current version transitions in versioned buckets.
523///
524/// Configures automatic transitions for previous versions of objects.
525pub struct NonCurrentVersionTransitionBuilder {
526    storage_class: LifecycleStorageClass,
527    transition_in_days: u32,
528    newer_non_current_versions: Option<u32>,
529}
530
531impl NonCurrentVersionTransitionBuilder {
532    pub fn new(storage_class: LifecycleStorageClass, transition_in_days: u32) -> Self {
533        Self {
534            storage_class,
535            transition_in_days,
536            newer_non_current_versions: None,
537        }
538    }
539
540    pub fn newer_non_current_versions(self, versions: u32) -> Self {
541        Self {
542            newer_non_current_versions: Some(versions),
543            ..self
544        }
545    }
546
547    #[must_use]
548    pub fn build(self) -> NonCurrentVersionTransition {
549        NonCurrentVersionTransition {
550            storage_class: self.storage_class.into(),
551            transition_in_days: self.transition_in_days,
552            newer_non_current_versions: self.newer_non_current_versions,
553        }
554    }
555}
556
557pub enum LifecycleRuleStatus {
558    Enabled,
559    Disabled,
560}
561
562impl From<LifecycleRuleStatus> for String {
563    fn from(value: LifecycleRuleStatus) -> Self {
564        match value {
565            LifecycleRuleStatus::Enabled => "Enabled".to_string(),
566            LifecycleRuleStatus::Disabled => "Disabled".to_string(),
567        }
568    }
569}
570
571/// Builder for S3 lifecycle rules.
572///
573/// Defines rules for automatic object expiration and transitions between storage classes.
574pub struct LifecycleRuleBuilder {
575    id: Option<String>,
576    status: LifecycleRuleStatus,
577    expiration_in_days: Option<u16>, // expiration must be > than expiration in transition (ow boy...)
578    prefix: Option<String>,
579    object_size_greater_than: Option<u32>,
580    object_size_less_than: Option<u32>,
581    abort_incomplete_multipart_upload: Option<u16>,
582    non_current_version_expiration: Option<u16>,
583    transitions: Option<Vec<LifecycleRuleTransition>>,
584    non_current_version_transitions: Option<Vec<NonCurrentVersionTransition>>,
585}
586
587impl LifecycleRuleBuilder {
588    pub fn new(status: LifecycleRuleStatus) -> Self {
589        Self {
590            status,
591            id: None,
592            expiration_in_days: None,
593            prefix: None,
594            object_size_greater_than: None,
595            object_size_less_than: None,
596            abort_incomplete_multipart_upload: None,
597            non_current_version_expiration: None,
598            transitions: None,
599            non_current_version_transitions: None,
600        }
601    }
602
603    pub fn id<T: Into<String>>(self, id: T) -> Self {
604        Self {
605            id: Some(id.into()),
606            ..self
607        }
608    }
609
610    pub fn expiration_in_days(self, days: u16) -> Self {
611        Self {
612            expiration_in_days: Some(days),
613            ..self
614        }
615    }
616
617    pub fn prefix<T: Into<String>>(self, prefix: T) -> Self {
618        Self {
619            prefix: Some(prefix.into()),
620            ..self
621        }
622    }
623
624    pub fn object_size(self, sizes: S3LifecycleObjectSizes) -> Self {
625        Self {
626            object_size_less_than: sizes.0,
627            object_size_greater_than: sizes.1,
628            ..self
629        }
630    }
631
632    pub fn abort_incomplete_multipart_upload(self, days: u16) -> Self {
633        Self {
634            abort_incomplete_multipart_upload: Some(days),
635            ..self
636        }
637    }
638
639    pub fn non_current_version_expiration(self, days: u16) -> Self {
640        Self {
641            non_current_version_expiration: Some(days),
642            ..self
643        }
644    }
645
646    pub fn add_transition(mut self, transition: LifecycleRuleTransition) -> Self {
647        if let Some(mut transitions) = self.transitions {
648            transitions.push(transition);
649            self.transitions = Some(transitions);
650        } else {
651            self.transitions = Some(vec![transition]);
652        }
653
654        Self { ..self }
655    }
656
657    pub fn add_non_current_version_transitions(mut self, transition: NonCurrentVersionTransition) -> Self {
658        if let Some(mut transitions) = self.non_current_version_transitions {
659            transitions.push(transition);
660            self.non_current_version_transitions = Some(transitions);
661        } else {
662            self.non_current_version_transitions = Some(vec![transition]);
663        }
664
665        Self { ..self }
666    }
667
668    pub fn build(self) -> LifecycleRule {
669        LifecycleRule {
670            id: self.id,
671            status: self.status.into(),
672            expiration_in_days: self.expiration_in_days,
673            prefix: self.prefix,
674            object_size_greater_than: self.object_size_greater_than,
675            object_size_less_than: self.object_size_less_than,
676            transitions: self.transitions,
677            abort_incomplete_multipart_upload: self.abort_incomplete_multipart_upload,
678            non_current_version_expiration: self.non_current_version_expiration,
679            non_current_version_transitions: self.non_current_version_transitions,
680        }
681    }
682}
683
684/// Builder for S3 lifecycle configuration.
685///
686/// Combines multiple lifecycle rules into a configuration for a bucket.
687pub struct LifecycleConfigurationBuilder {
688    rules: Vec<LifecycleRule>,
689    transition_minimum_size: Option<TransitionDefaultMinimumObjectSize>,
690}
691
692impl Default for LifecycleConfigurationBuilder {
693    fn default() -> Self {
694        Self::new()
695    }
696}
697
698impl LifecycleConfigurationBuilder {
699    pub fn new() -> Self {
700        Self {
701            rules: vec![],
702            transition_minimum_size: None,
703        }
704    }
705
706    pub fn transition_minimum_size(self, size: TransitionDefaultMinimumObjectSize) -> Self {
707        Self {
708            transition_minimum_size: Some(size),
709            ..self
710        }
711    }
712
713    pub fn add_rule(mut self, rule: LifecycleRule) -> Self {
714        self.rules.push(rule);
715        self
716    }
717
718    #[must_use]
719    pub fn build(self) -> LifecycleConfiguration {
720        LifecycleConfiguration {
721            rules: self.rules,
722            transition_minimum_size: self.transition_minimum_size.map(|v| v.into()),
723        }
724    }
725}
726
727/// Builder for S3 public access block configuration.
728///
729/// Controls public access to the bucket at the bucket level.
730pub struct PublicAccessBlockConfigurationBuilder {
731    block_public_acls: Option<bool>,
732    block_public_policy: Option<bool>,
733    ignore_public_acls: Option<bool>,
734    restrict_public_buckets: Option<bool>,
735}
736
737impl Default for PublicAccessBlockConfigurationBuilder {
738    fn default() -> Self {
739        Self::new()
740    }
741}
742
743impl PublicAccessBlockConfigurationBuilder {
744    pub fn new() -> Self {
745        Self {
746            block_public_acls: None,
747            block_public_policy: None,
748            ignore_public_acls: None,
749            restrict_public_buckets: None,
750        }
751    }
752
753    pub fn block_public_acls(self, config: bool) -> Self {
754        Self {
755            block_public_acls: Some(config),
756            ..self
757        }
758    }
759
760    pub fn block_public_policy(self, config: bool) -> Self {
761        Self {
762            block_public_policy: Some(config),
763            ..self
764        }
765    }
766
767    pub fn ignore_public_acls(self, config: bool) -> Self {
768        Self {
769            ignore_public_acls: Some(config),
770            ..self
771        }
772    }
773
774    pub fn restrict_public_buckets(self, config: bool) -> Self {
775        Self {
776            restrict_public_buckets: Some(config),
777            ..self
778        }
779    }
780
781    #[must_use]
782    pub fn build(self) -> PublicAccessBlockConfiguration {
783        PublicAccessBlockConfiguration {
784            block_public_acls: self.block_public_acls,
785            block_public_policy: self.block_public_policy,
786            ignore_public_acls: self.ignore_public_acls,
787            restrict_public_buckets: self.restrict_public_buckets,
788        }
789    }
790}