1use coven_format::code_fields::{FieldReader, FieldWriter};
4use coven_format::Error as FieldError;
5use serde::{Deserialize, Serialize};
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
9pub enum S3Profile {
10 Named(S3ProfileName),
12 Custom(S3Settings),
14}
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
19pub enum S3ProfileName {
20 Aws,
22 Gcs,
24 R2,
26 RustFs,
28 SeaweedFs,
30 CephRgw,
32 VersityGw,
34}
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub enum S3BucketVersioning {
39 Disabled,
41 Enabled,
43 Suspended,
45 Unavailable,
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
51#[serde(deny_unknown_fields)]
52pub struct S3Settings {
53 pub signing: S3Signing,
55 pub single_put: S3SinglePut,
57 pub copy_publication: S3CopyPublication,
59 pub multipart_timestamp: S3MultipartTimestamp,
61 pub checksums: S3Checksums,
63 pub listing: S3Listing,
65 pub revision: S3Revision,
67 pub response_headers: S3ResponseHeaders,
69 pub request_checksums: bool,
71}
72
73#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
75pub enum S3Signing {
76 AwsSigV4,
78 GoogleHmac,
80}
81
82#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
84pub enum S3SinglePut {
85 IfNoneMatch,
87 GenerationMatch,
89}
90
91#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
93pub enum S3CopyPublication {
94 Never,
97 Multipart(S3CopyCondition),
100 Every(S3CopyCondition),
103}
104impl S3CopyPublication {
105 pub(crate) fn multipart(self) -> Option<S3CopyCondition> {
107 match self {
108 Self::Never => None,
109 Self::Multipart(condition) | Self::Every(condition) => Some(condition),
110 }
111 }
112 pub(crate) fn single_request(self) -> Option<S3CopyCondition> {
114 match self {
115 Self::Never | Self::Multipart(_) => None,
116 Self::Every(condition) => Some(condition),
117 }
118 }
119}
120
121#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
123pub enum S3CopyCondition {
124 IfNoneMatch,
126 GenerationMatch,
128 CloudflareIfNoneMatch,
130}
131
132#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
134pub enum S3MultipartTimestamp {
135 Completion,
137 Start,
139}
140
141#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
143pub enum S3Checksums {
144 ScopeStated,
146 UnstatedIgnored,
148 Absent,
150}
151
152#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
154pub enum S3Listing {
155 V2,
157 V1,
159}
160
161#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
163pub enum S3Revision {
164 ETag,
166 Generation,
168}
169
170#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
172pub enum S3ResponseHeaders {
173 S3,
175 Google,
177}
178
179impl S3Profile {
182 pub(crate) fn pack(&self, out: &mut FieldWriter) -> Result<(), FieldError> {
183 match self {
184 Self::Named(name) => {
185 out.u8(0)?;
186 out.u8(name.tag())
187 }
188 Self::Custom(settings) => {
189 out.u8(1)?;
190 out.u16(settings.bits())
191 }
192 }
193 }
194
195 pub(crate) fn unpack(input: &mut FieldReader<'_>) -> Result<Self, FieldError> {
196 match input.u8()? {
197 0 => Ok(Self::Named(S3ProfileName::from_tag(input.u8()?)?)),
198 1 => Ok(Self::Custom(S3Settings::from_bits(input.u16()?)?)),
199 tag => Err(FieldError::UnknownTag { field: "S3 profile", tag }),
200 }
201 }
202}
203
204impl S3ProfileName {
205 const ALL: [Self; 7] = [
206 Self::Aws,
207 Self::Gcs,
208 Self::R2,
209 Self::RustFs,
210 Self::SeaweedFs,
211 Self::CephRgw,
212 Self::VersityGw,
213 ];
214
215 fn tag(self) -> u8 {
216 match self {
217 Self::Aws => 0,
218 Self::Gcs => 1,
219 Self::R2 => 2,
220 Self::RustFs => 3,
221 Self::SeaweedFs => 4,
222 Self::CephRgw => 5,
223 Self::VersityGw => 6,
224 }
225 }
226
227 fn from_tag(tag: u8) -> Result<Self, FieldError> {
228 Self::ALL
229 .into_iter()
230 .find(|name| name.tag() == tag)
231 .ok_or(FieldError::UnknownTag { field: "S3 profile name", tag })
232 }
233}
234
235const SIGNING: u32 = 0;
240const SINGLE_PUT: u32 = 1;
241const COPY_PUBLICATION: u32 = 2;
242const MULTIPART_TIMESTAMP: u32 = 5;
243const CHECKSUMS: u32 = 6;
244const LISTING: u32 = 8;
245const REVISION: u32 = 9;
246const RESPONSE_HEADERS: u32 = 10;
247const REQUEST_CHECKSUMS: u32 = 11;
248const USED_BITS: u32 = 12;
249const COPY_PUBLICATIONS: [S3CopyPublication; 7] = [
251 S3CopyPublication::Never,
252 S3CopyPublication::Multipart(S3CopyCondition::IfNoneMatch),
253 S3CopyPublication::Multipart(S3CopyCondition::GenerationMatch),
254 S3CopyPublication::Multipart(S3CopyCondition::CloudflareIfNoneMatch),
255 S3CopyPublication::Every(S3CopyCondition::IfNoneMatch),
256 S3CopyPublication::Every(S3CopyCondition::GenerationMatch),
257 S3CopyPublication::Every(S3CopyCondition::CloudflareIfNoneMatch),
258];
259
260impl S3Settings {
261 fn bits(&self) -> u16 {
262 let copy_publication = match self.copy_publication {
264 S3CopyPublication::Never => 0,
265 S3CopyPublication::Multipart(S3CopyCondition::IfNoneMatch) => 1,
266 S3CopyPublication::Multipart(S3CopyCondition::GenerationMatch) => 2,
267 S3CopyPublication::Multipart(S3CopyCondition::CloudflareIfNoneMatch) => 3,
268 S3CopyPublication::Every(S3CopyCondition::IfNoneMatch) => 4,
269 S3CopyPublication::Every(S3CopyCondition::GenerationMatch) => 5,
270 S3CopyPublication::Every(S3CopyCondition::CloudflareIfNoneMatch) => 6,
271 };
272 let checksums = match self.checksums {
273 S3Checksums::ScopeStated => 0,
274 S3Checksums::UnstatedIgnored => 1,
275 S3Checksums::Absent => 2,
276 };
277 u16::from(self.signing == S3Signing::GoogleHmac) << SIGNING
278 | u16::from(self.single_put == S3SinglePut::GenerationMatch) << SINGLE_PUT
279 | copy_publication << COPY_PUBLICATION
280 | u16::from(self.multipart_timestamp == S3MultipartTimestamp::Start)
281 << MULTIPART_TIMESTAMP
282 | checksums << CHECKSUMS
283 | u16::from(self.listing == S3Listing::V1) << LISTING
284 | u16::from(self.revision == S3Revision::Generation) << REVISION
285 | u16::from(self.response_headers == S3ResponseHeaders::Google) << RESPONSE_HEADERS
286 | u16::from(self.request_checksums) << REQUEST_CHECKSUMS
287 }
288
289 fn from_bits(bits: u16) -> Result<Self, FieldError> {
290 let field = |shift: u32, width: u32| (bits >> shift) & ((1 << width) - 1);
291 let set = |shift: u32| field(shift, 1) == 1;
292 let unknown =
293 |field: &'static str, tag: u16| FieldError::UnknownTag { field, tag: tag as u8 };
294 if bits >> USED_BITS != 0 {
295 return Err(unknown("S3 settings", bits >> USED_BITS));
296 }
297 Ok(Self {
298 signing: if set(SIGNING) { S3Signing::GoogleHmac } else { S3Signing::AwsSigV4 },
299 single_put: if set(SINGLE_PUT) {
300 S3SinglePut::GenerationMatch
301 } else {
302 S3SinglePut::IfNoneMatch
303 },
304 copy_publication: *COPY_PUBLICATIONS
305 .get(usize::from(field(COPY_PUBLICATION, 3)))
306 .ok_or(unknown("S3 copy publication", field(COPY_PUBLICATION, 3)))?,
307 multipart_timestamp: if set(MULTIPART_TIMESTAMP) {
308 S3MultipartTimestamp::Start
309 } else {
310 S3MultipartTimestamp::Completion
311 },
312 checksums: match field(CHECKSUMS, 2) {
313 0 => S3Checksums::ScopeStated,
314 1 => S3Checksums::UnstatedIgnored,
315 2 => S3Checksums::Absent,
316 value => return Err(unknown("S3 checksums", value)),
317 },
318 listing: if set(LISTING) { S3Listing::V1 } else { S3Listing::V2 },
319 revision: if set(REVISION) { S3Revision::Generation } else { S3Revision::ETag },
320 response_headers: if set(RESPONSE_HEADERS) {
321 S3ResponseHeaders::Google
322 } else {
323 S3ResponseHeaders::S3
324 },
325 request_checksums: set(REQUEST_CHECKSUMS),
326 })
327 }
328}