use anyhow::Result;
use aws_sdk_s3::types::{
AbortIncompleteMultipartUpload, AccessControlTranslation, BlockedEncryptionTypes,
BucketLifecycleConfiguration, BucketLoggingStatus, BucketLogsPermission, Condition,
CorsConfiguration, CorsRule, DeleteMarkerReplication, DeleteMarkerReplicationStatus,
Destination, EncryptionConfiguration, EncryptionType, ErrorDocument, Event,
EventBridgeConfiguration, ExistingObjectReplication, ExistingObjectReplicationStatus,
ExpirationStatus, FilterRule, FilterRuleName, Grantee, IndexDocument,
LambdaFunctionConfiguration, LifecycleExpiration, LifecycleRule, LifecycleRuleAndOperator,
LifecycleRuleFilter, LoggingEnabled, Metrics, MetricsStatus, NoncurrentVersionExpiration,
NoncurrentVersionTransition, NotificationConfiguration, NotificationConfigurationFilter,
OwnerOverride, PartitionDateSource, PartitionedPrefix, Protocol,
PublicAccessBlockConfiguration, QueueConfiguration, Redirect, RedirectAllRequestsTo,
ReplicaModifications, ReplicaModificationsStatus, ReplicationConfiguration, ReplicationRule,
ReplicationRuleAndOperator, ReplicationRuleFilter, ReplicationRuleStatus, ReplicationTime,
ReplicationTimeStatus, ReplicationTimeValue, RoutingRule, S3KeyFilter, ServerSideEncryption,
ServerSideEncryptionByDefault, ServerSideEncryptionConfiguration, ServerSideEncryptionRule,
SimplePrefix, SourceSelectionCriteria, SseKmsEncryptedObjects, SseKmsEncryptedObjectsStatus,
StorageClass, Tag as SdkTag, TargetGrant, TargetObjectKeyFormat, TopicConfiguration,
Transition, TransitionStorageClass, Type as GranteeType, WebsiteConfiguration,
};
use aws_smithy_types::DateTime;
use serde::Deserialize;
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct LifecycleConfigurationJson {
pub Rules: Vec<LifecycleRuleJson>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct LifecycleRuleJson {
pub ID: Option<String>,
pub Status: String,
pub Prefix: Option<String>,
pub Filter: Option<LifecycleRuleFilterJson>,
pub Expiration: Option<LifecycleExpirationJson>,
pub NoncurrentVersionExpiration: Option<NoncurrentVersionExpirationJson>,
pub Transitions: Option<Vec<TransitionJson>>,
pub NoncurrentVersionTransitions: Option<Vec<NoncurrentVersionTransitionJson>>,
pub AbortIncompleteMultipartUpload: Option<AbortIncompleteMultipartUploadJson>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct LifecycleRuleFilterJson {
pub Prefix: Option<String>,
pub Tag: Option<TagJson>,
pub ObjectSizeGreaterThan: Option<i64>,
pub ObjectSizeLessThan: Option<i64>,
pub And: Option<LifecycleRuleAndOperatorJson>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct LifecycleRuleAndOperatorJson {
pub Prefix: Option<String>,
pub Tags: Option<Vec<TagJson>>,
pub ObjectSizeGreaterThan: Option<i64>,
pub ObjectSizeLessThan: Option<i64>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct TagJson {
pub Key: String,
pub Value: String,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct LifecycleExpirationJson {
pub Date: Option<String>,
pub Days: Option<i32>,
pub ExpiredObjectDeleteMarker: Option<bool>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct NoncurrentVersionExpirationJson {
pub NoncurrentDays: Option<i32>,
pub NewerNoncurrentVersions: Option<i32>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct TransitionJson {
pub Date: Option<String>,
pub Days: Option<i32>,
pub StorageClass: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct NoncurrentVersionTransitionJson {
pub NoncurrentDays: Option<i32>,
pub StorageClass: Option<String>,
pub NewerNoncurrentVersions: Option<i32>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct AbortIncompleteMultipartUploadJson {
pub DaysAfterInitiation: Option<i32>,
}
impl LifecycleConfigurationJson {
pub fn into_sdk(self) -> Result<BucketLifecycleConfiguration> {
let rules: Result<Vec<LifecycleRule>> = self
.Rules
.into_iter()
.map(LifecycleRuleJson::into_sdk)
.collect();
Ok(BucketLifecycleConfiguration::builder()
.set_rules(Some(rules?))
.build()?)
}
}
impl LifecycleRuleJson {
fn into_sdk(self) -> Result<LifecycleRule> {
let mut b = LifecycleRule::builder().status(ExpirationStatus::from(self.Status.as_str()));
if let Some(id) = self.ID {
b = b.id(id);
}
if let Some(p) = self.Prefix {
#[allow(deprecated)]
{
b = b.prefix(p);
}
}
if let Some(f) = self.Filter {
b = b.filter(f.into_sdk()?);
}
if let Some(e) = self.Expiration {
b = b.expiration(e.into_sdk()?);
}
if let Some(n) = self.NoncurrentVersionExpiration {
b = b.noncurrent_version_expiration(n.into_sdk());
}
if let Some(ts) = self.Transitions {
for t in ts {
b = b.transitions(t.into_sdk()?);
}
}
if let Some(nts) = self.NoncurrentVersionTransitions {
for n in nts {
b = b.noncurrent_version_transitions(n.into_sdk());
}
}
if let Some(a) = self.AbortIncompleteMultipartUpload {
b = b.abort_incomplete_multipart_upload(a.into_sdk());
}
Ok(b.build()?)
}
}
impl LifecycleRuleFilterJson {
fn into_sdk(self) -> Result<LifecycleRuleFilter> {
let mut b = LifecycleRuleFilter::builder();
if let Some(p) = self.Prefix {
b = b.prefix(p);
}
if let Some(t) = self.Tag {
b = b.tag(SdkTag::builder().key(t.Key).value(t.Value).build()?);
}
if let Some(n) = self.ObjectSizeGreaterThan {
b = b.object_size_greater_than(n);
}
if let Some(n) = self.ObjectSizeLessThan {
b = b.object_size_less_than(n);
}
if let Some(and) = self.And {
b = b.and(and.into_sdk()?);
}
Ok(b.build())
}
}
impl LifecycleRuleAndOperatorJson {
fn into_sdk(self) -> Result<LifecycleRuleAndOperator> {
let mut b = LifecycleRuleAndOperator::builder();
if let Some(p) = self.Prefix {
b = b.prefix(p);
}
if let Some(tags) = self.Tags {
for t in tags {
b = b.tags(SdkTag::builder().key(t.Key).value(t.Value).build()?);
}
}
if let Some(n) = self.ObjectSizeGreaterThan {
b = b.object_size_greater_than(n);
}
if let Some(n) = self.ObjectSizeLessThan {
b = b.object_size_less_than(n);
}
Ok(b.build())
}
}
impl LifecycleExpirationJson {
fn into_sdk(self) -> Result<LifecycleExpiration> {
let mut b = LifecycleExpiration::builder();
if let Some(d) = self.Date {
b = b.date(parse_rfc3339(&d)?);
}
if let Some(days) = self.Days {
b = b.days(days);
}
if let Some(eodm) = self.ExpiredObjectDeleteMarker {
b = b.expired_object_delete_marker(eodm);
}
Ok(b.build())
}
}
impl NoncurrentVersionExpirationJson {
fn into_sdk(self) -> NoncurrentVersionExpiration {
let mut b = NoncurrentVersionExpiration::builder();
if let Some(n) = self.NoncurrentDays {
b = b.noncurrent_days(n);
}
if let Some(n) = self.NewerNoncurrentVersions {
b = b.newer_noncurrent_versions(n);
}
b.build()
}
}
impl TransitionJson {
fn into_sdk(self) -> Result<Transition> {
let mut b = Transition::builder();
if let Some(d) = self.Date {
b = b.date(parse_rfc3339(&d)?);
}
if let Some(days) = self.Days {
b = b.days(days);
}
if let Some(sc) = self.StorageClass {
b = b.storage_class(TransitionStorageClass::from(sc.as_str()));
}
Ok(b.build())
}
}
impl NoncurrentVersionTransitionJson {
fn into_sdk(self) -> NoncurrentVersionTransition {
let mut b = NoncurrentVersionTransition::builder();
if let Some(n) = self.NoncurrentDays {
b = b.noncurrent_days(n);
}
if let Some(sc) = self.StorageClass {
b = b.storage_class(TransitionStorageClass::from(sc.as_str()));
}
if let Some(n) = self.NewerNoncurrentVersions {
b = b.newer_noncurrent_versions(n);
}
b.build()
}
}
impl AbortIncompleteMultipartUploadJson {
fn into_sdk(self) -> AbortIncompleteMultipartUpload {
let mut b = AbortIncompleteMultipartUpload::builder();
if let Some(d) = self.DaysAfterInitiation {
b = b.days_after_initiation(d);
}
b.build()
}
}
fn parse_rfc3339(s: &str) -> Result<DateTime> {
let bytes = s.as_bytes();
let normalised = if bytes.len() == 10 && bytes[4] == b'-' && bytes[7] == b'-' {
format!("{s}T00:00:00Z")
} else {
s.to_string()
};
DateTime::from_str(&normalised, aws_smithy_types::date_time::Format::DateTime)
.map_err(|e| anyhow::anyhow!("invalid ISO 8601 timestamp {s:?}: {e}"))
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct ServerSideEncryptionConfigurationJson {
pub Rules: Vec<ServerSideEncryptionRuleJson>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct ServerSideEncryptionRuleJson {
pub ApplyServerSideEncryptionByDefault: Option<ApplyServerSideEncryptionByDefaultJson>,
pub BucketKeyEnabled: Option<bool>,
pub BlockedEncryptionTypes: Option<BlockedEncryptionTypesJson>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct ApplyServerSideEncryptionByDefaultJson {
pub SSEAlgorithm: String,
pub KMSMasterKeyID: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct BlockedEncryptionTypesJson {
pub EncryptionType: Option<Vec<String>>,
}
impl ServerSideEncryptionConfigurationJson {
pub fn into_sdk(self) -> Result<ServerSideEncryptionConfiguration> {
let rules: Result<Vec<ServerSideEncryptionRule>> = self
.Rules
.into_iter()
.map(ServerSideEncryptionRuleJson::into_sdk)
.collect();
Ok(ServerSideEncryptionConfiguration::builder()
.set_rules(Some(rules?))
.build()?)
}
}
impl ServerSideEncryptionRuleJson {
fn into_sdk(self) -> Result<ServerSideEncryptionRule> {
let mut b = ServerSideEncryptionRule::builder();
if let Some(d) = self.ApplyServerSideEncryptionByDefault {
let mut bb = ServerSideEncryptionByDefault::builder()
.sse_algorithm(ServerSideEncryption::from(d.SSEAlgorithm.as_str()));
if let Some(k) = d.KMSMasterKeyID {
bb = bb.kms_master_key_id(k);
}
b = b.apply_server_side_encryption_by_default(bb.build()?);
}
if let Some(bke) = self.BucketKeyEnabled {
b = b.bucket_key_enabled(bke);
}
if let Some(bet) = self.BlockedEncryptionTypes {
let mut bb = BlockedEncryptionTypes::builder();
if let Some(types) = bet.EncryptionType {
for t in types {
bb = bb.encryption_type(EncryptionType::from(t.as_str()));
}
}
b = b.blocked_encryption_types(bb.build());
}
Ok(b.build())
}
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct CorsConfigurationJson {
pub CORSRules: Vec<CorsRuleJson>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct CorsRuleJson {
pub ID: Option<String>,
pub AllowedHeaders: Option<Vec<String>>,
pub AllowedMethods: Vec<String>,
pub AllowedOrigins: Vec<String>,
pub ExposeHeaders: Option<Vec<String>>,
pub MaxAgeSeconds: Option<i32>,
}
impl CorsConfigurationJson {
pub fn into_sdk(self) -> Result<CorsConfiguration> {
let rules: Result<Vec<CorsRule>> = self
.CORSRules
.into_iter()
.map(CorsRuleJson::into_sdk)
.collect();
Ok(CorsConfiguration::builder()
.set_cors_rules(Some(rules?))
.build()?)
}
}
impl CorsRuleJson {
fn into_sdk(self) -> Result<CorsRule> {
let mut b = CorsRule::builder()
.set_allowed_methods(Some(self.AllowedMethods))
.set_allowed_origins(Some(self.AllowedOrigins));
if let Some(id) = self.ID {
b = b.id(id);
}
if let Some(h) = self.AllowedHeaders {
b = b.set_allowed_headers(Some(h));
}
if let Some(eh) = self.ExposeHeaders {
b = b.set_expose_headers(Some(eh));
}
if let Some(m) = self.MaxAgeSeconds {
b = b.max_age_seconds(m);
}
Ok(b.build()?)
}
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct PublicAccessBlockConfigurationJson {
pub BlockPublicAcls: Option<bool>,
pub IgnorePublicAcls: Option<bool>,
pub BlockPublicPolicy: Option<bool>,
pub RestrictPublicBuckets: Option<bool>,
}
impl PublicAccessBlockConfigurationJson {
pub fn into_sdk(self) -> Result<PublicAccessBlockConfiguration> {
Ok(PublicAccessBlockConfiguration::builder()
.block_public_acls(self.BlockPublicAcls.unwrap_or(false))
.ignore_public_acls(self.IgnorePublicAcls.unwrap_or(false))
.block_public_policy(self.BlockPublicPolicy.unwrap_or(false))
.restrict_public_buckets(self.RestrictPublicBuckets.unwrap_or(false))
.build())
}
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct WebsiteConfigurationJson {
pub IndexDocument: Option<IndexDocumentJson>,
pub ErrorDocument: Option<ErrorDocumentJson>,
pub RedirectAllRequestsTo: Option<RedirectAllRequestsToJson>,
pub RoutingRules: Option<Vec<RoutingRuleJson>>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct IndexDocumentJson {
pub Suffix: String,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct ErrorDocumentJson {
pub Key: String,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct RedirectAllRequestsToJson {
pub HostName: String,
pub Protocol: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct RoutingRuleJson {
pub Condition: Option<ConditionJson>,
pub Redirect: RedirectJson,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct ConditionJson {
pub HttpErrorCodeReturnedEquals: Option<String>,
pub KeyPrefixEquals: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct RedirectJson {
pub HostName: Option<String>,
pub HttpRedirectCode: Option<String>,
pub Protocol: Option<String>,
pub ReplaceKeyPrefixWith: Option<String>,
pub ReplaceKeyWith: Option<String>,
}
impl WebsiteConfigurationJson {
pub fn into_sdk(self) -> Result<WebsiteConfiguration> {
let mut b = WebsiteConfiguration::builder();
if let Some(ix) = self.IndexDocument {
b = b.index_document(IndexDocument::builder().suffix(ix.Suffix).build()?);
}
if let Some(err) = self.ErrorDocument {
b = b.error_document(ErrorDocument::builder().key(err.Key).build()?);
}
if let Some(r) = self.RedirectAllRequestsTo {
let mut rb = RedirectAllRequestsTo::builder().host_name(r.HostName);
if let Some(p) = r.Protocol {
rb = rb.protocol(Protocol::from(p.as_str()));
}
b = b.redirect_all_requests_to(rb.build()?);
}
if let Some(rules) = self.RoutingRules {
for rr in rules {
b = b.routing_rules(rr.into_sdk()?);
}
}
Ok(b.build())
}
}
impl RoutingRuleJson {
fn into_sdk(self) -> Result<RoutingRule> {
let mut b = RoutingRule::builder().redirect(self.Redirect.into_sdk());
if let Some(c) = self.Condition {
let mut cb = Condition::builder();
if let Some(code) = c.HttpErrorCodeReturnedEquals {
cb = cb.http_error_code_returned_equals(code);
}
if let Some(prefix) = c.KeyPrefixEquals {
cb = cb.key_prefix_equals(prefix);
}
b = b.condition(cb.build());
}
Ok(b.build())
}
}
impl RedirectJson {
fn into_sdk(self) -> Redirect {
let mut b = Redirect::builder();
if let Some(h) = self.HostName {
b = b.host_name(h);
}
if let Some(c) = self.HttpRedirectCode {
b = b.http_redirect_code(c);
}
if let Some(p) = self.Protocol {
b = b.protocol(Protocol::from(p.as_str()));
}
if let Some(p) = self.ReplaceKeyPrefixWith {
b = b.replace_key_prefix_with(p);
}
if let Some(k) = self.ReplaceKeyWith {
b = b.replace_key_with(k);
}
b.build()
}
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct BucketLoggingStatusJson {
pub LoggingEnabled: Option<LoggingEnabledJson>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct LoggingEnabledJson {
pub TargetBucket: String,
pub TargetPrefix: String,
pub TargetObjectKeyFormat: Option<TargetObjectKeyFormatJson>,
pub TargetGrants: Option<Vec<TargetGrantJson>>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct TargetGrantJson {
pub Grantee: Option<GranteeJson>,
pub Permission: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct GranteeJson {
pub DisplayName: Option<String>,
pub EmailAddress: Option<String>,
pub ID: Option<String>,
pub URI: Option<String>,
pub Type: String,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct TargetObjectKeyFormatJson {
pub SimplePrefix: Option<SimplePrefixJson>,
pub PartitionedPrefix: Option<PartitionedPrefixJson>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct SimplePrefixJson {}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct PartitionedPrefixJson {
pub PartitionDateSource: Option<String>,
}
impl BucketLoggingStatusJson {
pub fn into_sdk(self) -> Result<BucketLoggingStatus> {
let mut b = BucketLoggingStatus::builder();
if let Some(le) = self.LoggingEnabled {
b = b.logging_enabled(le.into_sdk()?);
}
Ok(b.build())
}
}
impl LoggingEnabledJson {
fn into_sdk(self) -> Result<LoggingEnabled> {
let mut b = LoggingEnabled::builder()
.target_bucket(self.TargetBucket)
.target_prefix(self.TargetPrefix);
if let Some(fmt) = self.TargetObjectKeyFormat {
b = b.target_object_key_format(fmt.into_sdk());
}
if let Some(grants) = self.TargetGrants {
for g in grants {
b = b.target_grants(g.into_sdk()?);
}
}
Ok(b.build()?)
}
}
impl TargetGrantJson {
fn into_sdk(self) -> Result<TargetGrant> {
let mut b = TargetGrant::builder();
if let Some(g) = self.Grantee {
b = b.grantee(g.into_sdk()?);
}
if let Some(p) = self.Permission {
b = b.permission(BucketLogsPermission::from(p.as_str()));
}
Ok(b.build())
}
}
impl GranteeJson {
fn into_sdk(self) -> Result<Grantee> {
let mut b = Grantee::builder().r#type(GranteeType::from(self.Type.as_str()));
if let Some(v) = self.DisplayName {
b = b.display_name(v);
}
if let Some(v) = self.EmailAddress {
b = b.email_address(v);
}
if let Some(v) = self.ID {
b = b.id(v);
}
if let Some(v) = self.URI {
b = b.uri(v);
}
Ok(b.build()?)
}
}
impl TargetObjectKeyFormatJson {
fn into_sdk(self) -> TargetObjectKeyFormat {
let mut b = TargetObjectKeyFormat::builder();
if self.SimplePrefix.is_some() {
b = b.simple_prefix(SimplePrefix::builder().build());
}
if let Some(pp) = self.PartitionedPrefix {
b = b.partitioned_prefix(pp.into_sdk());
}
b.build()
}
}
impl PartitionedPrefixJson {
fn into_sdk(self) -> PartitionedPrefix {
let mut b = PartitionedPrefix::builder();
if let Some(s) = self.PartitionDateSource {
b = b.partition_date_source(PartitionDateSource::from(s.as_str()));
}
b.build()
}
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct NotificationConfigurationJson {
pub TopicConfigurations: Option<Vec<TopicConfigurationJson>>,
pub QueueConfigurations: Option<Vec<QueueConfigurationJson>>,
pub LambdaFunctionConfigurations: Option<Vec<LambdaFunctionConfigurationJson>>,
pub EventBridgeConfiguration: Option<EventBridgeConfigurationJson>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct TopicConfigurationJson {
pub Id: Option<String>,
pub TopicArn: String,
pub Events: Vec<String>,
pub Filter: Option<NotificationConfigurationFilterJson>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct QueueConfigurationJson {
pub Id: Option<String>,
pub QueueArn: String,
pub Events: Vec<String>,
pub Filter: Option<NotificationConfigurationFilterJson>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct LambdaFunctionConfigurationJson {
pub Id: Option<String>,
pub LambdaFunctionArn: String,
pub Events: Vec<String>,
pub Filter: Option<NotificationConfigurationFilterJson>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct EventBridgeConfigurationJson {}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct NotificationConfigurationFilterJson {
pub Key: Option<S3KeyFilterJson>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct S3KeyFilterJson {
pub FilterRules: Option<Vec<FilterRuleJson>>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct FilterRuleJson {
pub Name: String,
pub Value: String,
}
impl NotificationConfigurationJson {
pub fn into_sdk(self) -> Result<NotificationConfiguration> {
let mut b = NotificationConfiguration::builder();
if let Some(topics) = self.TopicConfigurations {
let v: Result<Vec<TopicConfiguration>> = topics
.into_iter()
.map(TopicConfigurationJson::into_sdk)
.collect();
b = b.set_topic_configurations(Some(v?));
}
if let Some(queues) = self.QueueConfigurations {
let v: Result<Vec<QueueConfiguration>> = queues
.into_iter()
.map(QueueConfigurationJson::into_sdk)
.collect();
b = b.set_queue_configurations(Some(v?));
}
if let Some(lambdas) = self.LambdaFunctionConfigurations {
let v: Result<Vec<LambdaFunctionConfiguration>> = lambdas
.into_iter()
.map(LambdaFunctionConfigurationJson::into_sdk)
.collect();
b = b.set_lambda_function_configurations(Some(v?));
}
if self.EventBridgeConfiguration.is_some() {
b = b.event_bridge_configuration(EventBridgeConfiguration::builder().build());
}
Ok(b.build())
}
}
impl TopicConfigurationJson {
fn into_sdk(self) -> Result<TopicConfiguration> {
let events: Vec<Event> = self
.Events
.iter()
.map(|s| Event::from(s.as_str()))
.collect();
let mut b = TopicConfiguration::builder()
.topic_arn(self.TopicArn)
.set_events(Some(events));
if let Some(id) = self.Id {
b = b.id(id);
}
if let Some(f) = self.Filter {
b = b.filter(f.into_sdk());
}
Ok(b.build()?)
}
}
impl QueueConfigurationJson {
fn into_sdk(self) -> Result<QueueConfiguration> {
let events: Vec<Event> = self
.Events
.iter()
.map(|s| Event::from(s.as_str()))
.collect();
let mut b = QueueConfiguration::builder()
.queue_arn(self.QueueArn)
.set_events(Some(events));
if let Some(id) = self.Id {
b = b.id(id);
}
if let Some(f) = self.Filter {
b = b.filter(f.into_sdk());
}
Ok(b.build()?)
}
}
impl LambdaFunctionConfigurationJson {
fn into_sdk(self) -> Result<LambdaFunctionConfiguration> {
let events: Vec<Event> = self
.Events
.iter()
.map(|s| Event::from(s.as_str()))
.collect();
let mut b = LambdaFunctionConfiguration::builder()
.lambda_function_arn(self.LambdaFunctionArn)
.set_events(Some(events));
if let Some(id) = self.Id {
b = b.id(id);
}
if let Some(f) = self.Filter {
b = b.filter(f.into_sdk());
}
Ok(b.build()?)
}
}
impl NotificationConfigurationFilterJson {
fn into_sdk(self) -> NotificationConfigurationFilter {
let mut b = NotificationConfigurationFilter::builder();
if let Some(k) = self.Key {
b = b.key(k.into_sdk());
}
b.build()
}
}
impl S3KeyFilterJson {
fn into_sdk(self) -> S3KeyFilter {
let mut b = S3KeyFilter::builder();
if let Some(rules) = self.FilterRules {
for r in rules {
b = b.filter_rules(r.into_sdk());
}
}
b.build()
}
}
impl FilterRuleJson {
fn into_sdk(self) -> FilterRule {
FilterRule::builder()
.name(FilterRuleName::from(self.Name.as_str()))
.value(self.Value)
.build()
}
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct ReplicationConfigurationJson {
pub Role: String,
pub Rules: Vec<ReplicationRuleJson>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct ReplicationRuleJson {
pub ID: Option<String>,
pub Priority: Option<i32>,
pub Prefix: Option<String>,
pub Filter: Option<ReplicationRuleFilterJson>,
pub Status: String,
pub SourceSelectionCriteria: Option<SourceSelectionCriteriaJson>,
pub ExistingObjectReplication: Option<ExistingObjectReplicationJson>,
pub Destination: DestinationJson,
pub DeleteMarkerReplication: Option<DeleteMarkerReplicationJson>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct ReplicationRuleFilterJson {
pub Prefix: Option<String>,
pub Tag: Option<TagJson>,
pub And: Option<ReplicationRuleAndOperatorJson>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct ReplicationRuleAndOperatorJson {
pub Prefix: Option<String>,
pub Tags: Option<Vec<TagJson>>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct SourceSelectionCriteriaJson {
pub SseKmsEncryptedObjects: Option<SseKmsEncryptedObjectsJson>,
pub ReplicaModifications: Option<ReplicaModificationsJson>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct SseKmsEncryptedObjectsJson {
pub Status: String,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct ReplicaModificationsJson {
pub Status: String,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct ExistingObjectReplicationJson {
pub Status: String,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct DestinationJson {
pub Bucket: String,
pub Account: Option<String>,
pub StorageClass: Option<String>,
pub AccessControlTranslation: Option<AccessControlTranslationJson>,
pub EncryptionConfiguration: Option<EncryptionConfigurationJson>,
pub ReplicationTime: Option<ReplicationTimeJson>,
pub Metrics: Option<MetricsJson>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct AccessControlTranslationJson {
pub Owner: String,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct EncryptionConfigurationJson {
pub ReplicaKmsKeyID: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct ReplicationTimeJson {
pub Status: String,
pub Time: ReplicationTimeValueJson,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct ReplicationTimeValueJson {
pub Minutes: i32,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct MetricsJson {
pub Status: String,
pub EventThreshold: Option<ReplicationTimeValueJson>,
}
#[derive(Debug, Clone, Deserialize)]
#[allow(non_snake_case)]
pub struct DeleteMarkerReplicationJson {
pub Status: String,
}
impl ReplicationConfigurationJson {
pub fn into_sdk(self) -> Result<ReplicationConfiguration> {
let rules: Result<Vec<ReplicationRule>> = self
.Rules
.into_iter()
.map(ReplicationRuleJson::into_sdk)
.collect();
Ok(ReplicationConfiguration::builder()
.role(self.Role)
.set_rules(Some(rules?))
.build()?)
}
}
impl ReplicationRuleJson {
#[allow(deprecated)]
fn into_sdk(self) -> Result<ReplicationRule> {
let mut b = ReplicationRule::builder()
.status(ReplicationRuleStatus::from(self.Status.as_str()))
.destination(self.Destination.into_sdk()?);
if let Some(id) = self.ID {
b = b.id(id);
}
if let Some(p) = self.Priority {
b = b.priority(p);
}
if let Some(prefix) = self.Prefix {
b = b.prefix(prefix);
}
if let Some(f) = self.Filter {
b = b.filter(f.into_sdk()?);
}
if let Some(ssc) = self.SourceSelectionCriteria {
b = b.source_selection_criteria(ssc.into_sdk()?);
}
if let Some(eor) = self.ExistingObjectReplication {
b = b.existing_object_replication(
ExistingObjectReplication::builder()
.status(ExistingObjectReplicationStatus::from(eor.Status.as_str()))
.build()?,
);
}
if let Some(dmr) = self.DeleteMarkerReplication {
b = b.delete_marker_replication(
DeleteMarkerReplication::builder()
.status(DeleteMarkerReplicationStatus::from(dmr.Status.as_str()))
.build(),
);
}
Ok(b.build()?)
}
}
impl ReplicationRuleFilterJson {
fn into_sdk(self) -> Result<ReplicationRuleFilter> {
let mut b = ReplicationRuleFilter::builder();
if let Some(prefix) = self.Prefix {
b = b.prefix(prefix);
}
if let Some(t) = self.Tag {
b = b.tag(SdkTag::builder().key(t.Key).value(t.Value).build()?);
}
if let Some(a) = self.And {
let mut ab = ReplicationRuleAndOperator::builder();
if let Some(prefix) = a.Prefix {
ab = ab.prefix(prefix);
}
if let Some(tags) = a.Tags {
let sdk_tags: Result<Vec<SdkTag>> = tags
.into_iter()
.map(|t| Ok(SdkTag::builder().key(t.Key).value(t.Value).build()?))
.collect();
ab = ab.set_tags(Some(sdk_tags?));
}
b = b.and(ab.build());
}
Ok(b.build())
}
}
impl SourceSelectionCriteriaJson {
fn into_sdk(self) -> Result<SourceSelectionCriteria> {
let mut b = SourceSelectionCriteria::builder();
if let Some(sse) = self.SseKmsEncryptedObjects {
b = b.sse_kms_encrypted_objects(
SseKmsEncryptedObjects::builder()
.status(SseKmsEncryptedObjectsStatus::from(sse.Status.as_str()))
.build()?,
);
}
if let Some(rm) = self.ReplicaModifications {
b = b.replica_modifications(
ReplicaModifications::builder()
.status(ReplicaModificationsStatus::from(rm.Status.as_str()))
.build()?,
);
}
Ok(b.build())
}
}
impl DestinationJson {
fn into_sdk(self) -> Result<Destination> {
let mut b = Destination::builder().bucket(self.Bucket);
if let Some(a) = self.Account {
b = b.account(a);
}
if let Some(sc) = self.StorageClass {
b = b.storage_class(StorageClass::from(sc.as_str()));
}
if let Some(act) = self.AccessControlTranslation {
b = b.access_control_translation(
AccessControlTranslation::builder()
.owner(OwnerOverride::from(act.Owner.as_str()))
.build()?,
);
}
if let Some(ec) = self.EncryptionConfiguration {
let mut eb = EncryptionConfiguration::builder();
if let Some(k) = ec.ReplicaKmsKeyID {
eb = eb.replica_kms_key_id(k);
}
b = b.encryption_configuration(eb.build());
}
if let Some(rt) = self.ReplicationTime {
b = b.replication_time(
ReplicationTime::builder()
.status(ReplicationTimeStatus::from(rt.Status.as_str()))
.time(
ReplicationTimeValue::builder()
.minutes(rt.Time.Minutes)
.build(),
)
.build()?,
);
}
if let Some(metrics) = self.Metrics {
let mut mb = Metrics::builder().status(MetricsStatus::from(metrics.Status.as_str()));
if let Some(et) = metrics.EventThreshold {
mb =
mb.event_threshold(ReplicationTimeValue::builder().minutes(et.Minutes).build());
}
b = b.metrics(mb.build()?);
}
Ok(b.build()?)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lifecycle_parses_aws_cli_skeleton_shape() {
let json = r#"{
"Rules": [
{
"ID": "ExpireOldLogs",
"Status": "Enabled",
"Filter": { "Prefix": "logs/" },
"Expiration": { "Days": 365 }
}
]
}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).expect("parses");
assert_eq!(parsed.Rules.len(), 1);
assert_eq!(parsed.Rules[0].ID.as_deref(), Some("ExpireOldLogs"));
}
#[test]
fn lifecycle_into_sdk_preserves_id_and_status() {
let json = r#"{"Rules":[{"ID":"r1","Status":"Enabled","Expiration":{"Days":1}}]}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let rules = cfg.rules();
assert_eq!(rules.len(), 1);
assert_eq!(rules[0].id(), Some("r1"));
assert_eq!(rules[0].status(), &ExpirationStatus::Enabled);
}
#[test]
fn lifecycle_into_sdk_preserves_filter_prefix() {
let json = r#"{"Rules":[{"Status":"Enabled","Filter":{"Prefix":"logs/"},"Expiration":{"Days":1}}]}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let f = cfg.rules()[0].filter().expect("filter");
assert_eq!(f.prefix(), Some("logs/"));
}
#[test]
fn lifecycle_into_sdk_preserves_filter_and_with_tags() {
let json = r#"{
"Rules":[{
"Status":"Enabled",
"Filter":{"And":{"Prefix":"x/","Tags":[{"Key":"a","Value":"1"}]}},
"Expiration":{"Days":1}
}]
}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let and = cfg.rules()[0].filter().unwrap().and().expect("and");
assert_eq!(and.prefix(), Some("x/"));
assert_eq!(and.tags().len(), 1);
assert_eq!(and.tags()[0].key(), "a");
assert_eq!(and.tags()[0].value(), "1");
}
#[test]
fn lifecycle_into_sdk_preserves_transitions() {
let json = r#"{
"Rules":[{
"Status":"Enabled",
"Transitions":[{"Days":30,"StorageClass":"GLACIER"}],
"Expiration":{"Days":365}
}]
}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let t = &cfg.rules()[0].transitions()[0];
assert_eq!(t.days(), Some(30));
assert_eq!(t.storage_class(), Some(&TransitionStorageClass::Glacier));
}
#[test]
fn lifecycle_into_sdk_preserves_noncurrent_version_expiration() {
let json = r#"{"Rules":[{"Status":"Enabled","NoncurrentVersionExpiration":{"NoncurrentDays":7}}]}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let n = cfg.rules()[0]
.noncurrent_version_expiration()
.expect("noncurrent version expiration");
assert_eq!(n.noncurrent_days(), Some(7));
}
#[test]
fn lifecycle_into_sdk_preserves_abort_incomplete_multipart_upload() {
let json = r#"{"Rules":[{"Status":"Enabled","AbortIncompleteMultipartUpload":{"DaysAfterInitiation":3}}]}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let a = cfg.rules()[0]
.abort_incomplete_multipart_upload()
.expect("abort");
assert_eq!(a.days_after_initiation(), Some(3));
}
#[test]
fn lifecycle_invalid_json_errors() {
let res: Result<LifecycleConfigurationJson, _> = serde_json::from_str("{not json");
assert!(res.is_err());
}
#[test]
fn lifecycle_missing_rules_errors() {
let res: Result<LifecycleConfigurationJson, _> = serde_json::from_str("{}");
assert!(res.is_err(), "missing required `Rules` must error");
}
#[test]
fn lifecycle_invalid_date_errors_at_into_sdk() {
let json = r#"{"Rules":[{"Status":"Enabled","Expiration":{"Date":"not-a-date"}}]}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let res = parsed.into_sdk();
assert!(res.is_err(), "invalid date must error at into_sdk()");
}
#[test]
fn encryption_parses_aws_cli_skeleton_shape() {
let json = r#"{
"Rules": [
{ "ApplyServerSideEncryptionByDefault": { "SSEAlgorithm": "AES256" } }
]
}"#;
let parsed: ServerSideEncryptionConfigurationJson = serde_json::from_str(json).unwrap();
assert_eq!(parsed.Rules.len(), 1);
}
#[test]
fn encryption_into_sdk_preserves_aes256() {
let json =
r#"{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}"#;
let parsed: ServerSideEncryptionConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let r = &cfg.rules()[0];
assert_eq!(
r.apply_server_side_encryption_by_default()
.unwrap()
.sse_algorithm(),
&ServerSideEncryption::Aes256
);
}
#[test]
fn encryption_into_sdk_preserves_kms_with_key_id() {
let json = r#"{
"Rules":[{
"ApplyServerSideEncryptionByDefault":{
"SSEAlgorithm":"aws:kms",
"KMSMasterKeyID":"arn:aws:kms:us-east-1:111111111111:key/abc"
},
"BucketKeyEnabled": true
}]
}"#;
let parsed: ServerSideEncryptionConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let r = &cfg.rules()[0];
let d = r.apply_server_side_encryption_by_default().unwrap();
assert_eq!(d.sse_algorithm(), &ServerSideEncryption::AwsKms);
assert_eq!(
d.kms_master_key_id(),
Some("arn:aws:kms:us-east-1:111111111111:key/abc")
);
assert_eq!(r.bucket_key_enabled(), Some(true));
}
#[test]
fn encryption_invalid_json_errors() {
assert!(
serde_json::from_str::<ServerSideEncryptionConfigurationJson>("{not json").is_err()
);
}
#[test]
fn encryption_missing_rules_errors() {
assert!(serde_json::from_str::<ServerSideEncryptionConfigurationJson>("{}").is_err());
}
#[test]
fn cors_parses_aws_cli_skeleton_shape() {
let json = r#"{
"CORSRules": [
{
"AllowedMethods": ["GET", "HEAD"],
"AllowedOrigins": ["*"],
"AllowedHeaders": ["*"],
"MaxAgeSeconds": 3000
}
]
}"#;
let parsed: CorsConfigurationJson = serde_json::from_str(json).unwrap();
assert_eq!(parsed.CORSRules.len(), 1);
}
#[test]
fn cors_into_sdk_preserves_methods_and_origins() {
let json = r#"{"CORSRules":[{"AllowedMethods":["GET"],"AllowedOrigins":["https://example.com"]}]}"#;
let parsed: CorsConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let r = &cfg.cors_rules()[0];
assert_eq!(r.allowed_methods(), &["GET".to_string()]);
assert_eq!(r.allowed_origins(), &["https://example.com".to_string()]);
}
#[test]
fn cors_into_sdk_preserves_max_age_and_id() {
let json = r#"{"CORSRules":[{"ID":"r1","AllowedMethods":["GET"],"AllowedOrigins":["*"],"MaxAgeSeconds":600}]}"#;
let parsed: CorsConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let r = &cfg.cors_rules()[0];
assert_eq!(r.id(), Some("r1"));
assert_eq!(r.max_age_seconds(), Some(600));
}
#[test]
fn cors_into_sdk_preserves_expose_headers() {
let json = r#"{"CORSRules":[{"AllowedMethods":["GET"],"AllowedOrigins":["*"],"ExposeHeaders":["x-amz-id-2"]}]}"#;
let parsed: CorsConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
assert_eq!(
cfg.cors_rules()[0].expose_headers(),
&["x-amz-id-2".to_string()]
);
}
#[test]
fn cors_invalid_json_errors() {
assert!(serde_json::from_str::<CorsConfigurationJson>("{not json").is_err());
}
#[test]
fn cors_missing_cors_rules_errors() {
assert!(serde_json::from_str::<CorsConfigurationJson>("{}").is_err());
}
#[test]
fn pab_parses_aws_cli_skeleton_shape() {
let json = r#"{
"BlockPublicAcls": true,
"IgnorePublicAcls": true,
"BlockPublicPolicy": true,
"RestrictPublicBuckets": true
}"#;
let parsed: PublicAccessBlockConfigurationJson = serde_json::from_str(json).unwrap();
assert_eq!(parsed.BlockPublicAcls, Some(true));
assert_eq!(parsed.RestrictPublicBuckets, Some(true));
}
#[test]
fn pab_into_sdk_all_true() {
let json = r#"{"BlockPublicAcls":true,"IgnorePublicAcls":true,"BlockPublicPolicy":true,"RestrictPublicBuckets":true}"#;
let parsed: PublicAccessBlockConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
assert_eq!(cfg.block_public_acls(), Some(true));
assert_eq!(cfg.ignore_public_acls(), Some(true));
assert_eq!(cfg.block_public_policy(), Some(true));
assert_eq!(cfg.restrict_public_buckets(), Some(true));
}
#[test]
fn pab_into_sdk_absent_fields_default_to_false() {
let json = r#"{}"#;
let parsed: PublicAccessBlockConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
assert_eq!(cfg.block_public_acls(), Some(false));
assert_eq!(cfg.ignore_public_acls(), Some(false));
assert_eq!(cfg.block_public_policy(), Some(false));
assert_eq!(cfg.restrict_public_buckets(), Some(false));
}
#[test]
fn pab_into_sdk_partial_input() {
let json = r#"{"BlockPublicAcls":true,"BlockPublicPolicy":true}"#;
let parsed: PublicAccessBlockConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
assert_eq!(cfg.block_public_acls(), Some(true));
assert_eq!(cfg.ignore_public_acls(), Some(false));
assert_eq!(cfg.block_public_policy(), Some(true));
assert_eq!(cfg.restrict_public_buckets(), Some(false));
}
#[test]
fn pab_invalid_json_errors() {
assert!(serde_json::from_str::<PublicAccessBlockConfigurationJson>("{not json").is_err());
}
#[test]
fn website_parses_minimal_index_document_shape() {
let json = r#"{
"IndexDocument": { "Suffix": "index.html" }
}"#;
let parsed: WebsiteConfigurationJson = serde_json::from_str(json).unwrap();
assert_eq!(
parsed.IndexDocument.as_ref().map(|d| d.Suffix.as_str()),
Some("index.html")
);
assert!(parsed.ErrorDocument.is_none());
assert!(parsed.RedirectAllRequestsTo.is_none());
assert!(parsed.RoutingRules.is_none());
}
#[test]
fn website_into_sdk_preserves_index_document() {
let json = r#"{"IndexDocument":{"Suffix":"index.html"}}"#;
let parsed: WebsiteConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
assert_eq!(cfg.index_document().map(|d| d.suffix()), Some("index.html"));
assert!(cfg.error_document().is_none());
assert!(cfg.redirect_all_requests_to().is_none());
assert!(cfg.routing_rules().is_empty());
}
#[test]
fn website_into_sdk_preserves_index_and_error_documents() {
let json = r#"{
"IndexDocument": { "Suffix": "index.html" },
"ErrorDocument": { "Key": "error.html" }
}"#;
let parsed: WebsiteConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
assert_eq!(cfg.index_document().map(|d| d.suffix()), Some("index.html"));
assert_eq!(cfg.error_document().map(|d| d.key()), Some("error.html"));
}
#[test]
fn website_into_sdk_preserves_redirect_all_requests_to_with_protocol() {
let json = r#"{
"RedirectAllRequestsTo": { "HostName": "example.com", "Protocol": "https" }
}"#;
let parsed: WebsiteConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let r = cfg.redirect_all_requests_to().expect("redirect");
assert_eq!(r.host_name(), "example.com");
assert_eq!(
r.protocol().map(aws_sdk_s3::types::Protocol::as_str),
Some("https")
);
}
#[test]
fn website_into_sdk_preserves_redirect_all_requests_to_without_protocol() {
let json = r#"{
"RedirectAllRequestsTo": { "HostName": "example.com" }
}"#;
let parsed: WebsiteConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let r = cfg.redirect_all_requests_to().expect("redirect");
assert_eq!(r.host_name(), "example.com");
assert!(r.protocol().is_none());
}
#[test]
fn website_into_sdk_preserves_routing_rules_with_condition_and_redirect() {
let json = r#"{
"IndexDocument": { "Suffix": "index.html" },
"RoutingRules": [
{
"Condition": {
"HttpErrorCodeReturnedEquals": "404",
"KeyPrefixEquals": "docs/"
},
"Redirect": {
"HostName": "new.example.com",
"HttpRedirectCode": "301",
"Protocol": "https",
"ReplaceKeyPrefixWith": "documents/"
}
}
]
}"#;
let parsed: WebsiteConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let rules = cfg.routing_rules();
assert_eq!(rules.len(), 1);
let cond = rules[0].condition().expect("condition");
assert_eq!(cond.http_error_code_returned_equals(), Some("404"));
assert_eq!(cond.key_prefix_equals(), Some("docs/"));
let red = rules[0].redirect().expect("redirect");
assert_eq!(red.host_name(), Some("new.example.com"));
assert_eq!(red.http_redirect_code(), Some("301"));
assert_eq!(
red.protocol().map(aws_sdk_s3::types::Protocol::as_str),
Some("https")
);
assert_eq!(red.replace_key_prefix_with(), Some("documents/"));
assert!(red.replace_key_with().is_none());
}
#[test]
fn website_into_sdk_routing_rule_redirect_only_with_replace_key_with() {
let json = r#"{
"IndexDocument": { "Suffix": "index.html" },
"RoutingRules": [
{ "Redirect": { "ReplaceKeyWith": "error.html" } }
]
}"#;
let parsed: WebsiteConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let rules = cfg.routing_rules();
assert_eq!(rules.len(), 1);
assert!(rules[0].condition().is_none());
let red = rules[0].redirect().expect("redirect");
assert_eq!(red.replace_key_with(), Some("error.html"));
}
#[test]
fn website_empty_object_parses_and_sdk_is_empty() {
let json = r#"{}"#;
let parsed: WebsiteConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
assert!(cfg.index_document().is_none());
assert!(cfg.error_document().is_none());
assert!(cfg.redirect_all_requests_to().is_none());
assert!(cfg.routing_rules().is_empty());
}
#[test]
fn website_invalid_json_errors() {
assert!(serde_json::from_str::<WebsiteConfigurationJson>("{not json").is_err());
}
#[test]
fn logging_empty_object_parses_and_sdk_is_empty() {
let json = r#"{}"#;
let parsed: BucketLoggingStatusJson = serde_json::from_str(json).unwrap();
assert!(parsed.LoggingEnabled.is_none());
let cfg = parsed.into_sdk().unwrap();
assert!(cfg.logging_enabled().is_none());
}
#[test]
fn logging_parses_minimal_enabled_shape() {
let json = r#"{
"LoggingEnabled": {
"TargetBucket": "log-bucket",
"TargetPrefix": "logs/"
}
}"#;
let parsed: BucketLoggingStatusJson = serde_json::from_str(json).unwrap();
let le = parsed.LoggingEnabled.as_ref().expect("LoggingEnabled");
assert_eq!(le.TargetBucket, "log-bucket");
assert_eq!(le.TargetPrefix, "logs/");
assert!(le.TargetObjectKeyFormat.is_none());
}
#[test]
fn logging_into_sdk_preserves_target_bucket_and_prefix() {
let json = r#"{"LoggingEnabled":{"TargetBucket":"log-bucket","TargetPrefix":"logs/"}}"#;
let parsed: BucketLoggingStatusJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let le = cfg.logging_enabled().expect("logging_enabled");
assert_eq!(le.target_bucket(), "log-bucket");
assert_eq!(le.target_prefix(), "logs/");
assert!(le.target_object_key_format().is_none());
}
#[test]
fn logging_into_sdk_preserves_simple_prefix_marker() {
let json = r#"{
"LoggingEnabled": {
"TargetBucket": "log-bucket",
"TargetPrefix": "logs/",
"TargetObjectKeyFormat": { "SimplePrefix": {} }
}
}"#;
let parsed: BucketLoggingStatusJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let fmt = cfg
.logging_enabled()
.unwrap()
.target_object_key_format()
.expect("target_object_key_format");
assert!(fmt.simple_prefix().is_some());
assert!(fmt.partitioned_prefix().is_none());
}
#[test]
fn logging_into_sdk_preserves_partitioned_prefix_with_event_time() {
let json = r#"{
"LoggingEnabled": {
"TargetBucket": "log-bucket",
"TargetPrefix": "logs/",
"TargetObjectKeyFormat": {
"PartitionedPrefix": { "PartitionDateSource": "EventTime" }
}
}
}"#;
let parsed: BucketLoggingStatusJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let pp = cfg
.logging_enabled()
.unwrap()
.target_object_key_format()
.unwrap()
.partitioned_prefix()
.expect("partitioned_prefix");
assert_eq!(
pp.partition_date_source().map(PartitionDateSource::as_str),
Some("EventTime")
);
}
#[test]
fn logging_into_sdk_preserves_partitioned_prefix_with_delivery_time() {
let json = r#"{
"LoggingEnabled": {
"TargetBucket": "log-bucket",
"TargetPrefix": "logs/",
"TargetObjectKeyFormat": {
"PartitionedPrefix": { "PartitionDateSource": "DeliveryTime" }
}
}
}"#;
let parsed: BucketLoggingStatusJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let pp = cfg
.logging_enabled()
.unwrap()
.target_object_key_format()
.unwrap()
.partitioned_prefix()
.expect("partitioned_prefix");
assert_eq!(
pp.partition_date_source().map(PartitionDateSource::as_str),
Some("DeliveryTime")
);
}
#[test]
fn logging_into_sdk_partitioned_prefix_without_date_source() {
let json = r#"{
"LoggingEnabled": {
"TargetBucket": "log-bucket",
"TargetPrefix": "logs/",
"TargetObjectKeyFormat": { "PartitionedPrefix": {} }
}
}"#;
let parsed: BucketLoggingStatusJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let pp = cfg
.logging_enabled()
.unwrap()
.target_object_key_format()
.unwrap()
.partitioned_prefix()
.expect("partitioned_prefix");
assert!(pp.partition_date_source().is_none());
}
#[test]
fn logging_missing_target_bucket_errors_at_parse() {
let json = r#"{"LoggingEnabled":{"TargetPrefix":"logs/"}}"#;
assert!(serde_json::from_str::<BucketLoggingStatusJson>(json).is_err());
}
#[test]
fn logging_missing_target_prefix_errors_at_parse() {
let json = r#"{"LoggingEnabled":{"TargetBucket":"log-bucket"}}"#;
assert!(serde_json::from_str::<BucketLoggingStatusJson>(json).is_err());
}
#[test]
fn logging_invalid_json_errors() {
assert!(serde_json::from_str::<BucketLoggingStatusJson>("{not json").is_err());
}
#[test]
fn notification_empty_object_parses_and_sdk_is_empty() {
let json = r#"{}"#;
let parsed: NotificationConfigurationJson = serde_json::from_str(json).unwrap();
assert!(parsed.TopicConfigurations.is_none());
assert!(parsed.QueueConfigurations.is_none());
assert!(parsed.LambdaFunctionConfigurations.is_none());
assert!(parsed.EventBridgeConfiguration.is_none());
let cfg = parsed.into_sdk().unwrap();
assert!(cfg.topic_configurations.is_none());
assert!(cfg.queue_configurations.is_none());
assert!(cfg.lambda_function_configurations.is_none());
assert!(cfg.event_bridge_configuration().is_none());
}
#[test]
fn notification_parses_topic_configuration() {
let json = r#"{
"TopicConfigurations": [
{
"Id": "t1",
"TopicArn": "arn:aws:sns:us-east-1:111111111111:topic-x",
"Events": ["s3:ObjectCreated:*"]
}
]
}"#;
let parsed: NotificationConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let topics = cfg
.topic_configurations
.as_ref()
.expect("topic_configurations");
assert_eq!(topics.len(), 1);
assert_eq!(topics[0].id(), Some("t1"));
assert_eq!(
topics[0].topic_arn(),
"arn:aws:sns:us-east-1:111111111111:topic-x"
);
assert_eq!(topics[0].events().len(), 1);
assert_eq!(topics[0].events()[0].as_str(), "s3:ObjectCreated:*");
}
#[test]
fn notification_parses_queue_configuration() {
let json = r#"{
"QueueConfigurations": [
{
"QueueArn": "arn:aws:sqs:us-east-1:111111111111:queue-x",
"Events": ["s3:ObjectRemoved:Delete"]
}
]
}"#;
let parsed: NotificationConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let queues = cfg
.queue_configurations
.as_ref()
.expect("queue_configurations");
assert_eq!(queues.len(), 1);
assert_eq!(
queues[0].queue_arn(),
"arn:aws:sqs:us-east-1:111111111111:queue-x"
);
assert_eq!(queues[0].events()[0].as_str(), "s3:ObjectRemoved:Delete");
}
#[test]
fn notification_parses_lambda_configuration() {
let json = r#"{
"LambdaFunctionConfigurations": [
{
"LambdaFunctionArn": "arn:aws:lambda:us-east-1:111111111111:function:fn-x",
"Events": ["s3:ObjectCreated:Put"]
}
]
}"#;
let parsed: NotificationConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let lambdas = cfg
.lambda_function_configurations
.as_ref()
.expect("lambda_function_configurations");
assert_eq!(lambdas.len(), 1);
assert_eq!(
lambdas[0].lambda_function_arn(),
"arn:aws:lambda:us-east-1:111111111111:function:fn-x"
);
}
#[test]
fn notification_parses_event_bridge_marker() {
let json = r#"{ "EventBridgeConfiguration": {} }"#;
let parsed: NotificationConfigurationJson = serde_json::from_str(json).unwrap();
assert!(parsed.EventBridgeConfiguration.is_some());
let cfg = parsed.into_sdk().unwrap();
assert!(cfg.event_bridge_configuration().is_some());
}
#[test]
fn notification_parses_filter_with_prefix_and_suffix() {
let json = r#"{
"TopicConfigurations": [
{
"TopicArn": "arn:aws:sns:us-east-1:111111111111:topic-x",
"Events": ["s3:ObjectCreated:*"],
"Filter": {
"Key": {
"FilterRules": [
{ "Name": "prefix", "Value": "images/" },
{ "Name": "suffix", "Value": ".jpg" }
]
}
}
}
]
}"#;
let parsed: NotificationConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let topic = &cfg.topic_configurations.as_ref().unwrap()[0];
let key = topic.filter().unwrap().key().expect("key filter");
let rules = key.filter_rules();
assert_eq!(rules.len(), 2);
assert_eq!(rules[0].name().map(|n| n.as_str()), Some("prefix"));
assert_eq!(rules[0].value(), Some("images/"));
assert_eq!(rules[1].name().map(|n| n.as_str()), Some("suffix"));
assert_eq!(rules[1].value(), Some(".jpg"));
}
#[test]
fn notification_topic_missing_arn_errors_at_parse() {
let json = r#"{
"TopicConfigurations": [
{ "Events": ["s3:ObjectCreated:*"] }
]
}"#;
assert!(serde_json::from_str::<NotificationConfigurationJson>(json).is_err());
}
#[test]
fn notification_queue_missing_arn_errors_at_parse() {
let json = r#"{
"QueueConfigurations": [
{ "Events": ["s3:ObjectCreated:*"] }
]
}"#;
assert!(serde_json::from_str::<NotificationConfigurationJson>(json).is_err());
}
#[test]
fn notification_lambda_missing_arn_errors_at_parse() {
let json = r#"{
"LambdaFunctionConfigurations": [
{ "Events": ["s3:ObjectCreated:*"] }
]
}"#;
assert!(serde_json::from_str::<NotificationConfigurationJson>(json).is_err());
}
#[test]
fn notification_invalid_json_errors() {
assert!(serde_json::from_str::<NotificationConfigurationJson>("{not json").is_err());
}
#[test]
fn lifecycle_rule_with_deprecated_prefix_into_sdk_preserves_it() {
let json =
r#"{"Rules":[{"Status":"Enabled","Prefix":"old-style/","Expiration":{"Days":1}}]}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
#[allow(deprecated)]
let p = cfg.rules()[0].prefix();
assert_eq!(p, Some("old-style/"));
}
#[test]
fn lifecycle_rule_filter_with_tag_into_sdk_preserves_key_value() {
let json = r#"{
"Rules":[{
"Status":"Enabled",
"Filter":{"Tag":{"Key":"team","Value":"platform"}},
"Expiration":{"Days":1}
}]
}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let tag = cfg.rules()[0].filter().unwrap().tag().expect("tag filter");
assert_eq!(tag.key(), "team");
assert_eq!(tag.value(), "platform");
}
#[test]
fn lifecycle_expiration_with_date_into_sdk_parses_rfc3339() {
let json = r#"{
"Rules":[{
"Status":"Enabled",
"Expiration":{"Date":"2030-01-02T03:04:05Z"}
}]
}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let exp = cfg.rules()[0].expiration().expect("expiration");
let date = exp.date().expect("date set");
assert_eq!(date.secs(), 1_893_553_445);
}
#[test]
fn lifecycle_expiration_with_expired_object_delete_marker_into_sdk_preserves_it() {
let json = r#"{
"Rules":[{
"Status":"Enabled",
"Expiration":{"ExpiredObjectDeleteMarker":true}
}]
}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let exp = cfg.rules()[0].expiration().expect("expiration");
assert_eq!(exp.expired_object_delete_marker(), Some(true));
}
#[test]
fn lifecycle_transition_with_date_into_sdk_parses_rfc3339() {
let json = r#"{
"Rules":[{
"Status":"Enabled",
"Transitions":[{"Date":"2030-01-02T03:04:05Z","StorageClass":"GLACIER"}]
}]
}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let t = &cfg.rules()[0].transitions()[0];
let date = t.date().expect("date set");
assert_eq!(date.secs(), 1_893_553_445);
assert_eq!(t.storage_class(), Some(&TransitionStorageClass::Glacier));
}
#[test]
fn lifecycle_into_sdk_preserves_noncurrent_version_transitions() {
let json = r#"{
"Rules":[{
"Status":"Enabled",
"NoncurrentVersionTransitions":[
{"NoncurrentDays":7,"StorageClass":"GLACIER"}
]
}]
}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let nvts = cfg.rules()[0].noncurrent_version_transitions();
assert_eq!(nvts.len(), 1);
assert_eq!(nvts[0].noncurrent_days(), Some(7));
assert_eq!(
nvts[0].storage_class(),
Some(&TransitionStorageClass::Glacier)
);
}
#[test]
fn lifecycle_into_sdk_noncurrent_version_transition_omits_absent_fields() {
let json = r#"{
"Rules":[{
"Status":"Enabled",
"NoncurrentVersionTransitions":[ {} ]
}]
}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let nvt = &cfg.rules()[0].noncurrent_version_transitions()[0];
assert!(nvt.noncurrent_days().is_none());
assert!(nvt.storage_class().is_none());
}
#[test]
fn notification_queue_into_sdk_preserves_id_and_filter() {
let json = r#"{
"QueueConfigurations":[
{
"Id":"q1",
"QueueArn":"arn:aws:sqs:us-east-1:111111111111:queue-x",
"Events":["s3:ObjectCreated:*"],
"Filter":{"Key":{"FilterRules":[{"Name":"prefix","Value":"a/"}]}}
}
]
}"#;
let parsed: NotificationConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let q = &cfg.queue_configurations.as_ref().unwrap()[0];
assert_eq!(q.id(), Some("q1"));
let key = q.filter().unwrap().key().expect("key filter");
assert_eq!(key.filter_rules().len(), 1);
}
#[test]
fn notification_lambda_into_sdk_preserves_id_and_filter() {
let json = r#"{
"LambdaFunctionConfigurations":[
{
"Id":"l1",
"LambdaFunctionArn":"arn:aws:lambda:us-east-1:111111111111:function:fn-x",
"Events":["s3:ObjectCreated:Put"],
"Filter":{"Key":{"FilterRules":[{"Name":"suffix","Value":".jpg"}]}}
}
]
}"#;
let parsed: NotificationConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let l = &cfg.lambda_function_configurations.as_ref().unwrap()[0];
assert_eq!(l.id(), Some("l1"));
let key = l.filter().unwrap().key().expect("key filter");
assert_eq!(key.filter_rules().len(), 1);
}
#[test]
fn replication_parses_aws_cli_skeleton_shape() {
let json = r#"{
"Role": "arn:aws:iam::111111111111:role/replication",
"Rules": [
{
"Status": "Enabled",
"Destination": { "Bucket": "arn:aws:s3:::dest-bucket" }
}
]
}"#;
let parsed: ReplicationConfigurationJson = serde_json::from_str(json).unwrap();
assert_eq!(parsed.Role, "arn:aws:iam::111111111111:role/replication");
assert_eq!(parsed.Rules.len(), 1);
}
#[test]
fn replication_into_sdk_minimal_preserves_role_and_rule() {
let json = r#"{
"Role": "arn:aws:iam::111111111111:role/r",
"Rules":[{
"Status": "Enabled",
"Destination": { "Bucket": "arn:aws:s3:::d" }
}]
}"#;
let parsed: ReplicationConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
assert_eq!(cfg.role(), "arn:aws:iam::111111111111:role/r");
let rules = cfg.rules();
assert_eq!(rules.len(), 1);
assert_eq!(rules[0].status(), &ReplicationRuleStatus::Enabled);
assert_eq!(rules[0].destination().unwrap().bucket(), "arn:aws:s3:::d");
}
#[test]
fn replication_into_sdk_preserves_id_priority_prefix() {
let json = r#"{
"Role": "arn:aws:iam::111111111111:role/r",
"Rules":[{
"ID": "my-rule",
"Priority": 5,
"Prefix": "logs/",
"Status": "Disabled",
"Destination": { "Bucket": "arn:aws:s3:::d" }
}]
}"#;
let parsed: ReplicationConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let r = &cfg.rules()[0];
assert_eq!(r.id(), Some("my-rule"));
assert_eq!(r.priority(), Some(5));
#[allow(deprecated)]
let prefix_value = r.prefix();
assert_eq!(prefix_value, Some("logs/"));
assert_eq!(r.status(), &ReplicationRuleStatus::Disabled);
}
#[test]
fn replication_into_sdk_filter_prefix() {
let json = r#"{
"Role": "arn:aws:iam::111111111111:role/r",
"Rules":[{
"Filter": { "Prefix": "logs/" },
"Status": "Enabled",
"Destination": { "Bucket": "arn:aws:s3:::d" }
}]
}"#;
let parsed: ReplicationConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let f = cfg.rules()[0].filter().unwrap();
assert_eq!(f.prefix(), Some("logs/"));
}
#[test]
fn replication_into_sdk_filter_with_tag() {
let json = r#"{
"Role": "arn:aws:iam::111111111111:role/r",
"Rules":[{
"Filter": { "Tag": { "Key": "env", "Value": "prod" } },
"Status": "Enabled",
"Destination": { "Bucket": "arn:aws:s3:::d" }
}]
}"#;
let parsed: ReplicationConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let t = cfg.rules()[0].filter().unwrap().tag().unwrap();
assert_eq!(t.key(), "env");
assert_eq!(t.value(), "prod");
}
#[test]
fn replication_into_sdk_filter_with_and_prefix_and_tags() {
let json = r#"{
"Role": "arn:aws:iam::111111111111:role/r",
"Rules":[{
"Filter": { "And": { "Prefix": "p/", "Tags": [{"Key":"a","Value":"1"}] } },
"Status": "Enabled",
"Destination": { "Bucket": "arn:aws:s3:::d" }
}]
}"#;
let parsed: ReplicationConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let and = cfg.rules()[0].filter().unwrap().and().unwrap();
assert_eq!(and.prefix(), Some("p/"));
assert_eq!(and.tags().len(), 1);
assert_eq!(and.tags()[0].key(), "a");
}
#[test]
fn replication_into_sdk_source_selection_criteria() {
let json = r#"{
"Role": "arn:aws:iam::111111111111:role/r",
"Rules":[{
"Status": "Enabled",
"SourceSelectionCriteria": {
"SseKmsEncryptedObjects": { "Status": "Enabled" },
"ReplicaModifications": { "Status": "Enabled" }
},
"Destination": { "Bucket": "arn:aws:s3:::d" }
}]
}"#;
let parsed: ReplicationConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let ssc = cfg.rules()[0].source_selection_criteria().unwrap();
assert_eq!(
ssc.sse_kms_encrypted_objects().unwrap().status(),
&SseKmsEncryptedObjectsStatus::Enabled
);
assert_eq!(
ssc.replica_modifications().unwrap().status(),
&ReplicaModificationsStatus::Enabled
);
}
#[test]
fn replication_into_sdk_existing_object_replication() {
let json = r#"{
"Role": "arn:aws:iam::111111111111:role/r",
"Rules":[{
"Status": "Enabled",
"ExistingObjectReplication": { "Status": "Enabled" },
"Destination": { "Bucket": "arn:aws:s3:::d" }
}]
}"#;
let parsed: ReplicationConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let eor = cfg.rules()[0].existing_object_replication().unwrap();
assert_eq!(eor.status(), &ExistingObjectReplicationStatus::Enabled);
}
#[test]
fn replication_into_sdk_delete_marker_replication() {
let json = r#"{
"Role": "arn:aws:iam::111111111111:role/r",
"Rules":[{
"Status": "Enabled",
"DeleteMarkerReplication": { "Status": "Disabled" },
"Destination": { "Bucket": "arn:aws:s3:::d" }
}]
}"#;
let parsed: ReplicationConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let dmr = cfg.rules()[0].delete_marker_replication().unwrap();
assert_eq!(dmr.status(), Some(&DeleteMarkerReplicationStatus::Disabled));
}
#[test]
fn replication_into_sdk_destination_with_account_and_storage_class() {
let json = r#"{
"Role": "arn:aws:iam::111111111111:role/r",
"Rules":[{
"Status": "Enabled",
"Destination": {
"Bucket": "arn:aws:s3:::d",
"Account": "222222222222",
"StorageClass": "STANDARD_IA"
}
}]
}"#;
let parsed: ReplicationConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let d = cfg.rules()[0].destination().unwrap();
assert_eq!(d.account(), Some("222222222222"));
assert_eq!(d.storage_class(), Some(&StorageClass::StandardIa));
}
#[test]
fn replication_into_sdk_destination_with_access_control_translation() {
let json = r#"{
"Role": "arn:aws:iam::111111111111:role/r",
"Rules":[{
"Status": "Enabled",
"Destination": {
"Bucket": "arn:aws:s3:::d",
"AccessControlTranslation": { "Owner": "Destination" }
}
}]
}"#;
let parsed: ReplicationConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let act = cfg.rules()[0]
.destination()
.unwrap()
.access_control_translation()
.unwrap();
assert_eq!(act.owner().as_str(), "Destination");
}
#[test]
fn replication_into_sdk_destination_with_encryption_configuration() {
let json = r#"{
"Role": "arn:aws:iam::111111111111:role/r",
"Rules":[{
"Status": "Enabled",
"Destination": {
"Bucket": "arn:aws:s3:::d",
"EncryptionConfiguration": { "ReplicaKmsKeyID": "arn:aws:kms:us-east-1:1:key/abc" }
}
}]
}"#;
let parsed: ReplicationConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let ec = cfg.rules()[0]
.destination()
.unwrap()
.encryption_configuration()
.unwrap();
assert_eq!(
ec.replica_kms_key_id(),
Some("arn:aws:kms:us-east-1:1:key/abc")
);
}
#[test]
fn replication_into_sdk_destination_with_replication_time_and_metrics() {
let json = r#"{
"Role": "arn:aws:iam::111111111111:role/r",
"Rules":[{
"Status": "Enabled",
"Destination": {
"Bucket": "arn:aws:s3:::d",
"ReplicationTime": { "Status": "Enabled", "Time": { "Minutes": 15 } },
"Metrics": { "Status": "Enabled", "EventThreshold": { "Minutes": 15 } }
}
}]
}"#;
let parsed: ReplicationConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let d = cfg.rules()[0].destination().unwrap();
let rt = d.replication_time().unwrap();
assert_eq!(rt.status(), &ReplicationTimeStatus::Enabled);
assert_eq!(rt.time().unwrap().minutes(), Some(15));
let m = d.metrics().unwrap();
assert_eq!(m.status(), &MetricsStatus::Enabled);
assert_eq!(m.event_threshold().unwrap().minutes(), Some(15));
}
#[test]
fn replication_invalid_json_errors() {
let res: Result<ReplicationConfigurationJson, _> = serde_json::from_str("{not json");
assert!(res.is_err());
}
#[test]
fn replication_missing_role_errors() {
let res: Result<ReplicationConfigurationJson, _> = serde_json::from_str(r#"{"Rules":[]}"#);
assert!(res.is_err(), "missing required `Role` must error");
}
#[test]
fn replication_missing_rules_errors() {
let res: Result<ReplicationConfigurationJson, _> = serde_json::from_str(r#"{"Role":"r"}"#);
assert!(res.is_err(), "missing required `Rules` must error");
}
#[test]
fn replication_missing_destination_in_rule_errors() {
let res: Result<ReplicationConfigurationJson, _> =
serde_json::from_str(r#"{"Role":"r","Rules":[{"Status":"Enabled"}]}"#);
assert!(res.is_err(), "missing required `Destination` must error");
}
#[test]
fn replication_missing_status_in_rule_errors() {
let res: Result<ReplicationConfigurationJson, _> =
serde_json::from_str(r#"{"Role":"r","Rules":[{"Destination":{"Bucket":"d"}}]}"#);
assert!(res.is_err(), "missing required `Status` must error");
}
#[test]
fn replication_into_sdk_unknown_status_passes_through() {
let json = r#"{
"Role": "arn:aws:iam::111111111111:role/r",
"Rules":[{
"Status": "UnknownState",
"Destination": { "Bucket": "arn:aws:s3:::d" }
}]
}"#;
let parsed: ReplicationConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
assert_eq!(cfg.rules()[0].status().as_str(), "UnknownState");
}
#[test]
fn lifecycle_filter_with_object_size_greater_than_into_sdk() {
let json = r#"{
"Rules":[{
"Status":"Enabled",
"Filter":{"ObjectSizeGreaterThan": 1048576},
"Expiration":{"Days":1}
}]
}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let f = cfg.rules()[0].filter().expect("filter");
assert_eq!(f.object_size_greater_than(), Some(1_048_576));
assert!(f.object_size_less_than().is_none());
}
#[test]
fn lifecycle_filter_with_object_size_less_than_into_sdk() {
let json = r#"{
"Rules":[{
"Status":"Enabled",
"Filter":{"ObjectSizeLessThan": 5368709120},
"Expiration":{"Days":1}
}]
}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let f = cfg.rules()[0].filter().expect("filter");
assert_eq!(f.object_size_less_than(), Some(5_368_709_120));
}
#[test]
fn lifecycle_filter_and_with_object_size_predicates_into_sdk() {
let json = r#"{
"Rules":[{
"Status":"Enabled",
"Filter":{"And":{
"Prefix":"data/",
"Tags":[{"Key":"a","Value":"1"}],
"ObjectSizeGreaterThan": 1024,
"ObjectSizeLessThan": 1073741824
}},
"Expiration":{"Days":1}
}]
}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let and = cfg.rules()[0].filter().unwrap().and().expect("and");
assert_eq!(and.prefix(), Some("data/"));
assert_eq!(and.tags().len(), 1);
assert_eq!(and.object_size_greater_than(), Some(1024));
assert_eq!(and.object_size_less_than(), Some(1_073_741_824));
}
#[test]
fn lifecycle_noncurrent_version_expiration_with_newer_versions_into_sdk() {
let json = r#"{
"Rules":[{
"Status":"Enabled",
"NoncurrentVersionExpiration":{"NoncurrentDays":30,"NewerNoncurrentVersions":3}
}]
}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let n = cfg.rules()[0]
.noncurrent_version_expiration()
.expect("noncurrent version expiration");
assert_eq!(n.noncurrent_days(), Some(30));
assert_eq!(n.newer_noncurrent_versions(), Some(3));
}
#[test]
fn lifecycle_noncurrent_version_transition_with_newer_versions_into_sdk() {
let json = r#"{
"Rules":[{
"Status":"Enabled",
"NoncurrentVersionTransitions":[
{"NoncurrentDays":7,"StorageClass":"GLACIER","NewerNoncurrentVersions":2}
]
}]
}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let nvt = &cfg.rules()[0].noncurrent_version_transitions()[0];
assert_eq!(nvt.noncurrent_days(), Some(7));
assert_eq!(nvt.newer_noncurrent_versions(), Some(2));
}
#[test]
fn encryption_with_blocked_encryption_types_into_sdk() {
let json = r#"{
"Rules":[{
"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"},
"BlockedEncryptionTypes":{"EncryptionType":["SSE-C"]}
}]
}"#;
let parsed: ServerSideEncryptionConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let r = &cfg.rules()[0];
let bet = r.blocked_encryption_types().expect("blocked types");
assert_eq!(bet.encryption_type().len(), 1);
assert_eq!(bet.encryption_type()[0].as_str(), "SSE-C");
}
#[test]
fn encryption_with_blocked_encryption_types_empty_list_into_sdk() {
let json = r#"{
"Rules":[{
"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"},
"BlockedEncryptionTypes":{}
}]
}"#;
let parsed: ServerSideEncryptionConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let r = &cfg.rules()[0];
let bet = r.blocked_encryption_types().expect("blocked types");
assert!(bet.encryption_type().is_empty());
}
#[test]
fn encryption_with_blocked_encryption_types_multiple_into_sdk() {
let json = r#"{
"Rules":[{
"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"},
"BlockedEncryptionTypes":{"EncryptionType":["SSE-C","NONE","FUTURE"]}
}]
}"#;
let parsed: ServerSideEncryptionConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let bet = cfg.rules()[0]
.blocked_encryption_types()
.expect("blocked types");
let strs: Vec<&str> = bet.encryption_type().iter().map(|e| e.as_str()).collect();
assert_eq!(strs, vec!["SSE-C", "NONE", "FUTURE"]);
}
#[test]
fn encryption_without_blocked_encryption_types_into_sdk_is_none() {
let json = r#"{
"Rules":[{
"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}
}]
}"#;
let parsed: ServerSideEncryptionConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
assert!(cfg.rules()[0].blocked_encryption_types().is_none());
}
#[test]
fn lifecycle_filter_without_object_size_predicates_into_sdk_is_none() {
let json = r#"{
"Rules":[{
"Status":"Enabled",
"Filter":{"Prefix":"data/"},
"Expiration":{"Days":1}
}]
}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let f = cfg.rules()[0].filter().expect("filter");
assert!(f.object_size_greater_than().is_none());
assert!(f.object_size_less_than().is_none());
}
#[test]
fn lifecycle_filter_and_without_object_size_predicates_into_sdk_is_none() {
let json = r#"{
"Rules":[{
"Status":"Enabled",
"Filter":{"And":{"Prefix":"data/","Tags":[{"Key":"a","Value":"1"}]}},
"Expiration":{"Days":1}
}]
}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let and = cfg.rules()[0].filter().unwrap().and().expect("and");
assert!(and.object_size_greater_than().is_none());
assert!(and.object_size_less_than().is_none());
}
#[test]
fn lifecycle_noncurrent_version_expiration_without_newer_versions_into_sdk_is_none() {
let json = r#"{
"Rules":[{
"Status":"Enabled",
"NoncurrentVersionExpiration":{"NoncurrentDays":30}
}]
}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let n = cfg.rules()[0]
.noncurrent_version_expiration()
.expect("noncurrent version expiration");
assert_eq!(n.noncurrent_days(), Some(30));
assert!(n.newer_noncurrent_versions().is_none());
}
#[test]
fn lifecycle_noncurrent_version_transition_without_newer_versions_into_sdk_is_none() {
let json = r#"{
"Rules":[{
"Status":"Enabled",
"NoncurrentVersionTransitions":[{"NoncurrentDays":7,"StorageClass":"GLACIER"}]
}]
}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let nvt = &cfg.rules()[0].noncurrent_version_transitions()[0];
assert!(nvt.newer_noncurrent_versions().is_none());
}
#[test]
fn lifecycle_expiration_with_date_only_into_sdk_treats_as_midnight_utc() {
let json = r#"{
"Rules":[{
"Status":"Enabled",
"Expiration":{"Date":"2030-01-02"}
}]
}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let date = cfg.rules()[0]
.expiration()
.expect("expiration")
.date()
.expect("date set");
assert_eq!(date.secs(), 1_893_542_400);
}
#[test]
fn lifecycle_transition_with_date_only_into_sdk_treats_as_midnight_utc() {
let json = r#"{
"Rules":[{
"Status":"Enabled",
"Transitions":[{"Date":"2030-01-02","StorageClass":"GLACIER"}]
}]
}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let date = cfg.rules()[0].transitions()[0].date().expect("date set");
assert_eq!(date.secs(), 1_893_542_400);
}
#[test]
fn lifecycle_expiration_full_rfc3339_still_parses() {
let json = r#"{
"Rules":[{
"Status":"Enabled",
"Expiration":{"Date":"2030-01-02T03:04:05Z"}
}]
}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let date = cfg.rules()[0]
.expiration()
.expect("expiration")
.date()
.expect("date set");
assert_eq!(date.secs(), 1_893_553_445);
}
#[test]
fn lifecycle_invalid_date_only_format_still_errors() {
let json = r#"{"Rules":[{"Status":"Enabled","Expiration":{"Date":"2030-1-2"}}]}"#;
let parsed: LifecycleConfigurationJson = serde_json::from_str(json).unwrap();
let res = parsed.into_sdk();
assert!(res.is_err(), "malformed date must error");
}
#[test]
fn logging_into_sdk_preserves_target_grants_canonical_user() {
let json = r#"{
"LoggingEnabled":{
"TargetBucket":"log-bucket",
"TargetPrefix":"logs/",
"TargetGrants":[
{
"Grantee":{
"Type":"CanonicalUser",
"ID":"abcd1234",
"DisplayName":"alice"
},
"Permission":"FULL_CONTROL"
}
]
}
}"#;
let parsed: BucketLoggingStatusJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let le = cfg.logging_enabled().expect("logging_enabled");
let grants = le.target_grants();
assert_eq!(grants.len(), 1);
let g = &grants[0];
let grantee = g.grantee().expect("grantee");
assert_eq!(grantee.r#type().as_str(), "CanonicalUser");
assert_eq!(grantee.id(), Some("abcd1234"));
assert_eq!(grantee.display_name(), Some("alice"));
assert_eq!(g.permission().map(|p| p.as_str()), Some("FULL_CONTROL"));
}
#[test]
fn logging_into_sdk_preserves_target_grants_group_uri() {
let json = r#"{
"LoggingEnabled":{
"TargetBucket":"log-bucket",
"TargetPrefix":"logs/",
"TargetGrants":[
{
"Grantee":{
"Type":"Group",
"URI":"http://acs.amazonaws.com/groups/s3/LogDelivery"
},
"Permission":"WRITE"
}
]
}
}"#;
let parsed: BucketLoggingStatusJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let g = &cfg.logging_enabled().unwrap().target_grants()[0];
let grantee = g.grantee().unwrap();
assert_eq!(grantee.r#type().as_str(), "Group");
assert_eq!(
grantee.uri(),
Some("http://acs.amazonaws.com/groups/s3/LogDelivery")
);
assert_eq!(g.permission().map(|p| p.as_str()), Some("WRITE"));
}
#[test]
fn logging_into_sdk_target_grants_amazon_customer_by_email() {
let json = r#"{
"LoggingEnabled":{
"TargetBucket":"log-bucket",
"TargetPrefix":"logs/",
"TargetGrants":[
{
"Grantee":{
"Type":"AmazonCustomerByEmail",
"EmailAddress":"alice@example.com"
},
"Permission":"READ"
}
]
}
}"#;
let parsed: BucketLoggingStatusJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
let g = &cfg.logging_enabled().unwrap().target_grants()[0];
let grantee = g.grantee().unwrap();
assert_eq!(grantee.r#type().as_str(), "AmazonCustomerByEmail");
assert_eq!(grantee.email_address(), Some("alice@example.com"));
assert_eq!(g.permission().map(|p| p.as_str()), Some("READ"));
}
#[test]
fn logging_into_sdk_no_target_grants_yields_empty_slice() {
let json = r#"{
"LoggingEnabled":{
"TargetBucket":"log-bucket",
"TargetPrefix":"logs/"
}
}"#;
let parsed: BucketLoggingStatusJson = serde_json::from_str(json).unwrap();
let cfg = parsed.into_sdk().unwrap();
assert!(cfg.logging_enabled().unwrap().target_grants().is_empty());
}
#[test]
fn logging_into_sdk_target_grants_missing_type_errors() {
let json = r#"{
"LoggingEnabled":{
"TargetBucket":"log-bucket",
"TargetPrefix":"logs/",
"TargetGrants":[{"Grantee":{"ID":"abcd"},"Permission":"READ"}]
}
}"#;
assert!(serde_json::from_str::<BucketLoggingStatusJson>(json).is_err());
}
}