s7cmd 1.6.0

Reliable, flexible, and fast command-line tool for Amazon S3
// Vendored from s3util-rs@1.3.0
//   src/bin/s3util/cli/put_bucket_request_payment.rs
// Adjustments: no tests stripped; rewrote crate::cli → super;
//              replaced the process-exiting `validate_state_flag()` call
//              with the non-exiting `check_state_flag` invoked from
//              dispatch.rs (a `process::exit(2)` would kill a whole
//              batch-run instead of failing one line).
use anyhow::Result;
use aws_sdk_s3::types::RequestPaymentConfiguration;
use clap::CommandFactory;
use tracing::info;

use s3util_rs::config::ClientConfig;
use s3util_rs::config::args::put_bucket_request_payment::PutBucketRequestPaymentArgs;
use s3util_rs::storage::s3::api;

/// Runtime entry for
/// `s3util put-bucket-request-payment s3://<BUCKET> --requester|--bucket-owner`.
///
/// Builds the SDK client from `client_config`, issues `PutBucketRequestPayment`
/// with `Payer=Requester` or `Payer=BucketOwner` (determined by the
/// mutually-exclusive `--requester` / `--bucket-owner` flags), and exits
/// silently on success.
pub async fn run_put_bucket_request_payment(
    args: PutBucketRequestPaymentArgs,
    client_config: ClientConfig,
) -> Result<()> {
    // --requester/--bucket-owner presence is enforced by `check_state_flag`,
    // which dispatch.rs runs before invoking this runner.
    let bucket = args
        .bucket_name()
        .map_err(|e| anyhow::anyhow!("{}", e.trim_end()))?;
    let payer = args.payer();
    let cfg = RequestPaymentConfiguration::builder()
        .payer(payer.clone())
        .build()?;
    let client = client_config.create_client().await;
    if args.dry_run {
        info!(
            bucket = %bucket,
            payer = %payer.as_str(),
            "[dry-run] would put bucket request payment."
        );
        return Ok(());
    }
    api::put_bucket_request_payment(&client, &bucket, cfg).await?;
    info!(
        bucket = %bucket,
        payer = %payer.as_str(),
        "Bucket request payment set."
    );
    Ok(())
}

/// Non-exiting replacement for upstream's `validate_state_flag`, whose
/// `clap::Error::exit()` (`std::process::exit(2)`) cannot be contained by
/// batch-run's per-line error handling. dispatch.rs calls this before the
/// runner, prints the error, and returns exit code 2 — same message and
/// code as upstream, without terminating the process.
pub fn check_state_flag(args: &PutBucketRequestPaymentArgs) -> Result<(), clap::Error> {
    if !args.requester && !args.bucket_owner {
        let mut cmd = PutBucketRequestPaymentArgs::command();
        return Err(cmd.error(
            clap::error::ErrorKind::MissingRequiredArgument,
            "one of --requester or --bucket-owner must be specified",
        ));
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use clap::Parser;

    fn parse(argv: &[&str]) -> PutBucketRequestPaymentArgs {
        PutBucketRequestPaymentArgs::try_parse_from(argv).expect("args should parse")
    }

    #[test]
    fn check_state_flag_rejects_neither_flag() {
        let args = parse(&["put-bucket-request-payment", "s3://b"]);
        let err = check_state_flag(&args).expect_err("neither flag must be rejected");
        assert_eq!(err.kind(), clap::error::ErrorKind::MissingRequiredArgument);
        assert!(
            err.to_string()
                .contains("one of --requester or --bucket-owner must be specified")
        );
    }

    #[test]
    fn check_state_flag_accepts_requester() {
        let args = parse(&["put-bucket-request-payment", "s3://b", "--requester"]);
        assert!(check_state_flag(&args).is_ok());
    }

    #[test]
    fn check_state_flag_accepts_bucket_owner() {
        let args = parse(&["put-bucket-request-payment", "s3://b", "--bucket-owner"]);
        assert!(check_state_flag(&args).is_ok());
    }
}