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 {}