Skip to main content

khive_runtime/
params.rs

1//! Shared deserialization of a verb's JSON parameters.
2//!
3//! Every pack that decodes its parameters into a typed struct reports a malformed call the same
4//! way, so the text a caller sees for a bad argument does not depend on which pack handled it.
5
6use serde::de::DeserializeOwned;
7use serde_json::Value;
8
9use crate::RuntimeError;
10
11/// Deserialize a verb's JSON `params` into `T`.
12///
13/// A failure becomes [`RuntimeError::InvalidInput`] whose text is `bad params: ` followed by the
14/// message serde reports, for example a missing required field or a field rejected by
15/// `deny_unknown_fields`.
16pub fn deser_params<T: DeserializeOwned>(params: Value) -> Result<T, RuntimeError> {
17    serde_json::from_value(params)
18        .map_err(|e| RuntimeError::InvalidInput(format!("bad params: {e}")))
19}
20
21#[cfg(test)]
22mod tests {
23    use serde::Deserialize;
24    use serde_json::json;
25
26    use crate::{deser_params, RuntimeError};
27
28    #[derive(Debug, Deserialize, PartialEq)]
29    #[serde(deny_unknown_fields)]
30    struct Sample {
31        name: String,
32        #[serde(default)]
33        count: u32,
34    }
35
36    /// The message serde itself reports for `params`, which `deser_params` must carry through.
37    fn serde_message(params: &serde_json::Value) -> String {
38        serde_json::from_value::<Sample>(params.clone())
39            .expect_err("these params must not deserialize")
40            .to_string()
41    }
42
43    #[test]
44    fn missing_required_field_gives_bad_params_invalid_input() {
45        let params = json!({"count": 3});
46        let want = serde_message(&params);
47        assert!(want.contains("missing field `name`"), "{want}");
48
49        let err = deser_params::<Sample>(params).unwrap_err();
50        let RuntimeError::InvalidInput(message) = err else {
51            panic!("expected InvalidInput, got {err:?}");
52        };
53        assert!(message.starts_with("bad params: "), "{message}");
54        assert_eq!(message, format!("bad params: {want}"));
55    }
56
57    #[test]
58    fn unknown_field_under_deny_unknown_fields_gives_bad_params_invalid_input() {
59        let params = json!({"name": "alpha", "extra": true});
60        let want = serde_message(&params);
61        assert!(want.contains("unknown field `extra`"), "{want}");
62
63        let err = deser_params::<Sample>(params).unwrap_err();
64        let RuntimeError::InvalidInput(message) = err else {
65            panic!("expected InvalidInput, got {err:?}");
66        };
67        assert!(message.starts_with("bad params: "), "{message}");
68        assert_eq!(message, format!("bad params: {want}"));
69    }
70
71    #[test]
72    fn valid_params_deserialize_into_the_requested_type() {
73        let params = json!({"name": "alpha", "count": 3});
74        let parsed: Sample = deser_params(params).unwrap();
75        let expected = Sample {
76            name: "alpha".to_string(),
77            count: 3,
78        };
79        assert_eq!(parsed, expected);
80
81        let params = json!({"name": "beta"});
82        let defaulted: Sample = deser_params(params).unwrap();
83        let expected_default = Sample {
84            name: "beta".to_string(),
85            count: 0,
86        };
87        assert_eq!(defaulted, expected_default);
88    }
89}