1use crate::user_config::helpers::resolve_record_setting;
7use bytesize::ByteSize;
8use serde::Deserialize;
9use std::time::Duration;
10use target_spec::{Platform, TargetSpec};
11
12pub const MIN_MAX_OUTPUT_SIZE: ByteSize = ByteSize::b(1000);
18
19pub const MAX_MAX_OUTPUT_SIZE: ByteSize = ByteSize::mib(256);
27
28#[derive(Clone, Debug, Default, Deserialize)]
33#[serde(rename_all = "kebab-case")]
34pub struct DeserializedRecordConfig {
35 #[serde(default)]
41 pub enabled: Option<bool>,
42
43 #[serde(default)]
45 pub max_records: Option<usize>,
46
47 #[serde(default)]
49 pub max_total_size: Option<ByteSize>,
50
51 #[serde(default, with = "humantime_serde")]
53 pub max_age: Option<Duration>,
54
55 #[serde(default)]
57 pub max_output_size: Option<ByteSize>,
58}
59
60#[derive(Clone, Debug, Deserialize)]
65#[serde(rename_all = "kebab-case")]
66pub struct DefaultRecordConfig {
67 pub enabled: bool,
69
70 pub max_records: usize,
72
73 pub max_total_size: ByteSize,
75
76 #[serde(with = "humantime_serde")]
78 pub max_age: Duration,
79
80 pub max_output_size: ByteSize,
82}
83
84#[derive(Clone, Debug, Default, Deserialize)]
89#[serde(rename_all = "kebab-case")]
90pub(in crate::user_config) struct DeserializedRecordOverrideData {
91 pub(in crate::user_config) enabled: Option<bool>,
93
94 pub(in crate::user_config) max_records: Option<usize>,
96
97 pub(in crate::user_config) max_total_size: Option<ByteSize>,
99
100 #[serde(default, with = "humantime_serde")]
102 pub(in crate::user_config) max_age: Option<Duration>,
103
104 pub(in crate::user_config) max_output_size: Option<ByteSize>,
106}
107
108#[derive(Clone, Debug)]
113pub(in crate::user_config) struct CompiledRecordOverride {
114 platform_spec: TargetSpec,
115 data: RecordOverrideData,
116}
117
118impl CompiledRecordOverride {
119 pub(in crate::user_config) fn new(
121 platform_spec: TargetSpec,
122 data: DeserializedRecordOverrideData,
123 ) -> Self {
124 Self {
125 platform_spec,
126 data: RecordOverrideData {
127 enabled: data.enabled,
128 max_records: data.max_records,
129 max_total_size: data.max_total_size,
130 max_age: data.max_age,
131 max_output_size: data.max_output_size,
132 },
133 }
134 }
135
136 pub(in crate::user_config) fn matches(&self, host_platform: &Platform) -> bool {
141 self.platform_spec
142 .eval(host_platform)
143 .unwrap_or(false)
144 }
145
146 pub(in crate::user_config) fn data(&self) -> &RecordOverrideData {
148 &self.data
149 }
150}
151
152#[derive(Clone, Debug, Default)]
154pub(in crate::user_config) struct RecordOverrideData {
155 enabled: Option<bool>,
156 max_records: Option<usize>,
157 max_total_size: Option<ByteSize>,
158 max_age: Option<Duration>,
159 max_output_size: Option<ByteSize>,
160}
161
162impl RecordOverrideData {
163 pub(in crate::user_config) fn enabled(&self) -> Option<&bool> {
165 self.enabled.as_ref()
166 }
167
168 pub(in crate::user_config) fn max_records(&self) -> Option<&usize> {
170 self.max_records.as_ref()
171 }
172
173 pub(in crate::user_config) fn max_total_size(&self) -> Option<&ByteSize> {
175 self.max_total_size.as_ref()
176 }
177
178 pub(in crate::user_config) fn max_age(&self) -> Option<&Duration> {
180 self.max_age.as_ref()
181 }
182
183 pub(in crate::user_config) fn max_output_size(&self) -> Option<&ByteSize> {
185 self.max_output_size.as_ref()
186 }
187}
188
189#[derive(Clone, Debug)]
191pub struct RecordConfig {
192 pub enabled: bool,
197
198 pub max_records: usize,
200
201 pub max_total_size: ByteSize,
203
204 pub max_age: Duration,
206
207 pub max_output_size: ByteSize,
209}
210
211impl RecordConfig {
212 pub(in crate::user_config) fn resolve(
227 default_config: &DefaultRecordConfig,
228 default_overrides: &[CompiledRecordOverride],
229 user_config: Option<&DeserializedRecordConfig>,
230 user_overrides: &[CompiledRecordOverride],
231 host_platform: &Platform,
232 ) -> Self {
233 let mut max_output_size = resolve_record_setting(
234 &default_config.max_output_size,
235 default_overrides,
236 user_config.and_then(|c| c.max_output_size.as_ref()),
237 user_overrides,
238 host_platform,
239 |data| data.max_output_size(),
240 );
241
242 if max_output_size < MIN_MAX_OUTPUT_SIZE {
244 tracing::warn!(
245 "max-output-size ({}) is below minimum ({}), using minimum",
246 max_output_size,
247 MIN_MAX_OUTPUT_SIZE,
248 );
249 max_output_size = MIN_MAX_OUTPUT_SIZE;
250 } else if max_output_size > MAX_MAX_OUTPUT_SIZE {
251 tracing::warn!(
252 "max-output-size ({}) exceeds maximum ({}), using maximum",
253 max_output_size,
254 MAX_MAX_OUTPUT_SIZE,
255 );
256 max_output_size = MAX_MAX_OUTPUT_SIZE;
257 }
258
259 Self {
260 enabled: resolve_record_setting(
261 &default_config.enabled,
262 default_overrides,
263 user_config.and_then(|c| c.enabled.as_ref()),
264 user_overrides,
265 host_platform,
266 |data| data.enabled(),
267 ),
268 max_records: resolve_record_setting(
269 &default_config.max_records,
270 default_overrides,
271 user_config.and_then(|c| c.max_records.as_ref()),
272 user_overrides,
273 host_platform,
274 |data| data.max_records(),
275 ),
276 max_total_size: resolve_record_setting(
277 &default_config.max_total_size,
278 default_overrides,
279 user_config.and_then(|c| c.max_total_size.as_ref()),
280 user_overrides,
281 host_platform,
282 |data| data.max_total_size(),
283 ),
284 max_age: resolve_record_setting(
285 &default_config.max_age,
286 default_overrides,
287 user_config.and_then(|c| c.max_age.as_ref()),
288 user_overrides,
289 host_platform,
290 |data| data.max_age(),
291 ),
292 max_output_size,
293 }
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300 use crate::platform::detect_host_platform_for_tests;
301
302 #[test]
303 fn test_deserialized_record_config_parsing() {
304 let config: DeserializedRecordConfig = toml::from_str(
306 r#"
307 enabled = true
308 max-records = 50
309 max-total-size = "2GB"
310 max-age = "7d"
311 max-output-size = "5MB"
312 "#,
313 )
314 .unwrap();
315
316 assert_eq!(config.enabled, Some(true));
317 assert_eq!(config.max_records, Some(50));
318 assert_eq!(config.max_total_size, Some(ByteSize::gb(2)));
319 assert_eq!(config.max_age, Some(Duration::from_secs(7 * 24 * 60 * 60)));
320 assert_eq!(config.max_output_size, Some(ByteSize::mb(5)));
321
322 let config: DeserializedRecordConfig = toml::from_str(
324 r#"
325 max-records = 100
326 "#,
327 )
328 .unwrap();
329
330 assert!(config.enabled.is_none());
331 assert_eq!(config.max_records, Some(100));
332 assert!(config.max_total_size.is_none());
333 assert!(config.max_age.is_none());
334 assert!(config.max_output_size.is_none());
335
336 let config: DeserializedRecordConfig = toml::from_str("").unwrap();
338 assert!(config.enabled.is_none());
339 assert!(config.max_records.is_none());
340 assert!(config.max_total_size.is_none());
341 assert!(config.max_age.is_none());
342 assert!(config.max_output_size.is_none());
343 }
344
345 #[test]
346 fn test_default_record_config_parsing() {
347 let config: DefaultRecordConfig = toml::from_str(
348 r#"
349 enabled = true
350 max-records = 100
351 max-total-size = "1GB"
352 max-age = "30d"
353 max-output-size = "10MB"
354 "#,
355 )
356 .unwrap();
357
358 assert!(config.enabled);
359 assert_eq!(config.max_records, 100);
360 assert_eq!(config.max_total_size, ByteSize::gb(1));
361 assert_eq!(config.max_age, Duration::from_secs(30 * 24 * 60 * 60));
362 assert_eq!(config.max_output_size, ByteSize::mb(10));
363 }
364
365 #[test]
366 fn test_resolve_uses_defaults() {
367 let defaults = DefaultRecordConfig {
368 enabled: false,
369 max_records: 100,
370 max_total_size: ByteSize::gb(1),
371 max_age: Duration::from_secs(30 * 24 * 60 * 60),
372 max_output_size: ByteSize::mb(10),
373 };
374
375 let host = detect_host_platform_for_tests();
376 let resolved = RecordConfig::resolve(&defaults, &[], None, &[], &host);
377
378 assert!(!resolved.enabled);
379 assert_eq!(resolved.max_records, 100);
380 assert_eq!(resolved.max_total_size, ByteSize::gb(1));
381 assert_eq!(resolved.max_age, Duration::from_secs(30 * 24 * 60 * 60));
382 assert_eq!(resolved.max_output_size, ByteSize::mb(10));
383 }
384
385 #[test]
386 fn test_resolve_user_overrides_defaults() {
387 let defaults = DefaultRecordConfig {
388 enabled: false,
389 max_records: 100,
390 max_total_size: ByteSize::gb(1),
391 max_age: Duration::from_secs(30 * 24 * 60 * 60),
392 max_output_size: ByteSize::mb(10),
393 };
394
395 let user_config = DeserializedRecordConfig {
396 enabled: Some(true),
397 max_records: Some(50),
398 max_total_size: None,
399 max_age: Some(Duration::from_secs(7 * 24 * 60 * 60)),
400 max_output_size: Some(ByteSize::mb(5)),
401 };
402
403 let host = detect_host_platform_for_tests();
404 let resolved = RecordConfig::resolve(&defaults, &[], Some(&user_config), &[], &host);
405
406 assert!(resolved.enabled); assert_eq!(resolved.max_records, 50); assert_eq!(resolved.max_total_size, ByteSize::gb(1)); assert_eq!(resolved.max_age, Duration::from_secs(7 * 24 * 60 * 60)); assert_eq!(resolved.max_output_size, ByteSize::mb(5)); }
412
413 #[test]
414 fn test_resolve_clamps_small_max_output_size() {
415 let defaults = DefaultRecordConfig {
416 enabled: false,
417 max_records: 100,
418 max_total_size: ByteSize::gb(1),
419 max_age: Duration::from_secs(30 * 24 * 60 * 60),
420 max_output_size: ByteSize::mb(10),
421 };
422
423 let user_config = DeserializedRecordConfig {
425 enabled: None,
426 max_records: None,
427 max_total_size: None,
428 max_age: None,
429 max_output_size: Some(ByteSize::b(100)), };
431
432 let host = detect_host_platform_for_tests();
433 let resolved = RecordConfig::resolve(&defaults, &[], Some(&user_config), &[], &host);
434
435 assert_eq!(resolved.max_output_size, MIN_MAX_OUTPUT_SIZE);
437 }
438
439 #[test]
440 fn test_resolve_accepts_value_at_minimum() {
441 let defaults = DefaultRecordConfig {
442 enabled: false,
443 max_records: 100,
444 max_total_size: ByteSize::gb(1),
445 max_age: Duration::from_secs(30 * 24 * 60 * 60),
446 max_output_size: ByteSize::mb(10),
447 };
448
449 let user_config = DeserializedRecordConfig {
451 enabled: None,
452 max_records: None,
453 max_total_size: None,
454 max_age: None,
455 max_output_size: Some(MIN_MAX_OUTPUT_SIZE),
456 };
457
458 let host = detect_host_platform_for_tests();
459 let resolved = RecordConfig::resolve(&defaults, &[], Some(&user_config), &[], &host);
460
461 assert_eq!(resolved.max_output_size, MIN_MAX_OUTPUT_SIZE);
463 }
464
465 #[test]
466 fn test_resolve_clamps_large_max_output_size() {
467 let defaults = DefaultRecordConfig {
468 enabled: false,
469 max_records: 100,
470 max_total_size: ByteSize::gb(1),
471 max_age: Duration::from_secs(30 * 24 * 60 * 60),
472 max_output_size: ByteSize::mb(10),
473 };
474
475 let user_config = DeserializedRecordConfig {
477 enabled: None,
478 max_records: None,
479 max_total_size: None,
480 max_age: None,
481 max_output_size: Some(ByteSize::gib(1)), };
483
484 let host = detect_host_platform_for_tests();
485 let resolved = RecordConfig::resolve(&defaults, &[], Some(&user_config), &[], &host);
486
487 assert_eq!(resolved.max_output_size, MAX_MAX_OUTPUT_SIZE);
489 }
490
491 #[test]
492 fn test_resolve_accepts_value_at_maximum() {
493 let defaults = DefaultRecordConfig {
494 enabled: false,
495 max_records: 100,
496 max_total_size: ByteSize::gb(1),
497 max_age: Duration::from_secs(30 * 24 * 60 * 60),
498 max_output_size: ByteSize::mb(10),
499 };
500
501 let user_config = DeserializedRecordConfig {
503 enabled: None,
504 max_records: None,
505 max_total_size: None,
506 max_age: None,
507 max_output_size: Some(MAX_MAX_OUTPUT_SIZE),
508 };
509
510 let host = detect_host_platform_for_tests();
511 let resolved = RecordConfig::resolve(&defaults, &[], Some(&user_config), &[], &host);
512
513 assert_eq!(resolved.max_output_size, MAX_MAX_OUTPUT_SIZE);
515 }
516
517 fn make_override(
519 platform: &str,
520 data: DeserializedRecordOverrideData,
521 ) -> CompiledRecordOverride {
522 let platform_spec =
523 TargetSpec::new(platform.to_string()).expect("valid platform spec in test");
524 CompiledRecordOverride::new(platform_spec, data)
525 }
526
527 #[test]
528 fn test_resolve_user_override_applies() {
529 let defaults = DefaultRecordConfig {
530 enabled: false,
531 max_records: 100,
532 max_total_size: ByteSize::gb(1),
533 max_age: Duration::from_secs(30 * 24 * 60 * 60),
534 max_output_size: ByteSize::mb(10),
535 };
536
537 let override_ = make_override(
539 "cfg(all())",
540 DeserializedRecordOverrideData {
541 enabled: Some(true),
542 max_records: Some(50),
543 ..Default::default()
544 },
545 );
546
547 let host = detect_host_platform_for_tests();
548 let resolved = RecordConfig::resolve(&defaults, &[], None, &[override_], &host);
549
550 assert!(resolved.enabled);
551 assert_eq!(resolved.max_records, 50);
552 assert_eq!(resolved.max_total_size, ByteSize::gb(1)); assert_eq!(resolved.max_age, Duration::from_secs(30 * 24 * 60 * 60)); assert_eq!(resolved.max_output_size, ByteSize::mb(10)); }
556
557 #[test]
558 fn test_resolve_default_override_applies() {
559 let defaults = DefaultRecordConfig {
560 enabled: false,
561 max_records: 100,
562 max_total_size: ByteSize::gb(1),
563 max_age: Duration::from_secs(30 * 24 * 60 * 60),
564 max_output_size: ByteSize::mb(10),
565 };
566
567 let override_ = make_override(
569 "cfg(all())",
570 DeserializedRecordOverrideData {
571 enabled: Some(true),
572 max_records: Some(50),
573 ..Default::default()
574 },
575 );
576
577 let host = detect_host_platform_for_tests();
578 let resolved = RecordConfig::resolve(&defaults, &[override_], None, &[], &host);
579
580 assert!(resolved.enabled);
581 assert_eq!(resolved.max_records, 50);
582 assert_eq!(resolved.max_total_size, ByteSize::gb(1)); }
584
585 #[test]
586 fn test_resolve_platform_override_no_match() {
587 let defaults = DefaultRecordConfig {
588 enabled: false,
589 max_records: 100,
590 max_total_size: ByteSize::gb(1),
591 max_age: Duration::from_secs(30 * 24 * 60 * 60),
592 max_output_size: ByteSize::mb(10),
593 };
594
595 let override_ = make_override(
598 "cfg(any())",
599 DeserializedRecordOverrideData {
600 enabled: Some(true),
601 max_records: Some(50),
602 max_total_size: Some(ByteSize::gb(2)),
603 max_age: Some(Duration::from_secs(7 * 24 * 60 * 60)),
604 max_output_size: Some(ByteSize::mb(5)),
605 },
606 );
607
608 let host = detect_host_platform_for_tests();
609 let resolved = RecordConfig::resolve(&defaults, &[], None, &[override_], &host);
610
611 assert!(!resolved.enabled);
613 assert_eq!(resolved.max_records, 100);
614 assert_eq!(resolved.max_total_size, ByteSize::gb(1));
615 assert_eq!(resolved.max_age, Duration::from_secs(30 * 24 * 60 * 60));
616 assert_eq!(resolved.max_output_size, ByteSize::mb(10));
617 }
618
619 #[test]
620 fn test_resolve_first_matching_user_override_wins() {
621 let defaults = DefaultRecordConfig {
622 enabled: false,
623 max_records: 100,
624 max_total_size: ByteSize::gb(1),
625 max_age: Duration::from_secs(30 * 24 * 60 * 60),
626 max_output_size: ByteSize::mb(10),
627 };
628
629 let override1 = make_override(
631 "cfg(all())",
632 DeserializedRecordOverrideData {
633 enabled: Some(true),
634 ..Default::default()
635 },
636 );
637
638 let override2 = make_override(
639 "cfg(all())",
640 DeserializedRecordOverrideData {
641 enabled: Some(false), max_records: Some(50),
643 ..Default::default()
644 },
645 );
646
647 let host = detect_host_platform_for_tests();
648 let resolved = RecordConfig::resolve(&defaults, &[], None, &[override1, override2], &host);
649
650 assert!(resolved.enabled);
652 assert_eq!(resolved.max_records, 50);
654 }
655
656 #[test]
657 fn test_resolve_user_override_beats_default_override() {
658 let defaults = DefaultRecordConfig {
659 enabled: false,
660 max_records: 100,
661 max_total_size: ByteSize::gb(1),
662 max_age: Duration::from_secs(30 * 24 * 60 * 60),
663 max_output_size: ByteSize::mb(10),
664 };
665
666 let user_override = make_override(
668 "cfg(all())",
669 DeserializedRecordOverrideData {
670 enabled: Some(true),
671 ..Default::default()
672 },
673 );
674
675 let default_override = make_override(
677 "cfg(all())",
678 DeserializedRecordOverrideData {
679 enabled: Some(false), max_records: Some(50),
681 ..Default::default()
682 },
683 );
684
685 let host = detect_host_platform_for_tests();
686 let resolved = RecordConfig::resolve(
687 &defaults,
688 &[default_override],
689 None,
690 &[user_override],
691 &host,
692 );
693
694 assert!(resolved.enabled);
696 assert_eq!(resolved.max_records, 50);
698 }
699
700 #[test]
701 fn test_resolve_override_beats_user_base() {
702 let defaults = DefaultRecordConfig {
703 enabled: false,
704 max_records: 100,
705 max_total_size: ByteSize::gb(1),
706 max_age: Duration::from_secs(30 * 24 * 60 * 60),
707 max_output_size: ByteSize::mb(10),
708 };
709
710 let user_config = DeserializedRecordConfig {
712 enabled: Some(false),
713 max_records: Some(25),
714 ..Default::default()
715 };
716
717 let default_override = make_override(
719 "cfg(all())",
720 DeserializedRecordOverrideData {
721 enabled: Some(true),
722 ..Default::default()
723 },
724 );
725
726 let host = detect_host_platform_for_tests();
727 let resolved = RecordConfig::resolve(
728 &defaults,
729 &[default_override],
730 Some(&user_config),
731 &[],
732 &host,
733 );
734
735 assert!(resolved.enabled);
737 assert_eq!(resolved.max_records, 25);
739 }
740
741 #[test]
742 fn test_resolve_override_clamps_max_output_size() {
743 let defaults = DefaultRecordConfig {
744 enabled: false,
745 max_records: 100,
746 max_total_size: ByteSize::gb(1),
747 max_age: Duration::from_secs(30 * 24 * 60 * 60),
748 max_output_size: ByteSize::mb(10),
749 };
750
751 let override_ = make_override(
753 "cfg(all())",
754 DeserializedRecordOverrideData {
755 max_output_size: Some(ByteSize::b(100)), ..Default::default()
757 },
758 );
759
760 let host = detect_host_platform_for_tests();
761 let resolved = RecordConfig::resolve(&defaults, &[], None, &[override_], &host);
762
763 assert_eq!(resolved.max_output_size, MIN_MAX_OUTPUT_SIZE);
765 }
766}