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                format!("Failed to deserialize configuration: {}", err.into_inner()).into()
32            } else {
33                format!("Failed to deserialize configuration at {}", err).into()
34            }
35        })
36    }
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42    use serde::{Deserialize, Serialize};
43
44    #[derive(Debug, Serialize, Deserialize)]
45    struct TestConfig<S> {
46        name: String,
47        settings: S,
48    }
49
50    #[derive(Debug, Serialize, Deserialize)]
51    struct Settings {
52        enabled: bool,
53        count: u32,
54    }
55
56    #[derive(Debug, Serialize, Deserialize)]
57    struct InvalidSettings {
58        enabled: String,
59        count: u32,
60    }
61
62    #[test]
63    fn test_deserialize() {
64        // Test successful deserialization
65        let config_bytes = crate::cbor::to_vec(TestConfig {
66            name: "test".to_string(),
67            settings: Settings {
68                enabled: true,
69                count: 42,
70            },
71        })
72        .unwrap();
73        let configuration = Configuration::new(config_bytes);
74        let result: TestConfig<Settings> = configuration.deserialize().unwrap();
75        assert_eq!(result.name, "test");
76        assert!(result.settings.enabled);
77        assert_eq!(result.settings.count, 42);
78
79        // Test error with path reporting
80        let invalid_bytes = crate::cbor::to_vec(&TestConfig {
81            name: "test".to_string(),
82            settings: InvalidSettings {
83                enabled: "not_a_bool".to_string(),
84                count: 42,
85            },
86        })
87        .unwrap();
88        let invalid_configuration = Configuration::new(invalid_bytes);
89        let err = invalid_configuration.deserialize::<TestConfig<Settings>>().unwrap_err();
90
91        insta::assert_snapshot!(err, @"Failed to deserialize configuration at settings.enabled: unexpected type string at position 29: expected bool");
92
93        // Test error with absent config
94        let invalid_bytes = crate::cbor::to_vec(serde_json::Value::Null).unwrap();
95        let invalid_configuration = Configuration::new(invalid_bytes);
96        let err = invalid_configuration.deserialize::<TestConfig<Settings>>().unwrap_err();
97
98        insta::assert_snapshot!(err, @"Failed to deserialize configuration: unexpected type null at position 0: expected map");
99    }
100}