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}