faucet_source_s3/
config.rs1use faucet_core::DEFAULT_BATCH_SIZE;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
9#[serde(rename_all = "snake_case")]
10pub enum S3FileFormat {
11 #[default]
13 JsonLines,
14 JsonArray,
16 RawText,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
22pub struct S3SourceConfig {
23 pub bucket: String,
25 pub prefix: Option<String>,
27 pub region: Option<String>,
29 pub endpoint_url: Option<String>,
31 pub file_format: S3FileFormat,
33 pub max_objects: Option<usize>,
35 pub concurrency: usize,
37 #[serde(default = "default_batch_size")]
51 pub batch_size: usize,
52 #[serde(default = "default_true")]
60 pub verify_length: bool,
61 #[serde(default)]
70 pub verify_checksum: bool,
71 #[cfg(feature = "compression")]
77 #[serde(default)]
78 pub compression: faucet_core::CompressionConfig,
79}
80
81fn default_batch_size() -> usize {
82 DEFAULT_BATCH_SIZE
83}
84
85fn default_true() -> bool {
86 true
87}
88
89impl S3SourceConfig {
90 pub fn new(bucket: impl Into<String>) -> Self {
92 Self {
93 bucket: bucket.into(),
94 prefix: None,
95 region: None,
96 endpoint_url: None,
97 file_format: S3FileFormat::default(),
98 max_objects: None,
99 concurrency: 10,
100 batch_size: DEFAULT_BATCH_SIZE,
101 verify_length: true,
102 verify_checksum: false,
103 #[cfg(feature = "compression")]
104 compression: faucet_core::CompressionConfig::default(),
105 }
106 }
107
108 pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
110 self.prefix = Some(prefix.into());
111 self
112 }
113
114 pub fn region(mut self, region: impl Into<String>) -> Self {
116 self.region = Some(region.into());
117 self
118 }
119
120 pub fn endpoint_url(mut self, url: impl Into<String>) -> Self {
122 self.endpoint_url = Some(url.into());
123 self
124 }
125
126 pub fn file_format(mut self, format: S3FileFormat) -> Self {
128 self.file_format = format;
129 self
130 }
131
132 pub fn max_objects(mut self, max: usize) -> Self {
134 self.max_objects = Some(max);
135 self
136 }
137
138 pub fn concurrency(mut self, concurrency: usize) -> Self {
140 self.concurrency = concurrency;
141 self
142 }
143
144 pub fn with_batch_size(mut self, batch_size: usize) -> Self {
150 self.batch_size = batch_size;
151 self
152 }
153
154 pub fn verify_length(mut self, verify: bool) -> Self {
157 self.verify_length = verify;
158 self
159 }
160
161 pub fn verify_checksum(mut self, verify: bool) -> Self {
164 self.verify_checksum = verify;
165 self
166 }
167
168 #[cfg(feature = "compression")]
170 pub fn compression(mut self, c: faucet_core::CompressionConfig) -> Self {
171 self.compression = c;
172 self
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn default_config() {
182 let config = S3SourceConfig::new("my-bucket");
183 assert_eq!(config.bucket, "my-bucket");
184 assert!(config.prefix.is_none());
185 assert!(config.region.is_none());
186 assert!(config.endpoint_url.is_none());
187 assert!(matches!(config.file_format, S3FileFormat::JsonLines));
188 assert!(config.max_objects.is_none());
189 }
190
191 #[test]
192 fn builder_methods() {
193 let config = S3SourceConfig::new("my-bucket")
194 .prefix("data/")
195 .region("us-west-2")
196 .endpoint_url("http://localhost:9000")
197 .file_format(S3FileFormat::JsonArray)
198 .max_objects(10);
199
200 assert_eq!(config.bucket, "my-bucket");
201 assert_eq!(config.prefix.as_deref(), Some("data/"));
202 assert_eq!(config.region.as_deref(), Some("us-west-2"));
203 assert_eq!(
204 config.endpoint_url.as_deref(),
205 Some("http://localhost:9000")
206 );
207 assert!(matches!(config.file_format, S3FileFormat::JsonArray));
208 assert_eq!(config.max_objects, Some(10));
209 }
210
211 #[test]
212 fn file_format_default_is_json_lines() {
213 let format = S3FileFormat::default();
214 assert!(matches!(format, S3FileFormat::JsonLines));
215 }
216
217 #[test]
218 fn batch_size_defaults_to_default_batch_size() {
219 let config = S3SourceConfig::new("my-bucket");
220 assert_eq!(config.batch_size, faucet_core::DEFAULT_BATCH_SIZE);
221 }
222
223 #[test]
224 fn with_batch_size_overrides_default() {
225 let config = S3SourceConfig::new("my-bucket").with_batch_size(500);
226 assert_eq!(config.batch_size, 500);
227 }
228
229 #[test]
230 fn batch_size_zero_is_accepted_as_no_batching_sentinel() {
231 let config = S3SourceConfig::new("my-bucket").with_batch_size(0);
232 assert_eq!(config.batch_size, 0);
233 assert!(faucet_core::validate_batch_size(config.batch_size).is_ok());
234 }
235
236 #[test]
237 fn batch_size_above_max_is_rejected_by_validate_batch_size() {
238 let config =
239 S3SourceConfig::new("my-bucket").with_batch_size(faucet_core::MAX_BATCH_SIZE + 1);
240 assert!(faucet_core::validate_batch_size(config.batch_size).is_err());
241 }
242
243 #[test]
244 fn batch_size_deserializes_from_json() {
245 let json = r#"{
246 "bucket": "my-bucket",
247 "prefix": null,
248 "region": null,
249 "endpoint_url": null,
250 "file_format": "json_lines",
251 "max_objects": null,
252 "concurrency": 10,
253 "batch_size": 250
254 }"#;
255 let config: S3SourceConfig = serde_json::from_str(json).unwrap();
256 assert_eq!(config.batch_size, 250);
257 }
258
259 #[test]
260 fn verify_defaults_length_on_checksum_off() {
261 let cfg = S3SourceConfig::new("b");
262 assert!(cfg.verify_length, "length verification defaults on");
263 assert!(!cfg.verify_checksum, "checksum verification defaults off");
264 }
265
266 #[test]
267 fn verify_fields_default_when_absent_from_json() {
268 let json = r#"{
271 "bucket": "my-bucket",
272 "prefix": null,
273 "region": null,
274 "endpoint_url": null,
275 "file_format": "json_lines",
276 "max_objects": null,
277 "concurrency": 10,
278 "batch_size": 250
279 }"#;
280 let config: S3SourceConfig = serde_json::from_str(json).unwrap();
281 assert!(config.verify_length);
282 assert!(!config.verify_checksum);
283 }
284
285 #[test]
286 fn verify_builders_override() {
287 let cfg = S3SourceConfig::new("b")
288 .verify_length(false)
289 .verify_checksum(true);
290 assert!(!cfg.verify_length);
291 assert!(cfg.verify_checksum);
292 }
293
294 #[cfg(feature = "compression")]
295 #[test]
296 fn compression_default_is_auto() {
297 let cfg = S3SourceConfig::new("bucket");
298 assert_eq!(cfg.compression, faucet_core::CompressionConfig::Auto);
299 }
300}