Skip to main content

sim_config/
lib.rs

1//! Table and Dir substrate for layered SIM configuration.
2//!
3//! Configuration is plain kernel data: a [`ConfigTable`] is one library's
4//! `Expr::Map`, and a [`ConfigDir`] maps library ids to those tables. This crate
5//! provides the data model, merge/provenance rules, shape-backed defaults,
6//! typed read views, and safe path helpers used by loaders and codecs without
7//! introducing a parallel configuration value enum.
8//!
9//! # Example
10//!
11//! ```
12//! use sim_config::{ConfigDir, ConfigLayer, ConfigSource, merge_layers};
13//! use sim_kernel::{Expr, Symbol};
14//! use sim_value::build::{entry, map, text};
15//!
16//! let lib = Symbol::qualified("sim", "cookbook");
17//! let lower = ConfigDir::one(lib.clone(), map(vec![("mode", text("built-in"))])).unwrap();
18//! let upper = ConfigDir::one(lib.clone(), map(vec![("mode", text("work"))])).unwrap();
19//! let effective = merge_layers(&[
20//!     ConfigLayer::new(ConfigSource::BuiltIn { lib: lib.clone() }, lower),
21//!     ConfigLayer::new(ConfigSource::Explicit { label: "work".into() }, upper),
22//! ]);
23//!
24//! let table = effective.dir.table(&lib).unwrap();
25//! assert_eq!(sim_value::access::field(&table.table, "mode"), Some(&Expr::String("work".into())));
26//! assert_eq!(effective.trace[0].key, "mode");
27//! let _ = entry("ok", Expr::Bool(true));
28//! ```
29
30#![forbid(unsafe_code)]
31#![deny(missing_docs)]
32
33use std::fmt;
34
35use sim_kernel::{Expr, Symbol, id::SymbolError};
36
37pub mod merge;
38pub mod path;
39pub mod probe;
40pub mod report;
41pub mod shape;
42pub mod source;
43pub mod table;
44pub mod view;
45
46pub use merge::{ConfigLayer, EffectiveConfig, MergeTrace, merge_layers};
47pub use path::{ConfigRoots, lib_config_path};
48pub use probe::{
49    ConfigProbe, ConfigProbeCaps, ConfigProbeReport, ConfigProbeRequest, ConfigProbeStatus,
50};
51pub use report::{ConfigReport, ConfigReportEntry};
52pub use shape::{ConfigSecretField, ConfigShape, ConfigShapeResult, LibConfigDefaults};
53pub use source::{ConfigSource, ProbeMode};
54pub use table::{ConfigDir, ConfigTable, lib_symbol_from_str};
55pub use view::ConfigView;
56
57/// Result type used by `sim-config` helpers.
58pub type ConfigResult<T> = Result<T, ConfigError>;
59
60/// Error reported by config table, view, and path helpers.
61#[derive(Clone, Debug, PartialEq, Eq)]
62pub enum ConfigError {
63    /// A Dir expression was not an `Expr::Map`.
64    NonDirExpr,
65    /// A table expression was not an `Expr::Map`.
66    NonTableExpr,
67    /// A Dir entry for `lib` did not contain a table value.
68    NonTableConfig {
69        /// The library whose Dir entry was malformed.
70        lib: Symbol,
71    },
72    /// A Dir key could not be interpreted as a library id.
73    UnsupportedDirKey {
74        /// The unsupported key expression.
75        key: Expr,
76    },
77    /// A library id or id segment is not safe for config use.
78    InvalidLibId {
79        /// The rejected id.
80        id: String,
81    },
82    /// A library config path segment was rejected.
83    InvalidPathSegment {
84        /// The rejected segment.
85        segment: String,
86    },
87    /// A required field was absent from a config table.
88    MissingField {
89        /// The missing field name.
90        key: String,
91    },
92    /// A config field had a different shape than the caller requested.
93    TypeMismatch {
94        /// The field name.
95        key: String,
96        /// The expected shape.
97        expected: &'static str,
98    },
99}
100
101impl From<SymbolError> for ConfigError {
102    fn from(error: SymbolError) -> Self {
103        Self::InvalidLibId {
104            id: error.to_string(),
105        }
106    }
107}
108
109impl fmt::Display for ConfigError {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        match self {
112            Self::NonDirExpr => f.write_str("config dir must be an Expr::Map"),
113            Self::NonTableExpr => f.write_str("config table must be an Expr::Map"),
114            Self::NonTableConfig { lib } => {
115                write!(f, "config for `{}` must be a table", lib.as_qualified_str())
116            }
117            Self::UnsupportedDirKey { key } => {
118                write!(f, "config dir key {key:?} is not a supported library id")
119            }
120            Self::InvalidLibId { id } => write!(f, "invalid config library id `{id}`"),
121            Self::InvalidPathSegment { segment } => {
122                write!(f, "invalid config path segment `{segment}`")
123            }
124            Self::MissingField { key } => write!(f, "config field `{key}` is missing"),
125            Self::TypeMismatch { key, expected } => {
126                write!(f, "config field `{key}` is not {expected}")
127            }
128        }
129    }
130}
131
132impl std::error::Error for ConfigError {}