use derive_builder::Builder;
use serde_json::json;
use crate::api::common::{NameOrId, SecretStr};
use crate::api::endpoint_prelude::*;
#[derive(Debug, Builder, Clone)]
#[builder(derive(Debug), setter(strip_option))]
pub struct RemoteImportS3<'a> {
#[builder(setter(into))]
access_key_id: Cow<'a, str>,
#[builder(setter(into))]
bucket_name: Cow<'a, str>,
#[builder(setter(into))]
file_key: Cow<'a, str>,
#[builder(setter(into))]
path: Cow<'a, str>,
#[builder(setter(into))]
region: Cow<'a, str>,
#[builder(setter(into))]
secret_access_key: SecretStr<'a>,
#[builder(setter(into), default)]
name: Option<Cow<'a, str>>,
#[builder(setter(into), default)]
namespace: Option<NameOrId<'a>>,
}
impl<'a> RemoteImportS3<'a> {
pub fn builder() -> RemoteImportS3Builder<'a> {
RemoteImportS3Builder::default()
}
fn as_json(&self) -> serde_json::Value {
let (namespace_id, namespace_path) = match &self.namespace {
Some(NameOrId::Id(id)) => (Some(*id), None),
Some(NameOrId::Name(path)) => (None, Some(path.as_ref())),
None => (None, None),
};
JsonParams::clean(json!({
"access_key_id": self.access_key_id,
"bucket_name": self.bucket_name,
"file_key": self.file_key,
"name": self.name,
"namespace_id": namespace_id,
"namespace_path": namespace_path,
"path": self.path,
"region": self.region,
"secret_access_key": self.secret_access_key,
}))
}
}
impl Endpoint for RemoteImportS3<'_> {
fn method(&self) -> Method {
Method::POST
}
fn endpoint(&self) -> Cow<'static, str> {
"projects/remote-import-s3".into()
}
fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
JsonParams::into_body(&self.as_json())
}
}
#[cfg(test)]
mod tests {
use http::Method;
use crate::api::projects::import::{RemoteImportS3, RemoteImportS3BuilderError};
use crate::api::{self, Query};
use crate::test::client::{ExpectedUrl, SingleTestClient};
fn required_endpoint(body: &str) -> ExpectedUrl {
ExpectedUrl::builder()
.method(Method::POST)
.endpoint("projects/remote-import-s3")
.content_type("application/json")
.body_str(body)
.build()
.unwrap()
}
fn required_builder() -> crate::api::projects::import::RemoteImportS3Builder<'static> {
let mut b = RemoteImportS3::builder();
b.access_key_id("AKIAIOSFODNN7EXAMPLE")
.bucket_name("my-bucket")
.file_key("export.tar.gz")
.path("my-group/my-project")
.region("us-east-1")
.secret_access_key("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY");
b
}
#[test]
fn debug_redacts_secret_access_key() {
let endpoint = required_builder().build().unwrap();
let debug = format!("{endpoint:?}");
assert!(
!debug.contains("wJalrXUtnFEMI"),
"secret_access_key must not appear in Debug output",
);
assert!(
debug.contains("[REDACTED]"),
"Debug output must contain redaction marker",
);
assert!(
debug.contains("AKIAIOSFODNN7EXAMPLE"),
"access_key_id should remain visible",
);
}
#[test]
fn builder_debug_redacts_secret_access_key() {
let debug = format!("{:?}", required_builder());
assert!(
!debug.contains("wJalrXUtnFEMI"),
"secret_access_key must not appear in builder Debug output"
);
assert!(
debug.contains("[REDACTED]"),
"builder Debug output must contain redaction marker"
);
assert!(
debug.contains("AKIAIOSFODNN7EXAMPLE"),
"access_key_id should remain visible in builder Debug output"
);
}
#[test]
fn access_key_id_is_necessary() {
let err = RemoteImportS3::builder()
.bucket_name("my-bucket")
.file_key("export.tar.gz")
.path("my-group/my-project")
.region("us-east-1")
.secret_access_key("secret")
.build()
.unwrap_err();
crate::test::assert_missing_field!(err, RemoteImportS3BuilderError, "access_key_id");
}
#[test]
fn bucket_name_is_necessary() {
let err = RemoteImportS3::builder()
.access_key_id("key")
.file_key("export.tar.gz")
.path("my-group/my-project")
.region("us-east-1")
.secret_access_key("secret")
.build()
.unwrap_err();
crate::test::assert_missing_field!(err, RemoteImportS3BuilderError, "bucket_name");
}
#[test]
fn file_key_is_necessary() {
let err = RemoteImportS3::builder()
.access_key_id("key")
.bucket_name("my-bucket")
.path("my-group/my-project")
.region("us-east-1")
.secret_access_key("secret")
.build()
.unwrap_err();
crate::test::assert_missing_field!(err, RemoteImportS3BuilderError, "file_key");
}
#[test]
fn path_is_necessary() {
let err = RemoteImportS3::builder()
.access_key_id("key")
.bucket_name("my-bucket")
.file_key("export.tar.gz")
.region("us-east-1")
.secret_access_key("secret")
.build()
.unwrap_err();
crate::test::assert_missing_field!(err, RemoteImportS3BuilderError, "path");
}
#[test]
fn region_is_necessary() {
let err = RemoteImportS3::builder()
.access_key_id("key")
.bucket_name("my-bucket")
.file_key("export.tar.gz")
.path("my-group/my-project")
.secret_access_key("secret")
.build()
.unwrap_err();
crate::test::assert_missing_field!(err, RemoteImportS3BuilderError, "region");
}
#[test]
fn secret_access_key_is_necessary() {
let err = RemoteImportS3::builder()
.access_key_id("key")
.bucket_name("my-bucket")
.file_key("export.tar.gz")
.path("my-group/my-project")
.region("us-east-1")
.build()
.unwrap_err();
crate::test::assert_missing_field!(err, RemoteImportS3BuilderError, "secret_access_key");
}
#[test]
fn all_required_fields_are_sufficient() {
required_builder().build().unwrap();
}
#[test]
fn endpoint() {
let endpoint = required_endpoint(concat!(
"{",
"\"access_key_id\":\"AKIAIOSFODNN7EXAMPLE\",",
"\"bucket_name\":\"my-bucket\",",
"\"file_key\":\"export.tar.gz\",",
"\"path\":\"my-group/my-project\",",
"\"region\":\"us-east-1\",",
"\"secret_access_key\":\"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\"",
"}",
));
let client = SingleTestClient::new_raw(endpoint, "");
api::ignore(required_builder().build().unwrap())
.query(&client)
.unwrap();
}
#[test]
fn endpoint_name() {
let endpoint = required_endpoint(concat!(
"{",
"\"access_key_id\":\"AKIAIOSFODNN7EXAMPLE\",",
"\"bucket_name\":\"my-bucket\",",
"\"file_key\":\"export.tar.gz\",",
"\"name\":\"My Project\",",
"\"path\":\"my-group/my-project\",",
"\"region\":\"us-east-1\",",
"\"secret_access_key\":\"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\"",
"}",
));
let client = SingleTestClient::new_raw(endpoint, "");
api::ignore(required_builder().name("My Project").build().unwrap())
.query(&client)
.unwrap();
}
#[test]
fn endpoint_namespace_id() {
let endpoint = required_endpoint(concat!(
"{",
"\"access_key_id\":\"AKIAIOSFODNN7EXAMPLE\",",
"\"bucket_name\":\"my-bucket\",",
"\"file_key\":\"export.tar.gz\",",
"\"namespace_id\":5,",
"\"path\":\"my-group/my-project\",",
"\"region\":\"us-east-1\",",
"\"secret_access_key\":\"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\"",
"}",
));
let client = SingleTestClient::new_raw(endpoint, "");
api::ignore(required_builder().namespace(5).build().unwrap())
.query(&client)
.unwrap();
}
#[test]
fn endpoint_namespace_path() {
let endpoint = required_endpoint(concat!(
"{",
"\"access_key_id\":\"AKIAIOSFODNN7EXAMPLE\",",
"\"bucket_name\":\"my-bucket\",",
"\"file_key\":\"export.tar.gz\",",
"\"namespace_path\":\"my-group\",",
"\"path\":\"my-group/my-project\",",
"\"region\":\"us-east-1\",",
"\"secret_access_key\":\"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\"",
"}",
));
let client = SingleTestClient::new_raw(endpoint, "");
api::ignore(required_builder().namespace("my-group").build().unwrap())
.query(&client)
.unwrap();
}
}