Typed configuration storage¶
rtb_app::typed_config is the machinery that lets App hold a tool's typed
configuration without App itself becoming generic over that type. Most tool
authors never name these types directly — they call
App::typed_config — but the module is public because rtb-cli and
rtb-test-support both wire it.
ErasedConfig¶
A type alias, not a new type. It is Arc<dyn Any + …> specifically so that
Arc::downcast recovers Arc<Config<C>> sharing the same backing allocation —
no copy, no second refcount, and Arc::ptr_eq holds between the value inside App
and the one handed back by typed_config.
erase¶
pub fn erase<C>(config: Config<C>) -> ErasedConfig
where C: serde::de::DeserializeOwned + Send + Sync + 'static;
Wraps a Config<C> for storage. App::new calls it, and so does
TestAppBuilder::config.
TypedConfigOps¶
pub struct TypedConfigOps {
pub schema: serde_json::Value,
// plus a private render closure
}
impl TypedConfigOps {
pub fn new<C>() -> Self
where C: Serialize + DeserializeOwned + schemars::JsonSchema + Send + Sync + 'static;
pub fn render(&self, erased: &ErasedConfig) -> Option<serde_json::Value>;
}
The type-erased view onto a wired config: the JSON Schema for C, plus a closure
that renders the merged value as JSON. C is captured in the closure at construction
time and is never named again, which is what lets rtb-cli drive
config show / get / schema / validate without knowing the tool's config type.
TypedConfigOps implements Debug by hand — the schema is printed and the closure
renders as <closure>.
The bounds differ, and that is why config_schema() can be None¶
| Item | Bounds on C |
|---|---|
erase / App::new |
DeserializeOwned + Send + Sync + 'static |
TypedConfigOps::new |
Serialize + DeserializeOwned + JsonSchema + Send + Sync + 'static |
Storage needs only DeserializeOwned; producing a schema and rendering a value need
JsonSchema and Serialize on top. So a config type can be stored on an App
without being renderable, and that is the mechanical reason
App::config_schema() and App::config_value() return Option rather than the
value: App::new alone never builds the ops.
render returns None on a mismatch, silently¶
None means the erased value is not a Config<C> of the type captured when the ops
were built. There is no error describing the mismatch. The public API prevents this
pairing going wrong — with_typed_config takes both halves together — but if you
call it yourself, a mismatched pair produces a permanently empty config show and no
diagnostic anywhere.
Schema generation cannot fail loudly¶
TypedConfigOps::new generates the schema with
schemars::SchemaGenerator::default() and serialises it with
serde_json::to_value(...).unwrap_or(serde_json::Value::Null). A serialisation
failure therefore leaves schema as JSON null rather than returning an error. In
practice a derived JsonSchema always serialises, but a hand-written implementation
that does not will surface as a null schema rather than a message.