grafbase_sdk/types/
configuration.rs

1use serde::Deserialize;
2
3use crate::SdkError;
4
5/// Configuration data for the extension, from the gateway toml config.
6pub struct Configuration(Vec<u8>);
7
8impl Configuration {
9    /// Creates a new `Configuration` from a CBOR byte vector.
10    pub(crate) fn new(config: Vec<u8>) -> Self {
11        Self(config)
12    }
13
14    /// Deserializes the configuration bytes into the requested type.
15    ///
16    /// # Errors
17    ///
18    /// Returns an error if deserialization fails.
19    pub fn deserialize<'de, T>(&'de self) -> Result<T, SdkError>
20    where
21        T: Deserialize<'de>,
22    {
23        let mut deserializer = minicbor_serde::Deserializer::new(&self.0);
24        serde_path_to_error::deserialize(&mut deserializer).map_err(|err| {
25            if err.path().iter().len() == 0
26                || err
27                    .path()
28                    .iter()
29                    .all(|seg| matches!(seg, serde_path_to_error::Segment::Unknown))
30            {
31                let inner_err = err.into_inner();
32                format!("Failed to deserialize configuration: {inner_err}").into()
33            } else {
34                format!("Failed to deserialize configuration at {err}").into()
35            }
36        })
37    }
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43    use serde::{Deserialize, Serialize};
44
45    #[derive(Debug, Serialize, Deserialize)]
46    struct TestConfig<S> {
47        name: String,
48        settings: S,
49    }
50
51    #[derive(Debug, Serialize, Deserialize)]
52    struct Settings {
53        enabled: bool,
54        count: u32,
55    }
56
57    #[derive(Debug, Serialize, Deserialize)]
58    struct InvalidSettings {
59        enabled: String,
60        count: u32,
61    }
62
63    #[test]
64    fn test_deserialize() {
65        // Test successful deserialization
66        let config_bytes = crate::cbor::to_vec(TestConfig {
67            name: "test".to_string(),
68            settings: Settings {
69                enabled: true,
70                count: 42,
71            },
72        })
73        .unwrap();
74        let configuration = Configuration::new(config_bytes);
75        let result: TestConfig<Settings> = configuration.deserialize().unwrap();
76        assert_eq!(result.name, "test");
77        assert!(result.settings.enabled);
78        assert_eq!(result.settings.count, 42);
79
80        // Test error with path reporting
81        let invalid_bytes = crate::cbor::to_vec(&TestConfig {
82            name: "test".to_string(),
83            settings: InvalidSettings {
84                enabled: "not_a_bool".to_string(),
85                count: 42,
86            },
87        })
88        .unwrap();
89        let invalid_configuration = Configuration::new(invalid_bytes);
90        let err = invalid_configuration.deserialize::<TestConfig<Settings>>().unwrap_err();
91
92        insta::assert_snapshot!(err, @"Failed to deserialize configuration at settings.enabled: unexpected type string at position 29: expected bool");
93
94        // Test error with absent config
95        let invalid_bytes = crate::cbor::to_vec(serde_json::Value::Null).unwrap();
96        let invalid_configuration = Configuration::new(invalid_bytes);
97        let err = invalid_configuration.deserialize::<TestConfig<Settings>>().unwrap_err();
98
99        insta::assert_snapshot!(err, @"Failed to deserialize configuration: unexpected type null at position 0: expected map");
100    }
101}