configbuilder_pattern_loader/
feature_registry.rs1use crate::{
2 ConfigError, FeatureRecord, FeatureRecordBuilder, FeatureState, FeatureSummary, LoadedFeature,
3 OnError, OptionalSection, OverrideSource,
4};
5
6type FeatureObserver = Box<dyn Fn(&FeatureRecord)>;
7
8pub struct FeatureRegistry {
10 records: Vec<FeatureRecord>,
11 observers: Vec<FeatureObserver>,
12}
13
14fn resolve_feature_on_error<T: OptionalSection>(key: &str) -> OnError {
19 let var_name = format!(
20 "CONFIGBUILDER_FEATURE_{}_ON_ERROR",
21 key.to_uppercase().replace('.', "_")
22 );
23 match std::env::var(&var_name).as_deref() {
24 Ok("disable") => OnError::Disable,
25 Ok("fail") => OnError::Fail,
26 _ => T::on_error(),
27 }
28}
29
30impl FeatureRegistry {
31 pub(crate) fn new() -> Self {
33 Self {
34 records: Vec::new(),
35 observers: Vec::new(),
36 }
37 }
38
39 pub(crate) fn on_load(&mut self, observer: impl Fn(&FeatureRecord) + 'static) {
41 self.observers.push(Box::new(observer));
42 }
43
44 pub(crate) fn load<T>(
46 &mut self,
47 loader: &crate::DefaultLoader,
48 ) -> Result<FeatureState<T>, ConfigError>
49 where
50 T: OptionalSection,
51 {
52 let loaded: LoadedFeature<T> = loader.load_feature(T::section_name())?;
53 let LoadedFeature { state, record } = loaded;
54 let record = *record;
55
56 let validation_result = if let FeatureState::Enabled(ref value) = state {
57 Some(value.validate_enabled())
58 } else {
59 None
60 };
61
62 let (final_state, final_override) = match validation_result {
63 Some(Ok(())) | None => (state, record.override_source),
64 Some(Err(e)) => match resolve_feature_on_error::<T>(T::section_name()) {
65 OnError::Fail => return Err(e),
66 OnError::Disable => (
67 FeatureState::Disabled,
68 Some(OverrideSource::ValidationError {
69 reason: e.to_string(),
70 }),
71 ),
72 },
73 };
74
75 let mut built = FeatureRecordBuilder::new(record.section_name)
76 .enabled(final_state.is_enabled())
77 .requires(T::requires())
78 .metadata(T::metadata());
79 if let Some(override_source) = final_override {
80 built = built.override_source(override_source);
81 }
82 self.records.push(built.build());
83
84 if let Some(record) = self.records.last() {
85 for observer in &self.observers {
86 observer(record);
87 }
88 }
89
90 Ok(final_state)
91 }
92
93 pub(crate) fn validate_dependencies(&self) -> Result<(), ConfigError> {
95 let enabled: std::collections::HashSet<&str> = self
96 .records
97 .iter()
98 .filter(|r| r.enabled)
99 .map(|r| r.section_name.as_str())
100 .collect();
101
102 let violations: Vec<String> = self
103 .records
104 .iter()
105 .filter(|r| r.enabled)
106 .flat_map(|r| {
107 r.requires.iter().filter_map(|dep| {
108 if enabled.contains(dep) {
109 None
110 } else {
111 Some(format!(
112 "'{}' requires '{}' but '{}' is not enabled",
113 r.section_name, dep, dep
114 ))
115 }
116 })
117 })
118 .collect();
119
120 if violations.is_empty() {
121 Ok(())
122 } else {
123 Err(ConfigError::Validation {
124 section: String::from("feature_dependencies"),
125 reason: violations.join("; "),
126 })
127 }
128 }
129
130 pub(crate) fn records(&self) -> &[FeatureRecord] {
132 &self.records
133 }
134
135 pub(crate) fn summary(&self) -> FeatureSummary {
137 FeatureSummary {
138 records: self.records.clone(),
139 }
140 }
141}
142
143impl crate::FeatureRegistryOps for FeatureRegistry {
144 fn new() -> Self {
145 FeatureRegistry::new()
146 }
147
148 fn on_load(&mut self, observer: impl Fn(&FeatureRecord) + 'static) {
149 FeatureRegistry::on_load(self, observer)
150 }
151
152 fn load<T>(&mut self, loader: &crate::DefaultLoader) -> Result<FeatureState<T>, ConfigError>
153 where
154 T: OptionalSection,
155 {
156 FeatureRegistry::load(self, loader)
157 }
158
159 fn validate_dependencies(&self) -> Result<(), ConfigError> {
160 FeatureRegistry::validate_dependencies(self)
161 }
162
163 fn records(&self) -> &[FeatureRecord] {
164 FeatureRegistry::records(self)
165 }
166
167 fn summary(&self) -> FeatureSummary {
168 FeatureRegistry::summary(self)
169 }
170}
171
172impl Default for FeatureRegistry {
173 fn default() -> Self {
174 Self::new()
175 }
176}