apollo_rust_client/namespace/
json.rs

1//! JSON namespace implementation for handling structured JSON configuration data.
2//!
3//! This module provides the `Json` struct which wraps a `serde_json::Value` and
4//! provides methods for working with JSON-formatted configuration data. It supports
5//! deserialization into custom types and maintains the original JSON structure.
6//!
7//! # Usage
8//!
9//! The `Json` struct is typically created automatically by the namespace detection
10//! system when a namespace name contains a `.json` extension, but can also be
11//! created directly from any `serde_json::Value`.
12//!
13//! # Examples
14//!
15//! ```ignore
16//! use serde_json::json;
17//! use apollo_client::namespace::json::Json;
18//!
19//! let json_data = json!({"name": "MyApp", "version": "1.0.0"});
20//! let json_namespace = Json::from(json_data);
21//! ```
22
23use log::trace;
24use serde::de::DeserializeOwned;
25
26#[derive(Debug, thiserror::Error)]
27pub enum Error {
28    #[error("Failed to get content from JSON value")]
29    ContentNotFound,
30
31    #[error("Failed to deserialize JSON value: {0}")]
32    DeserializeError(#[from] serde_json::Error),
33}
34
35/// A wrapper around `serde_json::Value` for JSON-formatted configuration data.
36///
37/// This struct provides a type-safe interface for working with JSON configuration
38/// data retrieved from Apollo. It maintains the original JSON structure while
39/// providing convenient methods for deserialization into custom types.
40///
41/// # Thread Safety
42///
43/// This struct is `Clone` and `Debug`, making it easy to work with in concurrent
44/// environments. The underlying `serde_json::Value` is also thread-safe.
45///
46/// # Examples
47///
48/// ```ignore
49/// use serde_json::json;
50/// use apollo_client::namespace::json::Json;
51///
52/// let json_data = json!({
53///     "database": {
54///         "host": "localhost",
55///         "port": 5432
56///     }
57/// });
58///
59/// let json_namespace = Json::from(json_data);
60/// ```
61use serde::Serialize;
62
63#[derive(Clone, Debug, Serialize)]
64pub struct Json {
65    /// The underlying JSON value containing the configuration data
66    value: serde_json::Value,
67}
68
69impl From<Json> for wasm_bindgen::JsValue {
70    fn from(val: Json) -> Self {
71        serde_wasm_bindgen::to_value(&val.value).unwrap()
72    }
73}
74
75impl Json {
76    /// Deserializes the JSON data into a custom type.
77    ///
78    /// This method attempts to deserialize the stored JSON value into any type
79    /// that implements `DeserializeOwned`. This is useful for converting the
80    /// raw JSON configuration into strongly-typed structs.
81    ///
82    /// # Type Parameters
83    ///
84    /// * `T` - The target type to deserialize into. Must implement `DeserializeOwned`.
85    ///
86    /// # Returns
87    ///
88    /// An instance of type `T` containing the deserialized data.
89    ///
90    /// # Panics
91    ///
92    /// This method currently panics (via `todo!()`) as the implementation is not
93    /// yet complete. In the future, it will return a `Result` type to handle
94    /// deserialization errors gracefully.
95    ///
96    /// # Examples
97    ///
98    /// ```ignore
99    /// use serde::{Deserialize, Serialize};
100    /// use serde_json::json;
101    /// use apollo_client::namespace::json::Json;
102    ///
103    /// #[derive(Deserialize, Serialize)]
104    /// struct DatabaseConfig {
105    ///     host: String,
106    ///     port: u16,
107    /// }
108    ///
109    /// let json_data = json!({
110    ///     "host": "localhost",
111    ///     "port": 5432
112    /// });
113    ///
114    /// let json_namespace = Json::from(json_data);
115    /// // let config: DatabaseConfig = json_namespace.to_object(); // Will work when implemented
116    /// ```
117    pub fn to_object<T: DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
118        serde_json::from_value(self.value.clone())
119    }
120}
121
122/// Converts a `serde_json::Value` into a `Json` instance.
123///
124/// This implementation allows for easy creation of `Json` instances from
125/// raw JSON data, typically used by the namespace detection system.
126///
127/// # Examples
128///
129/// ```ignore
130/// use serde_json::json;
131/// use apollo_client::namespace::json::Json;
132///
133/// let json_data = json!({"key": "value"});
134/// let json_namespace = Json::from(json_data);
135/// ```
136impl TryFrom<serde_json::Value> for Json {
137    type Error = crate::namespace::json::Error;
138
139    fn try_from(json_value: serde_json::Value) -> Result<Self, Self::Error> {
140        let content_string = match json_value.get("content") {
141            Some(serde_json::Value::String(s)) => s,
142            None => return Err(Error::ContentNotFound),
143            _ => return Err(Error::ContentNotFound),
144        };
145        trace!("content_string: {content_string:?}");
146        let value = serde_json::from_str(content_string.as_str())?;
147        trace!("value: {value:?}");
148        Ok(Self { value })
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use serde::Deserialize;
155
156    #[derive(Debug, Deserialize, PartialEq)]
157    struct TestStruct {
158        host: String,
159        port: u16,
160        run: bool,
161    }
162
163    #[cfg(not(target_arch = "wasm32"))]
164    #[tokio::test]
165    async fn test_json_to_object() {
166        crate::tests::setup();
167        let json_namespace = crate::namespace::json::Json::try_from(serde_json::json!({
168            "content": "{\"host\": \"localhost\", \"port\": 8080, \"run\": true}"
169        }))
170        .unwrap();
171        let result: TestStruct = json_namespace.to_object().unwrap();
172        assert_eq!(
173            result,
174            TestStruct {
175                host: "localhost".to_string(),
176                port: 8080,
177                run: true,
178            }
179        );
180    }
181
182    #[cfg(not(target_arch = "wasm32"))]
183    #[tokio::test]
184    async fn test_namespace_to_object() {
185        crate::tests::setup();
186        let namespace = crate::tests::CLIENT_NO_SECRET
187            .namespace("application.json")
188            .await
189            .unwrap();
190
191        let result = match namespace {
192            crate::namespace::Namespace::Json(json_namespace) => json_namespace.to_object(),
193            _ => panic!("Namespace is not a JSON namespace"),
194        };
195        let result: TestStruct = result.unwrap();
196        assert_eq!(
197            result,
198            TestStruct {
199                host: "localhost".to_string(),
200                port: 8080,
201                run: true,
202            }
203        );
204    }
205}