1pub mod security;
7
8use crate::compression::CompressionConfig;
9pub use security::SecurityConfig;
10
11#[derive(Debug, thiserror::Error)]
13pub enum ConfigError {
14 #[error("config field `{section}.{field}` must be > 0")]
16 MustBePositive {
17 section: &'static str,
19 field: &'static str,
21 },
22
23 #[error("config constraint violated in `{section}`: {message}")]
25 InconsistentBounds {
26 section: &'static str,
28 message: &'static str,
30 },
31}
32
33#[derive(Debug, Clone, Default)]
35pub struct PjsConfig {
36 pub security: SecurityConfig,
38 pub compression: CompressionConfig,
40 pub parser: ParserConfig,
42 pub streaming: StreamingConfig,
44 pub simd: SimdConfig,
46}
47
48#[derive(Debug, Clone)]
50pub struct ParserConfig {
51 pub max_input_size_mb: usize,
53 pub buffer_initial_capacity: usize,
55 pub simd_min_size: usize,
57 pub enable_semantics: bool,
59}
60
61#[derive(Debug, Clone)]
63pub struct StreamingConfig {
64 pub max_frame_size: usize,
66 pub default_chunk_size: usize,
68 pub operation_timeout_ms: u64,
70 pub max_bandwidth_bps: u64,
72}
73
74#[derive(Debug, Clone)]
76pub struct SimdConfig {
77 pub batch_size: usize,
79 pub initial_capacity: usize,
81 pub avx512_alignment: usize,
83 pub vectorized_chunk_size: usize,
85 pub enable_stats: bool,
87}
88
89impl Default for ParserConfig {
90 fn default() -> Self {
91 Self {
92 max_input_size_mb: 100,
93 buffer_initial_capacity: 8192, simd_min_size: 4096, enable_semantics: true,
96 }
97 }
98}
99
100impl Default for StreamingConfig {
101 fn default() -> Self {
102 Self {
103 max_frame_size: 64 * 1024, default_chunk_size: 1024,
105 operation_timeout_ms: 5000, max_bandwidth_bps: 1_000_000, }
108 }
109}
110
111impl Default for SimdConfig {
112 fn default() -> Self {
113 Self {
114 batch_size: 100,
115 initial_capacity: 8192, avx512_alignment: 64,
117 vectorized_chunk_size: 32,
118 enable_stats: false,
119 }
120 }
121}
122
123impl StreamingConfig {
124 pub fn validate(&self) -> Result<(), ConfigError> {
139 if self.max_frame_size == 0 {
140 return Err(ConfigError::MustBePositive {
141 section: "streaming",
142 field: "max_frame_size",
143 });
144 }
145 if self.operation_timeout_ms == 0 {
146 return Err(ConfigError::MustBePositive {
147 section: "streaming",
148 field: "operation_timeout_ms",
149 });
150 }
151 Ok(())
152 }
153}
154
155impl ParserConfig {
156 pub fn validate(&self) -> Result<(), ConfigError> {
171 if self.max_input_size_mb == 0 {
172 return Err(ConfigError::MustBePositive {
173 section: "parser",
174 field: "max_input_size_mb",
175 });
176 }
177 if self.buffer_initial_capacity == 0 {
178 return Err(ConfigError::MustBePositive {
179 section: "parser",
180 field: "buffer_initial_capacity",
181 });
182 }
183 Ok(())
184 }
185}
186
187impl SimdConfig {
188 pub fn validate(&self) -> Result<(), ConfigError> {
207 if self.avx512_alignment == 0 {
208 return Err(ConfigError::MustBePositive {
209 section: "simd",
210 field: "avx512_alignment",
211 });
212 }
213 if !self.avx512_alignment.is_power_of_two() {
214 return Err(ConfigError::InconsistentBounds {
215 section: "simd",
216 message: "avx512_alignment must be a power of two",
217 });
218 }
219 Ok(())
220 }
221}
222
223impl PjsConfig {
225 pub fn validate(&self) -> Result<(), ConfigError> {
242 self.streaming.validate()?;
243 self.parser.validate()?;
244 self.simd.validate()?;
245 self.security.validate()?;
246 self.compression.validate()?;
247 Ok(())
248 }
249
250 pub fn low_latency() -> Self {
252 Self {
253 security: SecurityConfig::development(),
254 compression: CompressionConfig::default(),
255 parser: ParserConfig {
256 max_input_size_mb: 10,
257 buffer_initial_capacity: 4096, simd_min_size: 2048, enable_semantics: false, },
261 streaming: StreamingConfig {
262 max_frame_size: 16 * 1024, default_chunk_size: 512,
264 operation_timeout_ms: 1000, max_bandwidth_bps: 10_000_000, },
267 simd: SimdConfig {
268 batch_size: 50,
269 initial_capacity: 4096, avx512_alignment: 64,
271 vectorized_chunk_size: 16,
272 enable_stats: false,
273 },
274 }
275 }
276
277 pub fn high_throughput() -> Self {
279 Self {
280 security: SecurityConfig::high_throughput(),
281 compression: CompressionConfig::default(),
282 parser: ParserConfig {
283 max_input_size_mb: 1000, buffer_initial_capacity: 32768, simd_min_size: 8192, enable_semantics: true,
287 },
288 streaming: StreamingConfig {
289 max_frame_size: 256 * 1024, default_chunk_size: 4096,
291 operation_timeout_ms: 30000, max_bandwidth_bps: 100_000_000, },
294 simd: SimdConfig {
295 batch_size: 500,
296 initial_capacity: 32768, avx512_alignment: 64,
298 vectorized_chunk_size: 64,
299 enable_stats: true,
300 },
301 }
302 }
303
304 pub fn mobile() -> Self {
306 Self {
307 security: SecurityConfig::low_memory(),
308 compression: CompressionConfig {
309 min_array_length: 1,
310 min_string_length: 2,
311 min_frequency_count: 1,
312 uuid_compression_potential: 0.5,
313 min_net_savings: 4, delta_threshold: 15.0, min_delta_potential: 0.2,
316 run_length_threshold: 10.0, min_compression_potential: 0.3,
318 min_numeric_sequence_size: 2,
319 },
320 parser: ParserConfig {
321 max_input_size_mb: 10,
322 buffer_initial_capacity: 2048, simd_min_size: 1024, enable_semantics: false,
325 },
326 streaming: StreamingConfig {
327 max_frame_size: 8 * 1024, default_chunk_size: 256,
329 operation_timeout_ms: 10000, max_bandwidth_bps: 100_000, },
332 simd: SimdConfig {
333 batch_size: 25,
334 initial_capacity: 2048, avx512_alignment: 32, vectorized_chunk_size: 8,
337 enable_stats: false,
338 },
339 }
340 }
341}
342
343#[cfg(test)]
344mod tests {
345 use super::*;
346
347 #[test]
348 fn test_default_config() {
349 let config = PjsConfig::default();
350 assert_eq!(config.parser.max_input_size_mb, 100);
351 assert_eq!(config.streaming.max_frame_size, 64 * 1024);
352 assert_eq!(config.simd.batch_size, 100);
353 }
354
355 #[test]
356 fn test_pjs_config_default_validates() {
357 PjsConfig::default()
358 .validate()
359 .expect("PjsConfig::default() must be valid");
360 }
361
362 #[test]
363 fn test_streaming_config_default_validates() {
364 StreamingConfig::default()
365 .validate()
366 .expect("StreamingConfig::default() must be valid");
367 }
368
369 #[test]
370 fn test_parser_config_default_validates() {
371 ParserConfig::default()
372 .validate()
373 .expect("ParserConfig::default() must be valid");
374 }
375
376 #[test]
377 fn test_simd_config_default_validates() {
378 SimdConfig::default()
379 .validate()
380 .expect("SimdConfig::default() must be valid");
381 }
382
383 #[test]
384 fn test_streaming_rejects_zero_max_frame_size() {
385 let cfg = StreamingConfig {
386 max_frame_size: 0,
387 ..StreamingConfig::default()
388 };
389 let err = cfg.validate().unwrap_err();
390 assert!(matches!(
391 err,
392 ConfigError::MustBePositive {
393 section: "streaming",
394 field: "max_frame_size"
395 }
396 ));
397 }
398
399 #[test]
400 fn test_streaming_rejects_zero_operation_timeout_ms() {
401 let cfg = StreamingConfig {
402 operation_timeout_ms: 0,
403 ..StreamingConfig::default()
404 };
405 let err = cfg.validate().unwrap_err();
406 assert!(matches!(
407 err,
408 ConfigError::MustBePositive {
409 section: "streaming",
410 field: "operation_timeout_ms"
411 }
412 ));
413 }
414
415 #[test]
416 fn test_simd_rejects_non_power_of_two_alignment() {
417 let cfg = SimdConfig {
418 avx512_alignment: 3,
419 ..SimdConfig::default()
420 };
421 let err = cfg.validate().unwrap_err();
422 assert!(matches!(
423 err,
424 ConfigError::InconsistentBounds {
425 section: "simd",
426 ..
427 }
428 ));
429 }
430
431 #[test]
432 fn test_low_latency_profile() {
433 let config = PjsConfig::low_latency();
434 assert_eq!(config.streaming.max_frame_size, 16 * 1024);
435 assert!(!config.parser.enable_semantics);
436 assert_eq!(config.streaming.operation_timeout_ms, 1000);
437 }
438
439 #[test]
440 fn test_high_throughput_profile() {
441 let config = PjsConfig::high_throughput();
442 assert_eq!(config.streaming.max_frame_size, 256 * 1024);
443 assert!(config.parser.enable_semantics);
444 assert!(config.simd.enable_stats);
445 }
446
447 #[test]
448 fn test_mobile_profile() {
449 let config = PjsConfig::mobile();
450 assert_eq!(config.streaming.max_frame_size, 8 * 1024);
451 assert_eq!(config.compression.min_net_savings, 4);
452 assert_eq!(config.simd.vectorized_chunk_size, 8);
453 }
454
455 #[test]
456 fn test_compression_with_custom_config() {
457 use crate::compression::{CompressionConfig, SchemaAnalyzer};
458 use serde_json::json;
459
460 let compression_config = CompressionConfig {
462 min_net_savings: 0,
463 min_frequency_count: 1,
464 ..Default::default()
465 };
466
467 let mut analyzer = SchemaAnalyzer::with_config(compression_config);
468
469 let data = json!({
471 "users": [
472 {"status": "active", "role": "user"},
473 {"status": "active", "role": "user"}
474 ]
475 });
476
477 let strategy = analyzer.analyze(&data).unwrap();
478
479 match strategy {
481 crate::compression::CompressionStrategy::Dictionary { .. }
482 | crate::compression::CompressionStrategy::Hybrid { .. } => {
483 }
485 _ => {
486 }
488 }
489 }
490}