use serde::{Deserialize, Serialize};
use validator::Validate;
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct ObjectConfig {
pub client: ObjectClientConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum ObjectClientConfig {
S3(S3ObjectConfig),
Nixl(NixlObjectConfig),
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct S3ObjectConfig {
#[serde(default)]
pub endpoint_url: Option<String>,
pub bucket: String,
#[serde(default = "default_region")]
pub region: String,
#[serde(default)]
pub force_path_style: bool,
#[serde(default = "default_max_concurrent")]
pub max_concurrent_requests: usize,
}
fn default_region() -> String {
"us-east-1".to_string()
}
fn default_max_concurrent() -> usize {
16
}
impl Default for S3ObjectConfig {
fn default() -> Self {
Self {
endpoint_url: None,
bucket: "kvbm-blocks".to_string(),
region: default_region(),
force_path_style: false,
max_concurrent_requests: default_max_concurrent(),
}
}
}
impl S3ObjectConfig {
pub fn aws(bucket: String, region: String) -> Self {
Self {
endpoint_url: None,
bucket,
region,
force_path_style: false,
max_concurrent_requests: default_max_concurrent(),
}
}
pub fn minio(endpoint_url: String, bucket: String) -> Self {
Self {
endpoint_url: Some(endpoint_url),
bucket,
region: default_region(),
force_path_style: true,
max_concurrent_requests: default_max_concurrent(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "backend", rename_all = "lowercase")]
pub enum NixlObjectConfig {
S3(S3ObjectConfig),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_s3_config_default() {
let config = S3ObjectConfig::default();
assert!(config.endpoint_url.is_none());
assert_eq!(config.bucket, "kvbm-blocks");
assert_eq!(config.region, "us-east-1");
assert!(!config.force_path_style);
assert_eq!(config.max_concurrent_requests, 16);
}
#[test]
fn test_s3_config_aws() {
let config = S3ObjectConfig::aws("my-bucket".into(), "us-west-2".into());
assert!(config.endpoint_url.is_none());
assert_eq!(config.bucket, "my-bucket");
assert_eq!(config.region, "us-west-2");
assert!(!config.force_path_style);
}
#[test]
fn test_s3_config_minio() {
let config = S3ObjectConfig::minio("http://localhost:9000".into(), "test".into());
assert_eq!(config.endpoint_url, Some("http://localhost:9000".into()));
assert_eq!(config.bucket, "test");
assert!(config.force_path_style);
}
#[test]
fn test_object_config_serde_s3() {
let json = r#"{
"client": {
"type": "s3",
"bucket": "my-bucket",
"region": "us-west-2"
}
}"#;
let config: ObjectConfig = serde_json::from_str(json).unwrap();
match config.client {
ObjectClientConfig::S3(s3) => {
assert_eq!(s3.bucket, "my-bucket");
assert_eq!(s3.region, "us-west-2");
}
_ => panic!("Expected S3 config"),
}
}
#[test]
fn test_object_config_serde_nixl_s3() {
let json = r#"{
"client": {
"type": "nixl",
"backend": "s3",
"bucket": "nixl-bucket",
"endpoint_url": "http://minio:9000",
"force_path_style": true
}
}"#;
let config: ObjectConfig = serde_json::from_str(json).unwrap();
match config.client {
ObjectClientConfig::Nixl(NixlObjectConfig::S3(s3)) => {
assert_eq!(s3.bucket, "nixl-bucket");
assert_eq!(s3.endpoint_url, Some("http://minio:9000".into()));
assert!(s3.force_path_style);
}
_ => panic!("Expected Nixl S3 config"),
}
}
}