Skip to main content

vantage_aws/models/s3/
object.rs

1use serde::{Deserialize, Serialize};
2use vantage_table::table::Table;
3
4use crate::types::AwsDateTime;
5use crate::{AwsAccount, eq};
6
7/// One S3 object from `ListObjectsV2`. Field names match the wire XML
8/// (`<Contents><Key/><Size/>…</Contents>`) — we surface them as-is so
9/// existing S3 docs translate directly. `Size` arrives as a numeric
10/// string in v0 (no XML-to-typed coercion).
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Object {
13    #[serde(rename = "Key")]
14    pub key: String,
15    #[serde(rename = "Size", default)]
16    pub size: String,
17    #[serde(rename = "LastModified", default)]
18    pub last_modified: String,
19    #[serde(rename = "ETag", default)]
20    pub etag: String,
21    #[serde(rename = "StorageClass", default)]
22    pub storage_class: String,
23}
24
25/// `ListObjectsV2` table. Requires `eq("Bucket", "...")` — without it
26/// the path placeholder errors out at request-build time. Optional
27/// `prefix` / `delimiter` / `max-keys` filters become query params if
28/// supplied.
29///
30/// Used as the `objects` relation on [`super::bucket::Bucket`] —
31/// traversing from a single bucket fills `Bucket` automatically.
32pub fn objects_table(aws: AwsAccount) -> Table<AwsAccount, Object> {
33    Table::new("restxml/Contents:s3/GET /{Bucket}?list-type=2", aws)
34        .with_id_column("Key")
35        .with_title_column_of::<String>("Size")
36        .with_title_column_of::<AwsDateTime>("LastModified")
37        .with_column_of::<String>("ETag")
38        .with_column_of::<String>("StorageClass")
39}
40
41impl Object {
42    /// Build an [`objects_table`] narrowed to the object named in
43    /// `arn`. Accepts ARNs of the shape `arn:aws:s3:::<bucket>/<key>` —
44    /// the bucket fills the path placeholder, the key narrows
45    /// `prefix` so we only fetch the matching object.
46    pub fn from_arn(arn: &str, aws: AwsAccount) -> Option<Table<AwsAccount, Object>> {
47        let rest = arn.strip_prefix("arn:aws:s3:::")?;
48        let (bucket, key) = rest.split_once('/')?;
49        if bucket.is_empty() || key.is_empty() {
50            return None;
51        }
52        let mut t = objects_table(aws);
53        t.add_condition(eq("Bucket", bucket.to_string()));
54        t.add_condition(eq("prefix", key.to_string()));
55        // The post-hoc client filter at `impls::table_source` will
56        // narrow on `Key` once the response comes back — `prefix` only
57        // narrows server-side, so we add the literal Key match too.
58        t.add_condition(eq("Key", key.to_string()));
59        Some(t)
60    }
61}