transfer_family_cli 0.3.0

TUI to browse and transfer files via AWS Transfer Family connector
Documentation
//! Configuration for the Transfer Family Connector TUI CLI.

use std::path::PathBuf;

use crate::transfer_storage::split_s3_path;
use crate::types::{ConnectorId, S3Bucket, S3Key, S3Root};

/// Runtime configuration derived from CLI args and env.
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct Config {
    /// AWS Transfer Family connector ID (e.g. `c-01234567890abcdef`).
    pub connector_id: ConnectorId,
    /// S3 location for transfer CLI data: bucket and optional prefix, format `bucket/prefix`.
    /// Listings, retrieve, and send use subpaths under this root.
    pub s3_root: S3Root,
    /// AWS region (e.g. `us-east-1`).
    pub region: Option<String>,
    /// AWS profile name for credentials.
    pub profile: Option<String>,
    /// Local directory for downloads (get). Default: current directory.
    pub download_dir: PathBuf,
}

impl Config {
    /// Builds config from individual fields (for use with `#[non_exhaustive]`).
    #[allow(clippy::missing_const_for_fn)] // String/PathBuf not const
    #[must_use]
    pub fn new(
        connector_id: impl Into<ConnectorId>,
        s3_root: impl Into<S3Root>,
        region: Option<String>,
        profile: Option<String>,
        download_dir: PathBuf,
    ) -> Self {
        Self {
            connector_id: connector_id.into(),
            s3_root: s3_root.into(),
            region,
            profile,
            download_dir,
        }
    }

    fn s3_base(&self) -> String {
        self.s3_root.as_str().trim_end_matches('/').to_string()
    }

    /// S3 path for directory listing results: `/bucket/prefix/listings`. No trailing slash (API requirement).
    #[must_use]
    pub fn listings_prefix(&self) -> String {
        format!("/{}/listings", self.s3_base().trim_end_matches('/'))
    }

    /// S3 path for retrieve (get): `/bucket/prefix/retrieve`. No trailing slash (API requirement).
    #[must_use]
    pub fn retrieve_prefix(&self) -> String {
        format!("/{}/retrieve", self.s3_base().trim_end_matches('/'))
    }

    /// S3 path for send (put): `/bucket/prefix/send`. No trailing slash (API requirement).
    #[must_use]
    pub fn send_prefix(&self) -> String {
        format!("/{}/send", self.s3_base().trim_end_matches('/'))
    }

    /// Maps a full remote path (e.g. `/foo/bar.txt`) to S3 (bucket, key) under this config's `s3_root`.
    #[must_use]
    pub fn remote_path_to_s3(&self, remote_path: &str) -> (S3Bucket, S3Key) {
        let base = self.s3_base();
        let (bucket, prefix) = split_s3_path(&base);
        let path_part = remote_path.trim_start_matches('/');
        let key = crate::transfer_storage::build_s3_key(prefix.as_str(), path_part);
        (bucket, S3Key::from(key))
    }
}

#[cfg(test)]
#[allow(dead_code)]
pub(crate) fn test_config() -> Config {
    Config::new(
        "c-test",
        "bucket/transfer-cli/",
        None,
        None,
        std::path::PathBuf::from("/mem"),
    )
}