configbuilder_pattern_loader/optional_section.rs
1use crate::{ConfigError, DefaultLoader, FeatureMetadata, FeatureState, OnError};
2
3/// Marks a typed struct as an opt-in TOML feature section.
4///
5/// Unlike [`ConfigSection`], which returns `T::default()` for absent keys,
6/// `OptionalSection` treats absence as "feature disabled" and returns
7/// [`FeatureState::Disabled`] — no defaults, no error.
8///
9/// [`ConfigSection`]: crate::ConfigSection
10pub trait OptionalSection: serde::de::DeserializeOwned + Send + Sync + 'static {
11 /// The top-level TOML key for this section (e.g. `"message_broker"`).
12 fn section_name() -> &'static str;
13
14 /// Other section keys this feature requires to be enabled before it can run.
15 ///
16 /// Checked by [`crate::FeatureRegistryOps::validate_dependencies`] after all
17 /// features are loaded. Default: no dependencies (`&[]`).
18 fn requires() -> &'static [&'static str] {
19 &[]
20 }
21
22 /// What the feature registry should do when [`validate_enabled`] returns an error.
23 ///
24 /// - [`OnError::Fail`] *(default)* — propagate the error; startup halts.
25 /// - [`OnError::Disable`] — treat the feature as disabled and continue startup.
26 ///
27 /// [`validate_enabled`]: OptionalSection::validate_enabled
28 fn on_error() -> OnError {
29 OnError::Fail
30 }
31
32 /// Static documentation and ownership annotations for this feature.
33 fn metadata() -> FeatureMetadata {
34 FeatureMetadata::default()
35 }
36
37 /// Validate cross-field constraints after the section has been deserialised.
38 ///
39 /// Only called when the section key is present in TOML. Default: always `Ok(())`.
40 fn validate_enabled(&self) -> Result<(), ConfigError> {
41 Ok(())
42 }
43
44 /// Load this section as an optional feature from `loader`.
45 ///
46 /// Returns `FeatureState::Enabled(Self)` when the key is present and valid,
47 /// `FeatureState::Disabled` when absent, and `Err` for I/O, parse, or
48 /// validation failures.
49 ///
50 /// **Note:** [`on_error`] and [`requires`] are only applied when loading
51 /// through [`crate::FeatureRegistryOps::load`]. Use that for full startup
52 /// coordination with graceful degradation and dependency validation.
53 ///
54 /// [`on_error`]: OptionalSection::on_error
55 /// [`requires`]: OptionalSection::requires
56 fn load_optional(loader: &DefaultLoader) -> Result<FeatureState<Self>, ConfigError>
57 where
58 Self: Sized,
59 {
60 let state: FeatureState<Self> = loader.load_optional_section(Self::section_name())?;
61 if let FeatureState::Enabled(ref value) = state {
62 value.validate_enabled()?;
63 }
64 Ok(state)
65 }
66}