1use serde::{Deserialize, Serialize};
5use std::collections::HashSet;
6use std::env;
7use std::fmt;
8use std::str::FromStr;
9use std::sync::{OnceLock, RwLock};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13#[serde(rename_all = "snake_case")]
14#[non_exhaustive]
15pub enum Feature {
16 #[serde(alias = "sign_separate")]
19 SignSeparate,
20
21 #[serde(alias = "renames_r4_r6")]
23 RenamesR4R6,
24
25 #[serde(alias = "comp_1")]
27 Comp1,
28
29 #[serde(alias = "comp_2")]
31 Comp2,
32
33 #[serde(alias = "audit_system")]
36 AuditSystem,
37
38 #[serde(alias = "sox_compliance")]
40 SoxCompliance,
41
42 #[serde(alias = "hipaa_compliance")]
44 HipaaCompliance,
45
46 #[serde(alias = "gdpr_compliance")]
48 GdprCompliance,
49
50 #[serde(alias = "pci_dss_compliance")]
52 PciDssCompliance,
53
54 #[serde(alias = "security_monitoring")]
56 SecurityMonitoring,
57
58 #[serde(alias = "advanced_optimization")]
61 AdvancedOptimization,
62
63 #[serde(alias = "lru_cache")]
65 LruCache,
66
67 #[serde(alias = "parallel_decode")]
69 ParallelDecode,
70
71 #[serde(alias = "zero_copy")]
73 ZeroCopy,
74
75 #[serde(alias = "verbose_logging")]
78 VerboseLogging,
79
80 #[serde(alias = "diagnostic_output")]
82 DiagnosticOutput,
83
84 #[serde(alias = "profiling")]
86 Profiling,
87
88 #[serde(alias = "memory_tracking")]
90 MemoryTracking,
91
92 #[serde(alias = "mutation_testing")]
95 MutationTesting,
96
97 #[serde(alias = "fuzzing_integration")]
99 FuzzingIntegration,
100
101 #[serde(alias = "coverage_instrumentation")]
103 CoverageInstrumentation,
104
105 #[serde(alias = "property_based_testing")]
107 PropertyBasedTesting,
108}
109
110impl Feature {
111 #[inline]
122 #[must_use]
123 pub const fn category(self) -> FeatureCategory {
124 match self {
125 Feature::SignSeparate | Feature::RenamesR4R6 | Feature::Comp1 | Feature::Comp2 => {
126 FeatureCategory::Experimental
127 }
128 Feature::AuditSystem
129 | Feature::SoxCompliance
130 | Feature::HipaaCompliance
131 | Feature::GdprCompliance
132 | Feature::PciDssCompliance
133 | Feature::SecurityMonitoring => FeatureCategory::Enterprise,
134 Feature::AdvancedOptimization
135 | Feature::LruCache
136 | Feature::ParallelDecode
137 | Feature::ZeroCopy => FeatureCategory::Performance,
138 Feature::VerboseLogging
139 | Feature::DiagnosticOutput
140 | Feature::Profiling
141 | Feature::MemoryTracking => FeatureCategory::Debug,
142 Feature::MutationTesting
143 | Feature::FuzzingIntegration
144 | Feature::CoverageInstrumentation
145 | Feature::PropertyBasedTesting => FeatureCategory::Testing,
146 }
147 }
148
149 #[inline]
159 #[must_use]
160 pub const fn lifecycle(self) -> FeatureLifecycle {
161 match self {
162 Feature::SignSeparate | Feature::Comp1 | Feature::Comp2 => FeatureLifecycle::Stable,
163 Feature::RenamesR4R6
164 | Feature::AuditSystem
165 | Feature::SoxCompliance
166 | Feature::HipaaCompliance
167 | Feature::GdprCompliance
168 | Feature::PciDssCompliance
169 | Feature::SecurityMonitoring
170 | Feature::AdvancedOptimization
171 | Feature::LruCache
172 | Feature::ParallelDecode
173 | Feature::ZeroCopy
174 | Feature::VerboseLogging
175 | Feature::DiagnosticOutput
176 | Feature::Profiling
177 | Feature::MemoryTracking
178 | Feature::MutationTesting
179 | Feature::FuzzingIntegration
180 | Feature::CoverageInstrumentation
181 | Feature::PropertyBasedTesting => FeatureLifecycle::Experimental,
182 }
183 }
184
185 #[inline]
187 #[must_use]
188 pub const fn default_enabled(self) -> bool {
189 match self {
190 Feature::SignSeparate | Feature::Comp1 | Feature::Comp2 | Feature::LruCache => true,
191 Feature::RenamesR4R6
192 | Feature::AuditSystem
193 | Feature::SoxCompliance
194 | Feature::HipaaCompliance
195 | Feature::GdprCompliance
196 | Feature::PciDssCompliance
197 | Feature::SecurityMonitoring
198 | Feature::AdvancedOptimization
199 | Feature::ParallelDecode
200 | Feature::ZeroCopy
201 | Feature::VerboseLogging
202 | Feature::DiagnosticOutput
203 | Feature::Profiling
204 | Feature::MemoryTracking
205 | Feature::MutationTesting
206 | Feature::FuzzingIntegration
207 | Feature::CoverageInstrumentation
208 | Feature::PropertyBasedTesting => false,
209 }
210 }
211
212 #[inline]
214 #[must_use]
215 pub fn env_var_name(self) -> String {
216 format!("COPYBOOK_FF_{}", self.to_string().to_uppercase())
217 }
218
219 #[inline]
221 #[must_use]
222 pub const fn description(self) -> &'static str {
223 match self {
224 Feature::SignSeparate => "Enable SIGN SEPARATE clause support",
225 Feature::RenamesR4R6 => "Enable RENAMES R4-R6 advanced scenarios",
226 Feature::Comp1 => "Enable COMP-1 (single precision floating point) support",
227 Feature::Comp2 => "Enable COMP-2 (double precision floating point) support",
228 Feature::AuditSystem => "Enable audit system for compliance tracking",
229 Feature::SoxCompliance => "Enable SOX compliance validation",
230 Feature::HipaaCompliance => "Enable HIPAA compliance validation",
231 Feature::GdprCompliance => "Enable GDPR compliance validation",
232 Feature::PciDssCompliance => "Enable PCI DSS compliance validation",
233 Feature::SecurityMonitoring => "Enable security monitoring integration",
234 Feature::AdvancedOptimization => {
235 "Enable advanced optimization mode (SIMD, vectorization)"
236 }
237 Feature::LruCache => "Enable LRU cache for parsed copybooks",
238 Feature::ParallelDecode => "Enable parallel decoding for large files",
239 Feature::ZeroCopy => "Enable zero-copy parsing where possible",
240 Feature::VerboseLogging => "Enable verbose logging with detailed diagnostics",
241 Feature::DiagnosticOutput => "Enable diagnostic output for troubleshooting",
242 Feature::Profiling => "Enable CPU profiling hooks",
243 Feature::MemoryTracking => "Enable memory usage tracking",
244 Feature::MutationTesting => "Enable mutation testing hooks",
245 Feature::FuzzingIntegration => "Enable fuzzing integration points",
246 Feature::CoverageInstrumentation => "Enable test coverage instrumentation",
247 Feature::PropertyBasedTesting => "Enable property-based testing integration",
248 }
249 }
250}
251
252impl fmt::Display for Feature {
253 #[inline]
254 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255 let s = match self {
256 Feature::SignSeparate => "sign_separate",
257 Feature::RenamesR4R6 => "renames_r4_r6",
258 Feature::Comp1 => "comp_1",
259 Feature::Comp2 => "comp_2",
260 Feature::AuditSystem => "audit_system",
261 Feature::SoxCompliance => "sox_compliance",
262 Feature::HipaaCompliance => "hipaa_compliance",
263 Feature::GdprCompliance => "gdpr_compliance",
264 Feature::PciDssCompliance => "pci_dss_compliance",
265 Feature::SecurityMonitoring => "security_monitoring",
266 Feature::AdvancedOptimization => "advanced_optimization",
267 Feature::LruCache => "lru_cache",
268 Feature::ParallelDecode => "parallel_decode",
269 Feature::ZeroCopy => "zero_copy",
270 Feature::VerboseLogging => "verbose_logging",
271 Feature::DiagnosticOutput => "diagnostic_output",
272 Feature::Profiling => "profiling",
273 Feature::MemoryTracking => "memory_tracking",
274 Feature::MutationTesting => "mutation_testing",
275 Feature::FuzzingIntegration => "fuzzing_integration",
276 Feature::CoverageInstrumentation => "coverage_instrumentation",
277 Feature::PropertyBasedTesting => "property_based_testing",
278 };
279 write!(f, "{s}")
280 }
281}
282
283impl FromStr for Feature {
284 type Err = String;
285
286 #[inline]
287 fn from_str(s: &str) -> Result<Self, Self::Err> {
288 match s.to_lowercase().as_str() {
289 "sign_separate" => Ok(Self::SignSeparate),
290 "renames_r4_r6" => Ok(Self::RenamesR4R6),
291 "comp_1" => Ok(Self::Comp1),
292 "comp_2" => Ok(Self::Comp2),
293 "audit_system" => Ok(Self::AuditSystem),
294 "sox_compliance" => Ok(Self::SoxCompliance),
295 "hipaa_compliance" => Ok(Self::HipaaCompliance),
296 "gdpr_compliance" => Ok(Self::GdprCompliance),
297 "pci_dss_compliance" => Ok(Self::PciDssCompliance),
298 "security_monitoring" => Ok(Self::SecurityMonitoring),
299 "advanced_optimization" => Ok(Self::AdvancedOptimization),
300 "lru_cache" => Ok(Self::LruCache),
301 "parallel_decode" => Ok(Self::ParallelDecode),
302 "zero_copy" => Ok(Self::ZeroCopy),
303 "verbose_logging" => Ok(Self::VerboseLogging),
304 "diagnostic_output" => Ok(Self::DiagnosticOutput),
305 "profiling" => Ok(Self::Profiling),
306 "memory_tracking" => Ok(Self::MemoryTracking),
307 "mutation_testing" => Ok(Self::MutationTesting),
308 "fuzzing_integration" => Ok(Self::FuzzingIntegration),
309 "coverage_instrumentation" => Ok(Self::CoverageInstrumentation),
310 "property_based_testing" => Ok(Self::PropertyBasedTesting),
311 _ => Err(format!("Unknown feature flag: '{s}'")),
312 }
313 }
314}
315
316#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
318#[serde(rename_all = "snake_case")]
319#[non_exhaustive]
320pub enum FeatureCategory {
321 Experimental,
323 Enterprise,
325 Performance,
327 Debug,
329 Testing,
331}
332
333impl fmt::Display for FeatureCategory {
334 #[inline]
335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336 match self {
337 FeatureCategory::Experimental => write!(f, "experimental"),
338 FeatureCategory::Enterprise => write!(f, "enterprise"),
339 FeatureCategory::Performance => write!(f, "performance"),
340 FeatureCategory::Debug => write!(f, "debug"),
341 FeatureCategory::Testing => write!(f, "testing"),
342 }
343 }
344}
345
346#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
348#[serde(rename_all = "snake_case")]
349#[non_exhaustive]
350pub enum FeatureLifecycle {
351 Experimental,
353 Stable,
355 Deprecated,
357}
358
359impl fmt::Display for FeatureLifecycle {
360 #[inline]
361 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
362 match self {
363 FeatureLifecycle::Experimental => write!(f, "experimental"),
364 FeatureLifecycle::Stable => write!(f, "stable"),
365 FeatureLifecycle::Deprecated => write!(f, "deprecated"),
366 }
367 }
368}
369
370#[derive(Debug, Clone, Serialize, Deserialize)]
372pub struct FeatureFlags {
373 enabled: HashSet<Feature>,
374}
375
376impl Default for FeatureFlags {
377 #[inline]
378 fn default() -> Self {
379 let mut flags = Self {
380 enabled: HashSet::new(),
381 };
382
383 for feature in all_features() {
384 if feature.default_enabled() {
385 flags.enabled.insert(feature);
386 }
387 }
388
389 flags
390 }
391}
392
393impl FeatureFlags {
394 #[inline]
396 #[must_use]
397 pub fn global() -> &'static Self {
398 GLOBAL_FLAGS.get_or_init(Self::from_env)
399 }
400
401 #[inline]
403 pub fn set_global(flags: Self) {
404 let _ = GLOBAL_FLAGS.set(flags);
405 }
406
407 #[inline]
409 #[must_use]
410 pub fn from_env() -> Self {
411 let mut flags = Self::default();
412
413 for (key, value) in env::vars() {
414 if let Some(feature_name) = key.strip_prefix("COPYBOOK_FF_")
415 && let Ok(feature) = Feature::from_str(feature_name)
416 {
417 let enabled = matches!(
418 value.to_lowercase().as_str(),
419 "1" | "true" | "yes" | "on" | "enabled"
420 );
421 if enabled {
422 flags.enabled.insert(feature);
423 } else {
424 flags.enabled.remove(&feature);
425 }
426 }
427 }
428
429 flags
430 }
431
432 #[inline]
434 #[must_use]
435 pub fn is_enabled(&self, feature: Feature) -> bool {
436 self.enabled.contains(&feature)
437 }
438
439 #[inline]
441 pub fn enable(&mut self, feature: Feature) {
442 self.enabled.insert(feature);
443 }
444
445 #[inline]
447 pub fn disable(&mut self, feature: Feature) {
448 self.enabled.remove(&feature);
449 }
450
451 #[inline]
453 pub fn toggle(&mut self, feature: Feature) {
454 if self.enabled.contains(&feature) {
455 self.enabled.remove(&feature);
456 } else {
457 self.enabled.insert(feature);
458 }
459 }
460
461 #[inline]
463 pub fn enabled_features(&self) -> impl Iterator<Item = &Feature> {
464 self.enabled.iter()
465 }
466
467 #[inline]
469 #[must_use]
470 pub fn enabled_in_category(&self, category: FeatureCategory) -> Vec<Feature> {
471 self.enabled
472 .iter()
473 .filter(|f| f.category() == category)
474 .copied()
475 .collect()
476 }
477
478 #[inline]
480 #[must_use]
481 pub fn features_in_category(category: FeatureCategory) -> Vec<Feature> {
482 all_features()
483 .into_iter()
484 .filter(|f| f.category() == category)
485 .collect()
486 }
487
488 #[inline]
490 #[must_use]
491 pub fn builder() -> FeatureFlagsBuilder {
492 FeatureFlagsBuilder::default()
493 }
494}
495
496#[derive(Debug, Clone, Default)]
498pub struct FeatureFlagsBuilder {
499 flags: FeatureFlags,
500}
501
502impl FeatureFlagsBuilder {
503 #[inline]
505 #[must_use]
506 pub fn enable(mut self, feature: Feature) -> Self {
507 self.flags.enable(feature);
508 self
509 }
510
511 #[inline]
513 #[must_use]
514 pub fn disable(mut self, feature: Feature) -> Self {
515 self.flags.disable(feature);
516 self
517 }
518
519 #[inline]
521 #[must_use]
522 pub fn enable_category(mut self, category: FeatureCategory) -> Self {
523 for feature in FeatureFlags::features_in_category(category) {
524 self.flags.enable(feature);
525 }
526 self
527 }
528
529 #[inline]
531 #[must_use]
532 pub fn disable_category(mut self, category: FeatureCategory) -> Self {
533 for feature in FeatureFlags::features_in_category(category) {
534 self.flags.disable(feature);
535 }
536 self
537 }
538
539 #[inline]
541 #[must_use]
542 pub fn build(self) -> FeatureFlags {
543 self.flags
544 }
545}
546
547static GLOBAL_FLAGS: OnceLock<FeatureFlags> = OnceLock::new();
548
549#[derive(Debug)]
551pub struct FeatureFlagsHandle {
552 flags: RwLock<FeatureFlags>,
553}
554
555impl Default for FeatureFlagsHandle {
556 #[inline]
557 fn default() -> Self {
558 Self {
559 flags: RwLock::new(FeatureFlags::from_env()),
560 }
561 }
562}
563
564impl FeatureFlagsHandle {
565 #[inline]
567 #[must_use]
568 pub fn new() -> Self {
569 Self::default()
570 }
571
572 #[inline]
574 #[must_use]
575 pub fn is_enabled(&self, feature: Feature) -> bool {
576 self.flags
577 .read()
578 .is_ok_and(|flags| flags.is_enabled(feature))
579 }
580
581 #[inline]
583 pub fn enable(&self, feature: Feature) {
584 if let Ok(mut flags) = self.flags.write() {
585 flags.enable(feature);
586 }
587 }
588
589 #[inline]
591 pub fn disable(&self, feature: Feature) {
592 if let Ok(mut flags) = self.flags.write() {
593 flags.disable(feature);
594 }
595 }
596
597 #[inline]
599 pub fn toggle(&self, feature: Feature) {
600 if let Ok(mut flags) = self.flags.write() {
601 flags.toggle(feature);
602 }
603 }
604
605 #[inline]
607 #[must_use]
608 pub fn snapshot(&self) -> FeatureFlags {
609 self.flags
610 .read()
611 .map(|flags| flags.clone())
612 .unwrap_or_default()
613 }
614}
615
616impl Clone for FeatureFlagsHandle {
617 #[inline]
618 fn clone(&self) -> Self {
619 Self {
620 flags: RwLock::new(self.snapshot()),
621 }
622 }
623}
624
625#[inline]
627#[must_use]
628pub fn all_features() -> Vec<Feature> {
629 vec![
630 Feature::SignSeparate,
631 Feature::RenamesR4R6,
632 Feature::Comp1,
633 Feature::Comp2,
634 Feature::AuditSystem,
635 Feature::SoxCompliance,
636 Feature::HipaaCompliance,
637 Feature::GdprCompliance,
638 Feature::PciDssCompliance,
639 Feature::SecurityMonitoring,
640 Feature::AdvancedOptimization,
641 Feature::LruCache,
642 Feature::ParallelDecode,
643 Feature::ZeroCopy,
644 Feature::VerboseLogging,
645 Feature::DiagnosticOutput,
646 Feature::Profiling,
647 Feature::MemoryTracking,
648 Feature::MutationTesting,
649 Feature::FuzzingIntegration,
650 Feature::CoverageInstrumentation,
651 Feature::PropertyBasedTesting,
652 ]
653}
654
655#[cfg(test)]
656#[allow(clippy::expect_used)]
657#[allow(clippy::unwrap_used)]
658mod tests {
659 use super::*;
660
661 #[test]
662 fn test_feature_display() {
663 assert_eq!(Feature::SignSeparate.to_string(), "sign_separate");
664 assert_eq!(Feature::LruCache.to_string(), "lru_cache");
665 }
666
667 #[test]
668 fn test_feature_from_str() {
669 assert_eq!(
670 Feature::from_str("sign_separate").unwrap(),
671 Feature::SignSeparate
672 );
673 assert_eq!(Feature::from_str("LRU_CACHE").unwrap(), Feature::LruCache);
674 assert!(Feature::from_str("unknown_feature").is_err());
675 }
676
677 #[test]
678 fn test_feature_category() {
679 assert_eq!(
680 Feature::SignSeparate.category(),
681 FeatureCategory::Experimental
682 );
683 assert_eq!(Feature::AuditSystem.category(), FeatureCategory::Enterprise);
684 assert_eq!(Feature::LruCache.category(), FeatureCategory::Performance);
685 assert_eq!(Feature::VerboseLogging.category(), FeatureCategory::Debug);
686 assert_eq!(
687 Feature::MutationTesting.category(),
688 FeatureCategory::Testing
689 );
690 }
691
692 #[test]
693 fn test_default_enabled() {
694 assert!(Feature::SignSeparate.default_enabled());
695 assert!(Feature::Comp1.default_enabled());
696 assert!(Feature::Comp2.default_enabled());
697 assert!(Feature::LruCache.default_enabled());
698 assert!(!Feature::VerboseLogging.default_enabled());
699 }
700
701 #[test]
702 fn lifecycle_reconciles_comp1_comp2_with_support_matrix() {
703 for feat in [Feature::Comp1, Feature::Comp2, Feature::SignSeparate] {
708 assert_eq!(
709 feat.lifecycle(),
710 FeatureLifecycle::Stable,
711 "{feat} should report a Stable lifecycle"
712 );
713 assert!(feat.default_enabled(), "{feat} should be default-enabled");
714 }
715
716 assert_eq!(Feature::Comp1.category(), FeatureCategory::Experimental);
718 assert_eq!(Feature::Comp2.category(), FeatureCategory::Experimental);
719
720 assert_eq!(
722 Feature::RenamesR4R6.lifecycle(),
723 FeatureLifecycle::Experimental
724 );
725 assert_eq!(
726 Feature::AuditSystem.lifecycle(),
727 FeatureLifecycle::Experimental
728 );
729 }
730
731 #[test]
732 fn test_feature_flags_default() {
733 let flags = FeatureFlags::default();
734 assert!(flags.is_enabled(Feature::LruCache));
735 assert!(flags.is_enabled(Feature::SignSeparate));
736 assert!(flags.is_enabled(Feature::Comp1));
737 assert!(flags.is_enabled(Feature::Comp2));
738 }
739
740 #[test]
741 fn test_feature_flags_enable_disable() {
742 let mut flags = FeatureFlags::default();
743 flags.enable(Feature::SignSeparate);
744 assert!(flags.is_enabled(Feature::SignSeparate));
745 flags.disable(Feature::SignSeparate);
746 assert!(!flags.is_enabled(Feature::SignSeparate));
747 }
748
749 #[test]
750 fn test_feature_flags_toggle() {
751 let mut flags = FeatureFlags::default();
752 flags.toggle(Feature::LruCache);
753 assert!(!flags.is_enabled(Feature::LruCache));
754 flags.toggle(Feature::LruCache);
755 assert!(flags.is_enabled(Feature::LruCache));
756 }
757
758 #[test]
759 fn test_feature_flags_builder() {
760 let flags = FeatureFlags::builder()
761 .enable(Feature::SignSeparate)
762 .disable(Feature::LruCache)
763 .build();
764 assert!(flags.is_enabled(Feature::SignSeparate));
765 assert!(!flags.is_enabled(Feature::LruCache));
766 }
767
768 #[test]
769 fn test_feature_flags_enable_category() {
770 let flags = FeatureFlags::builder()
771 .enable_category(FeatureCategory::Experimental)
772 .build();
773 assert!(flags.is_enabled(Feature::SignSeparate));
774 assert!(flags.is_enabled(Feature::RenamesR4R6));
775 assert!(flags.is_enabled(Feature::Comp1));
776 assert!(flags.is_enabled(Feature::Comp2));
777 }
778
779 #[test]
780 fn test_feature_flags_handle() {
781 let handle = FeatureFlagsHandle::new();
782 handle.enable(Feature::SignSeparate);
783 assert!(handle.is_enabled(Feature::SignSeparate));
784 handle.disable(Feature::SignSeparate);
785 assert!(!handle.is_enabled(Feature::SignSeparate));
786 handle.enable(Feature::SignSeparate);
787 assert!(handle.is_enabled(Feature::SignSeparate));
788 }
789
790 #[test]
791 fn test_all_features() {
792 let features = all_features();
793 assert!(features.contains(&Feature::SignSeparate));
794 assert!(features.contains(&Feature::LruCache));
795 assert!(features.contains(&Feature::VerboseLogging));
796 }
797
798 #[test]
799 fn test_enabled_in_category() {
800 let mut flags = FeatureFlags::default();
801 flags.enable(Feature::RenamesR4R6);
802 let experimental = flags.enabled_in_category(FeatureCategory::Experimental);
803 assert_eq!(experimental.len(), 4);
804 assert!(experimental.contains(&Feature::SignSeparate));
805 assert!(experimental.contains(&Feature::Comp1));
806 assert!(experimental.contains(&Feature::Comp2));
807 assert!(experimental.contains(&Feature::RenamesR4R6));
808 }
809
810 #[test]
811 fn test_env_var_name() {
812 assert_eq!(
813 Feature::SignSeparate.env_var_name(),
814 "COPYBOOK_FF_SIGN_SEPARATE"
815 );
816 assert_eq!(Feature::LruCache.env_var_name(), "COPYBOOK_FF_LRU_CACHE");
817 }
818
819 #[test]
820 fn test_feature_serde_json_roundtrip() {
821 let feature = Feature::ParallelDecode;
822 let json = serde_json::to_string(&feature).unwrap();
823 let back: Feature = serde_json::from_str(&json).unwrap();
824 assert_eq!(back, feature);
825 }
826
827 #[test]
828 fn test_feature_category_serde_roundtrip() {
829 let cat = FeatureCategory::Enterprise;
830 let json = serde_json::to_string(&cat).unwrap();
831 let back: FeatureCategory = serde_json::from_str(&json).unwrap();
832 assert_eq!(back, cat);
833 }
834
835 #[test]
836 fn test_feature_lifecycle_display() {
837 assert_eq!(FeatureLifecycle::Experimental.to_string(), "experimental");
838 assert_eq!(FeatureLifecycle::Stable.to_string(), "stable");
839 assert_eq!(FeatureLifecycle::Deprecated.to_string(), "deprecated");
840 }
841
842 #[test]
843 fn test_feature_category_display() {
844 assert_eq!(FeatureCategory::Experimental.to_string(), "experimental");
845 assert_eq!(FeatureCategory::Enterprise.to_string(), "enterprise");
846 assert_eq!(FeatureCategory::Performance.to_string(), "performance");
847 assert_eq!(FeatureCategory::Debug.to_string(), "debug");
848 assert_eq!(FeatureCategory::Testing.to_string(), "testing");
849 }
850
851 #[test]
852 fn test_feature_flags_all_disabled() {
853 let mut flags = FeatureFlags::default();
854 for feature in all_features() {
855 flags.disable(feature);
856 }
857 for feature in all_features() {
858 assert!(!flags.is_enabled(feature), "{feature} should be disabled");
859 }
860 }
861
862 #[test]
863 fn test_feature_flags_all_enabled() {
864 let mut flags = FeatureFlags::default();
865 for feature in all_features() {
866 flags.enable(feature);
867 }
868 for feature in all_features() {
869 assert!(flags.is_enabled(feature), "{feature} should be enabled");
870 }
871 }
872
873 #[test]
874 fn test_builder_disable_category() {
875 let flags = FeatureFlags::builder()
876 .enable_category(FeatureCategory::Debug)
877 .disable_category(FeatureCategory::Debug)
878 .build();
879 let debug_features = flags.enabled_in_category(FeatureCategory::Debug);
880 assert!(debug_features.is_empty());
881 }
882
883 #[test]
884 fn test_handle_toggle_and_snapshot() {
885 let handle = FeatureFlagsHandle::new();
886 let initially_enabled = handle.is_enabled(Feature::LruCache);
887 handle.toggle(Feature::LruCache);
888 assert_ne!(handle.is_enabled(Feature::LruCache), initially_enabled);
889 let snap = handle.snapshot();
890 assert_ne!(snap.is_enabled(Feature::LruCache), initially_enabled);
891 }
892
893 #[test]
894 fn test_handle_clone_is_independent() {
895 let handle = FeatureFlagsHandle::new();
896 handle.enable(Feature::Profiling);
897 let cloned = handle.clone();
898 handle.disable(Feature::Profiling);
899 assert!(cloned.is_enabled(Feature::Profiling));
900 assert!(!handle.is_enabled(Feature::Profiling));
901 }
902
903 #[test]
904 fn test_features_in_category_counts() {
905 let experimental = FeatureFlags::features_in_category(FeatureCategory::Experimental);
906 assert_eq!(experimental.len(), 4);
907 let enterprise = FeatureFlags::features_in_category(FeatureCategory::Enterprise);
908 assert_eq!(enterprise.len(), 6);
909 let performance = FeatureFlags::features_in_category(FeatureCategory::Performance);
910 assert_eq!(performance.len(), 4);
911 let debug = FeatureFlags::features_in_category(FeatureCategory::Debug);
912 assert_eq!(debug.len(), 4);
913 let testing = FeatureFlags::features_in_category(FeatureCategory::Testing);
914 assert_eq!(testing.len(), 4);
915 }
916
917 #[test]
918 fn test_enabled_features_iterator_count() {
919 let flags = FeatureFlags::default();
920 let count = flags.enabled_features().count();
921 assert_eq!(count, 4);
923 }
924
925 #[test]
926 fn test_description_nonempty_for_all_features() {
927 for feature in all_features() {
928 assert!(
929 !feature.description().is_empty(),
930 "{feature} has empty description"
931 );
932 }
933 }
934
935 #[test]
936 fn test_from_str_unknown_returns_err() {
937 assert!(Feature::from_str("does_not_exist").is_err());
938 assert!(Feature::from_str("").is_err());
939 }
940
941 #[test]
942 fn test_feature_flags_serde_json_roundtrip() {
943 let flags = FeatureFlags::builder()
944 .enable(Feature::Profiling)
945 .disable(Feature::LruCache)
946 .build();
947 let json = serde_json::to_string(&flags).unwrap();
948 let back: FeatureFlags = serde_json::from_str(&json).unwrap();
949 assert!(back.is_enabled(Feature::Profiling));
950 assert!(!back.is_enabled(Feature::LruCache));
951 }
952}