Skip to main content

rskit_config/strict/
raw.rs

1use std::collections::BTreeMap;
2
3use rskit_errors::{AppError, AppResult};
4use serde::de::DeserializeOwned;
5
6/// A raw value retained verbatim for later, downstream parsing.
7///
8/// This is the canonical [`serde_json::Value`] tree shared across rskit (see `rskit-codec`),
9/// regardless of the on-disk format the document was parsed from.
10pub type RawValue = serde_json::Value;
11
12/// A map of dynamic-keyed raw subtrees (for example `[ecosystems.<id>]`).
13///
14/// Each subtree is kept as an un-deserialized [`RawValue`] so a downstream owner — a provider, plugin,
15/// or adapter — can parse it later with its own `#[serde(deny_unknown_fields)]` schema.
16/// The strict top-level document rejects unknown *reserved* keys while a [`RawTable`] field captures the open,
17/// dynamic-keyed sections verbatim.
18pub type RawTable = BTreeMap<String, RawValue>;
19
20/// Deserialize a retained raw subtree into a concrete type.
21///
22/// Honors `#[serde(deny_unknown_fields)]` on `T`. `field` names the subtree for error reporting
23/// and is included in the returned [`AppError`] on failure.
24pub fn deserialize_subtree<T>(field: &str, value: RawValue) -> AppResult<T>
25where
26    T: DeserializeOwned,
27{
28    T::deserialize(value).map_err(|error| {
29        AppError::invalid_input(field, format!("invalid '{field}' configuration: {error}"))
30    })
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36    use serde::Deserialize;
37
38    #[derive(Debug, Deserialize, PartialEq, Eq)]
39    #[serde(deny_unknown_fields)]
40    struct Sub {
41        enabled: bool,
42        retries: u16,
43    }
44
45    #[test]
46    fn deserialize_subtree_parses_typed_value() {
47        let value: RawValue = serde_json::json!({ "enabled": true, "retries": 3 });
48
49        let sub: Sub = deserialize_subtree("driver", value).unwrap();
50
51        assert_eq!(
52            sub,
53            Sub {
54                enabled: true,
55                retries: 3
56            }
57        );
58    }
59
60    #[test]
61    fn deserialize_subtree_rejects_unknown_field_with_named_error() {
62        let value: RawValue = serde_json::json!({ "enabled": true, "retries": 3, "extra": 1 });
63
64        let err = deserialize_subtree::<Sub>("driver", value).unwrap_err();
65
66        assert!(err.to_string().contains("driver"));
67        assert!(err.to_string().contains("unknown field"));
68    }
69}