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 report;
40pub mod shape;
41pub mod source;
42pub mod table;
43pub mod view;
44
45pub use merge::{ConfigLayer, EffectiveConfig, MergeTrace, merge_layers};
46pub use path::{ConfigRoots, lib_config_path};
47pub use report::{ConfigReport, ConfigReportEntry};
48pub use shape::{ConfigSecretField, ConfigShape, ConfigShapeResult, LibConfigDefaults};
49pub use source::{ConfigSource, ProbeMode};
50pub use table::{ConfigDir, ConfigTable, lib_symbol_from_str};
51pub use view::ConfigView;
52
53/// Result type used by `sim-config` helpers.
54pub type ConfigResult<T> = Result<T, ConfigError>;
55
56/// Error reported by config table, view, and path helpers.
57#[derive(Clone, Debug, PartialEq, Eq)]
58pub enum ConfigError {
59    /// A Dir expression was not an `Expr::Map`.
60    NonDirExpr,
61    /// A table expression was not an `Expr::Map`.
62    NonTableExpr,
63    /// A Dir entry for `lib` did not contain a table value.
64    NonTableConfig {
65        /// The library whose Dir entry was malformed.
66        lib: Symbol,
67    },
68    /// A Dir key could not be interpreted as a library id.
69    UnsupportedDirKey {
70        /// The unsupported key expression.
71        key: Expr,
72    },
73    /// A library id or id segment is not safe for config use.
74    InvalidLibId {
75        /// The rejected id.
76        id: String,
77    },
78    /// A library config path segment was rejected.
79    InvalidPathSegment {
80        /// The rejected segment.
81        segment: String,
82    },
83    /// A required field was absent from a config table.
84    MissingField {
85        /// The missing field name.
86        key: String,
87    },
88    /// A config field had a different shape than the caller requested.
89    TypeMismatch {
90        /// The field name.
91        key: String,
92        /// The expected shape.
93        expected: &'static str,
94    },
95}
96
97impl From<SymbolError> for ConfigError {
98    fn from(error: SymbolError) -> Self {
99        Self::InvalidLibId {
100            id: error.to_string(),
101        }
102    }
103}
104
105impl fmt::Display for ConfigError {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        match self {
108            Self::NonDirExpr => f.write_str("config dir must be an Expr::Map"),
109            Self::NonTableExpr => f.write_str("config table must be an Expr::Map"),
110            Self::NonTableConfig { lib } => {
111                write!(f, "config for `{}` must be a table", lib.as_qualified_str())
112            }
113            Self::UnsupportedDirKey { key } => {
114                write!(f, "config dir key {key:?} is not a supported library id")
115            }
116            Self::InvalidLibId { id } => write!(f, "invalid config library id `{id}`"),
117            Self::InvalidPathSegment { segment } => {
118                write!(f, "invalid config path segment `{segment}`")
119            }
120            Self::MissingField { key } => write!(f, "config field `{key}` is missing"),
121            Self::TypeMismatch { key, expected } => {
122                write!(f, "config field `{key}` is not {expected}")
123            }
124        }
125    }
126}
127
128impl std::error::Error for ConfigError {}