1use serde::{Deserialize, Serialize};
10use validator::Validate;
11
12#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
16pub struct ObjectConfig {
17 pub client: ObjectClientConfig,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
25#[serde(tag = "type", rename_all = "lowercase")]
26pub enum ObjectClientConfig {
27 S3(S3ObjectConfig),
29 Nixl(NixlObjectConfig),
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
38pub struct S3ObjectConfig {
39 #[serde(default)]
42 pub endpoint_url: Option<String>,
43
44 pub bucket: String,
46
47 #[serde(default = "default_region")]
49 pub region: String,
50
51 #[serde(default)]
54 pub force_path_style: bool,
55
56 #[serde(default = "default_max_concurrent")]
58 pub max_concurrent_requests: usize,
59}
60
61fn default_region() -> String {
62 "us-east-1".to_string()
63}
64
65fn default_max_concurrent() -> usize {
66 16
67}
68
69impl Default for S3ObjectConfig {
70 fn default() -> Self {
71 Self {
72 endpoint_url: None,
73 bucket: "kvbm-blocks".to_string(),
74 region: default_region(),
75 force_path_style: false,
76 max_concurrent_requests: default_max_concurrent(),
77 }
78 }
79}
80
81impl S3ObjectConfig {
82 pub fn aws(bucket: String, region: String) -> Self {
84 Self {
85 endpoint_url: None,
86 bucket,
87 region,
88 force_path_style: false,
89 max_concurrent_requests: default_max_concurrent(),
90 }
91 }
92
93 pub fn minio(endpoint_url: String, bucket: String) -> Self {
95 Self {
96 endpoint_url: Some(endpoint_url),
97 bucket,
98 region: default_region(),
99 force_path_style: true,
100 max_concurrent_requests: default_max_concurrent(),
101 }
102 }
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
110#[serde(tag = "backend", rename_all = "lowercase")]
111pub enum NixlObjectConfig {
112 S3(S3ObjectConfig),
114 }
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn test_s3_config_default() {
125 let config = S3ObjectConfig::default();
126 assert!(config.endpoint_url.is_none());
127 assert_eq!(config.bucket, "kvbm-blocks");
128 assert_eq!(config.region, "us-east-1");
129 assert!(!config.force_path_style);
130 assert_eq!(config.max_concurrent_requests, 16);
131 }
132
133 #[test]
134 fn test_s3_config_aws() {
135 let config = S3ObjectConfig::aws("my-bucket".into(), "us-west-2".into());
136 assert!(config.endpoint_url.is_none());
137 assert_eq!(config.bucket, "my-bucket");
138 assert_eq!(config.region, "us-west-2");
139 assert!(!config.force_path_style);
140 }
141
142 #[test]
143 fn test_s3_config_minio() {
144 let config = S3ObjectConfig::minio("http://localhost:9000".into(), "test".into());
145 assert_eq!(config.endpoint_url, Some("http://localhost:9000".into()));
146 assert_eq!(config.bucket, "test");
147 assert!(config.force_path_style);
148 }
149
150 #[test]
151 fn test_object_config_serde_s3() {
152 let json = r#"{
153 "client": {
154 "type": "s3",
155 "bucket": "my-bucket",
156 "region": "us-west-2"
157 }
158 }"#;
159 let config: ObjectConfig = serde_json::from_str(json).unwrap();
160 match config.client {
161 ObjectClientConfig::S3(s3) => {
162 assert_eq!(s3.bucket, "my-bucket");
163 assert_eq!(s3.region, "us-west-2");
164 }
165 _ => panic!("Expected S3 config"),
166 }
167 }
168
169 #[test]
170 fn test_object_config_serde_nixl_s3() {
171 let json = r#"{
172 "client": {
173 "type": "nixl",
174 "backend": "s3",
175 "bucket": "nixl-bucket",
176 "endpoint_url": "http://minio:9000",
177 "force_path_style": true
178 }
179 }"#;
180 let config: ObjectConfig = serde_json::from_str(json).unwrap();
181 match config.client {
182 ObjectClientConfig::Nixl(NixlObjectConfig::S3(s3)) => {
183 assert_eq!(s3.bucket, "nixl-bucket");
184 assert_eq!(s3.endpoint_url, Some("http://minio:9000".into()));
185 assert!(s3.force_path_style);
186 }
187 _ => panic!("Expected Nixl S3 config"),
188 }
189 }
190}