rustack_s3_core/ops/
bucket.rs1use rustack_s3_model::{
7 error::S3Error,
8 input::{
9 CreateBucketInput, DeleteBucketInput, GetBucketLocationInput, HeadBucketInput,
10 ListBucketsInput,
11 },
12 output::{CreateBucketOutput, GetBucketLocationOutput, HeadBucketOutput, ListBucketsOutput},
13 types::{Bucket, BucketLocationConstraint, LocationType, Owner},
14};
15use tracing::debug;
16
17use crate::{
18 error::S3ServiceError, provider::RustackS3, state::object::Owner as InternalOwner,
19 validation::validate_bucket_name,
20};
21
22pub(crate) fn to_model_owner(owner: &InternalOwner) -> Owner {
24 Owner {
25 display_name: Some(owner.display_name.clone()),
26 id: Some(owner.id.clone()),
27 }
28}
29
30#[allow(clippy::unused_async)]
34impl RustackS3 {
35 pub async fn handle_create_bucket(
37 &self,
38 input: CreateBucketInput,
39 ) -> Result<CreateBucketOutput, S3Error> {
40 let bucket_name = input.bucket;
41
42 validate_bucket_name(&bucket_name).map_err(S3ServiceError::into_s3_error)?;
43
44 let region = input
45 .create_bucket_configuration
46 .and_then(|c| c.location_constraint)
47 .map_or_else(
48 || self.config.default_region.clone(),
49 |lc: BucketLocationConstraint| lc.as_str().to_owned(),
50 );
51
52 let owner = InternalOwner::default();
53
54 let object_lock_enabled = input.object_lock_enabled_for_bucket.unwrap_or(false);
56
57 self.state
58 .create_bucket(bucket_name.clone(), region, owner)
59 .map_err(S3ServiceError::into_s3_error)?;
60
61 if object_lock_enabled {
63 if let Ok(bucket) = self.state.get_bucket(&bucket_name) {
64 *bucket.object_lock_enabled.write() = true;
65 bucket.enable_versioning();
67 }
68 }
69
70 debug!(bucket = %bucket_name, "create_bucket completed");
71
72 Ok(CreateBucketOutput {
73 bucket_arn: None,
74 location: Some(format!("/{bucket_name}")),
75 })
76 }
77
78 pub async fn handle_delete_bucket(&self, input: DeleteBucketInput) -> Result<(), S3Error> {
80 let bucket_name = input.bucket;
81
82 self.cors_index.delete_rules(&bucket_name);
84
85 self.storage.delete_bucket_data(&bucket_name);
87
88 self.state
90 .delete_bucket(&bucket_name)
91 .map_err(S3ServiceError::into_s3_error)?;
92
93 debug!(bucket = %bucket_name, "delete_bucket completed");
94
95 Ok(())
96 }
97
98 pub async fn handle_head_bucket(
100 &self,
101 input: HeadBucketInput,
102 ) -> Result<HeadBucketOutput, S3Error> {
103 let bucket_name = input.bucket;
104
105 let bucket = self
106 .state
107 .get_bucket(&bucket_name)
108 .map_err(S3ServiceError::into_s3_error)?;
109
110 Ok(HeadBucketOutput {
111 access_point_alias: None,
112 bucket_arn: None,
113 bucket_location_name: Some(bucket.region.clone()),
114 bucket_location_type: Some(LocationType::from("Region")),
115 bucket_region: Some(bucket.region.clone()),
116 })
117 }
118
119 pub async fn handle_list_buckets(
121 &self,
122 _input: ListBucketsInput,
123 ) -> Result<ListBucketsOutput, S3Error> {
124 let bucket_list = self.state.list_buckets();
125
126 let buckets: Vec<Bucket> = bucket_list
127 .into_iter()
128 .map(|(name, creation_date)| Bucket {
129 bucket_arn: None,
130 name: Some(name),
131 creation_date: Some(creation_date),
132 bucket_region: None,
133 })
134 .collect();
135
136 let owner = to_model_owner(&InternalOwner::default());
137
138 Ok(ListBucketsOutput {
139 buckets,
140 continuation_token: None,
141 owner: Some(owner),
142 prefix: None,
143 })
144 }
145
146 pub async fn handle_get_bucket_location(
148 &self,
149 input: GetBucketLocationInput,
150 ) -> Result<GetBucketLocationOutput, S3Error> {
151 let bucket_name = input.bucket;
152
153 let bucket = self
154 .state
155 .get_bucket(&bucket_name)
156 .map_err(S3ServiceError::into_s3_error)?;
157
158 let location_constraint = if bucket.region == "us-east-1" {
159 None
161 } else {
162 Some(BucketLocationConstraint::from(bucket.region.as_str()))
163 };
164
165 Ok(GetBucketLocationOutput {
166 location_constraint,
167 })
168 }
169}