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 Ok(())
247 }
248
249 pub fn low_latency() -> Self {
251 Self {
252 security: SecurityConfig::development(),
253 compression: CompressionConfig::default(),
254 parser: ParserConfig {
255 max_input_size_mb: 10,
256 buffer_initial_capacity: 4096, simd_min_size: 2048, enable_semantics: false, },
260 streaming: StreamingConfig {
261 max_frame_size: 16 * 1024, default_chunk_size: 512,
263 operation_timeout_ms: 1000, max_bandwidth_bps: 10_000_000, },
266 simd: SimdConfig {
267 batch_size: 50,
268 initial_capacity: 4096, avx512_alignment: 64,
270 vectorized_chunk_size: 16,
271 enable_stats: false,
272 },
273 }
274 }
275
276 pub fn high_throughput() -> Self {
278 Self {
279 security: SecurityConfig::high_throughput(),
280 compression: CompressionConfig::default(),
281 parser: ParserConfig {
282 max_input_size_mb: 1000, buffer_initial_capacity: 32768, simd_min_size: 8192, enable_semantics: true,
286 },
287 streaming: StreamingConfig {
288 max_frame_size: 256 * 1024, default_chunk_size: 4096,
290 operation_timeout_ms: 30000, max_bandwidth_bps: 100_000_000, },
293 simd: SimdConfig {
294 batch_size: 500,
295 initial_capacity: 32768, avx512_alignment: 64,
297 vectorized_chunk_size: 64,
298 enable_stats: true,
299 },
300 }
301 }
302
303 pub fn mobile() -> Self {
305 Self {
306 security: SecurityConfig::low_memory(),
307 compression: CompressionConfig {
308 min_array_length: 1,
309 min_string_length: 2,
310 min_frequency_count: 1,
311 uuid_compression_potential: 0.5,
312 string_dict_threshold: 25.0, delta_threshold: 15.0, min_delta_potential: 0.2,
315 run_length_threshold: 10.0, min_compression_potential: 0.3,
317 min_numeric_sequence_size: 2,
318 },
319 parser: ParserConfig {
320 max_input_size_mb: 10,
321 buffer_initial_capacity: 2048, simd_min_size: 1024, enable_semantics: false,
324 },
325 streaming: StreamingConfig {
326 max_frame_size: 8 * 1024, default_chunk_size: 256,
328 operation_timeout_ms: 10000, max_bandwidth_bps: 100_000, },
331 simd: SimdConfig {
332 batch_size: 25,
333 initial_capacity: 2048, avx512_alignment: 32, vectorized_chunk_size: 8,
336 enable_stats: false,
337 },
338 }
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345
346 #[test]
347 fn test_default_config() {
348 let config = PjsConfig::default();
349 assert_eq!(config.parser.max_input_size_mb, 100);
350 assert_eq!(config.streaming.max_frame_size, 64 * 1024);
351 assert_eq!(config.simd.batch_size, 100);
352 }
353
354 #[test]
355 fn test_pjs_config_default_validates() {
356 PjsConfig::default()
357 .validate()
358 .expect("PjsConfig::default() must be valid");
359 }
360
361 #[test]
362 fn test_streaming_config_default_validates() {
363 StreamingConfig::default()
364 .validate()
365 .expect("StreamingConfig::default() must be valid");
366 }
367
368 #[test]
369 fn test_parser_config_default_validates() {
370 ParserConfig::default()
371 .validate()
372 .expect("ParserConfig::default() must be valid");
373 }
374
375 #[test]
376 fn test_simd_config_default_validates() {
377 SimdConfig::default()
378 .validate()
379 .expect("SimdConfig::default() must be valid");
380 }
381
382 #[test]
383 fn test_streaming_rejects_zero_max_frame_size() {
384 let cfg = StreamingConfig {
385 max_frame_size: 0,
386 ..StreamingConfig::default()
387 };
388 let err = cfg.validate().unwrap_err();
389 assert!(matches!(
390 err,
391 ConfigError::MustBePositive {
392 section: "streaming",
393 field: "max_frame_size"
394 }
395 ));
396 }
397
398 #[test]
399 fn test_streaming_rejects_zero_operation_timeout_ms() {
400 let cfg = StreamingConfig {
401 operation_timeout_ms: 0,
402 ..StreamingConfig::default()
403 };
404 let err = cfg.validate().unwrap_err();
405 assert!(matches!(
406 err,
407 ConfigError::MustBePositive {
408 section: "streaming",
409 field: "operation_timeout_ms"
410 }
411 ));
412 }
413
414 #[test]
415 fn test_simd_rejects_non_power_of_two_alignment() {
416 let cfg = SimdConfig {
417 avx512_alignment: 3,
418 ..SimdConfig::default()
419 };
420 let err = cfg.validate().unwrap_err();
421 assert!(matches!(
422 err,
423 ConfigError::InconsistentBounds {
424 section: "simd",
425 ..
426 }
427 ));
428 }
429
430 #[test]
431 fn test_low_latency_profile() {
432 let config = PjsConfig::low_latency();
433 assert_eq!(config.streaming.max_frame_size, 16 * 1024);
434 assert!(!config.parser.enable_semantics);
435 assert_eq!(config.streaming.operation_timeout_ms, 1000);
436 }
437
438 #[test]
439 fn test_high_throughput_profile() {
440 let config = PjsConfig::high_throughput();
441 assert_eq!(config.streaming.max_frame_size, 256 * 1024);
442 assert!(config.parser.enable_semantics);
443 assert!(config.simd.enable_stats);
444 }
445
446 #[test]
447 fn test_mobile_profile() {
448 let config = PjsConfig::mobile();
449 assert_eq!(config.streaming.max_frame_size, 8 * 1024);
450 assert_eq!(config.compression.string_dict_threshold, 25.0);
451 assert_eq!(config.simd.vectorized_chunk_size, 8);
452 }
453
454 #[test]
455 fn test_compression_with_custom_config() {
456 use crate::compression::{CompressionConfig, SchemaAnalyzer};
457 use serde_json::json;
458
459 let compression_config = CompressionConfig {
461 string_dict_threshold: 10.0, min_frequency_count: 1,
463 ..Default::default()
464 };
465
466 let mut analyzer = SchemaAnalyzer::with_config(compression_config);
467
468 let data = json!({
470 "users": [
471 {"status": "active", "role": "user"},
472 {"status": "active", "role": "user"}
473 ]
474 });
475
476 let strategy = analyzer.analyze(&data).unwrap();
477
478 match strategy {
480 crate::compression::CompressionStrategy::Dictionary { .. }
481 | crate::compression::CompressionStrategy::Hybrid { .. } => {
482 }
484 _ => {
485 }
487 }
488 }
489}