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