Line data Source code
1 : use anyhow::Result;
2 : use std::str::FromStr;
3 :
4 : /// Struct to hold parsed S3 components
5 : #[derive(Debug, Clone, PartialEq, Eq)]
6 : pub struct S3Uri {
7 : pub bucket: String,
8 : pub key: String,
9 : }
10 :
11 : impl FromStr for S3Uri {
12 : type Err = anyhow::Error;
13 :
14 : /// Parse an S3 URI into a bucket and key
15 0 : fn from_str(uri: &str) -> Result<Self> {
16 0 : // Ensure the URI starts with "s3://"
17 0 : if !uri.starts_with("s3://") {
18 0 : return Err(anyhow::anyhow!("Invalid S3 URI scheme"));
19 0 : }
20 0 :
21 0 : // Remove the "s3://" prefix
22 0 : let stripped_uri = &uri[5..];
23 :
24 : // Split the remaining string into bucket and key parts
25 0 : if let Some((bucket, key)) = stripped_uri.split_once('/') {
26 0 : Ok(S3Uri {
27 0 : bucket: bucket.to_string(),
28 0 : key: key.to_string(),
29 0 : })
30 : } else {
31 0 : Err(anyhow::anyhow!(
32 0 : "Invalid S3 URI format, missing bucket or key"
33 0 : ))
34 : }
35 0 : }
36 : }
37 :
38 : impl S3Uri {
39 0 : pub fn append(&self, suffix: &str) -> Self {
40 0 : Self {
41 0 : bucket: self.bucket.clone(),
42 0 : key: format!("{}{}", self.key, suffix),
43 0 : }
44 0 : }
45 : }
46 :
47 : impl std::fmt::Display for S3Uri {
48 0 : fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
49 0 : write!(f, "s3://{}/{}", self.bucket, self.key)
50 0 : }
51 : }
52 :
53 : impl clap::builder::TypedValueParser for S3Uri {
54 : type Value = Self;
55 :
56 0 : fn parse_ref(
57 0 : &self,
58 0 : _cmd: &clap::Command,
59 0 : _arg: Option<&clap::Arg>,
60 0 : value: &std::ffi::OsStr,
61 0 : ) -> Result<Self::Value, clap::Error> {
62 0 : let value_str = value.to_str().ok_or_else(|| {
63 0 : clap::Error::raw(
64 0 : clap::error::ErrorKind::InvalidUtf8,
65 0 : "Invalid UTF-8 sequence",
66 0 : )
67 0 : })?;
68 0 : S3Uri::from_str(value_str).map_err(|e| {
69 0 : clap::Error::raw(
70 0 : clap::error::ErrorKind::InvalidValue,
71 0 : format!("Failed to parse S3 URI: {}", e),
72 0 : )
73 0 : })
74 0 : }
75 : }
|