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