apollo_rust_client/namespace/mod.rs
1//! Namespace module for handling different configuration data formats.
2//!
3//! This module provides abstractions for working with various configuration data formats
4//! such as JSON, Properties, YAML, XML, and plain text. It includes functionality to
5//! automatically detect the format based on namespace naming conventions and convert
6//! raw JSON values into appropriate typed representations.
7//!
8//! # Supported Formats
9//!
10//! - **Properties**: Key-value pairs, typically used for application configuration
11//! - **JSON**: Structured JSON data with full object support
12//! - **YAML**: Structured YAML data with full object support
13//! - **XML**: XML format (planned, currently commented out)
14//! - **Text**: Plain text content
15//!
16//! # Usage
17//!
18//! The module automatically detects the format based on file extensions in the namespace name:
19//! - `.json` → JSON format
20//! - `.yaml` or `.yml` → YAML format
21//! - `.xml` → XML format
22//! - `.txt` → Text format
23//! - No extension → Properties format (default)
24
25use json::Json;
26use properties::Properties;
27use yaml::Yaml;
28
29pub mod json;
30pub mod properties;
31pub mod yaml;
32
33#[derive(Debug, thiserror::Error)]
34pub enum Error {
35 #[error("Failed to get JSON namespace: {0}")]
36 Json(#[from] json::Error),
37 #[error("Failed to get YAML namespace: {0}")]
38 Yaml(#[from] yaml::Error),
39 #[error("Failed to get text namespace: {0}")]
40 Text(String),
41 #[error("Failed to get XML namespace: {0}")]
42 Xml(String),
43 // #[error("Failed to get Properties namespace: {0}")]
44 // Properties(properties::Error),
45}
46
47/// Represents different types of configuration data formats.
48///
49/// This enum provides a unified interface for working with various configuration
50/// formats, allowing the client to handle different data types transparently.
51///
52/// # Examples
53///
54/// ```ignore
55/// use apollo_client::namespace::Namespace;
56///
57/// // Working with JSON data
58/// let json_namespace = Namespace::Json(json_data);
59///
60/// // Working with Properties data
61/// let props_namespace = Namespace::Properties(properties_data);
62///
63/// // Working with plain text
64/// let text_namespace = Namespace::Text("configuration content".to_string());
65/// ```
66#[derive(Clone, Debug)]
67pub enum Namespace {
68 /// Properties format - key-value pairs for application configuration
69 Properties(Properties),
70 /// JSON format - structured data with full object support
71 Json(Json),
72 /// YAML format - structured YAML data with full object support
73 Yaml(Yaml),
74 /// XML format - planned support for XML configuration files
75 // Xml(T),
76 /// Plain text format - raw string content
77 Text(String),
78}
79
80impl From<Namespace> for wasm_bindgen::JsValue {
81 fn from(val: Namespace) -> Self {
82 match val {
83 Namespace::Properties(properties) => properties.into(),
84 Namespace::Json(json) => json.into(),
85 Namespace::Yaml(yaml) => yaml.into(),
86 Namespace::Text(text) => text.into(),
87 }
88 }
89}
90
91/// Internal enum for identifying namespace data formats.
92///
93/// This enum is used internally by the format detection logic to determine
94/// the appropriate format based on namespace naming conventions.
95#[derive(Clone, Debug, PartialEq)]
96enum NamespaceType {
97 /// Properties format (default when no extension is specified)
98 Properties,
99 /// JSON format (detected by `.json` extension)
100 Json,
101 /// YAML format (detected by `.yaml` or `.yml` extensions)
102 Yaml,
103 /// XML format (detected by `.xml` extension)
104 Xml,
105 /// Plain text format (detected by `.txt` extension)
106 Text,
107}
108
109/// Determines the namespace type based on the namespace string.
110///
111/// This function analyzes the namespace string to detect the intended data format
112/// based on file extension conventions. If no extension is present, it defaults
113/// to the Properties format.
114///
115/// # Arguments
116///
117/// * `namespace` - The namespace identifier string, potentially containing a file extension
118///
119/// # Returns
120///
121/// A `NamespaceType` enum variant indicating the detected format
122///
123/// # Examples
124///
125/// ```rust
126/// // These examples show the internal logic (function is private)
127/// // get_namespace_type("app.config") -> NamespaceType::Properties
128/// // get_namespace_type("settings.json") -> NamespaceType::Json
129/// // get_namespace_type("config.yaml") -> NamespaceType::Yaml
130/// // get_namespace_type("data.xml") -> NamespaceType::Xml
131/// // get_namespace_type("readme.txt") -> NamespaceType::Text
132/// ```
133fn get_namespace_type(namespace: &str) -> NamespaceType {
134 let parts = namespace.split(".").collect::<Vec<&str>>();
135 if parts.len() == 1 {
136 NamespaceType::Properties
137 } else {
138 match parts.last().unwrap().to_lowercase().as_str() {
139 "json" => NamespaceType::Json,
140 "yaml" | "yml" => NamespaceType::Yaml,
141 "xml" => NamespaceType::Xml,
142 "txt" => NamespaceType::Text,
143 _ => NamespaceType::Text,
144 }
145 }
146}
147
148/// Creates a `Namespace` instance from a namespace identifier and JSON value.
149///
150/// This function serves as the main entry point for converting raw JSON data
151/// into the appropriate typed namespace representation. It automatically detects
152/// the format based on the namespace string and creates the corresponding variant.
153///
154/// # Arguments
155///
156/// * `namespace` - The namespace identifier string used for format detection
157/// * `value` - The raw JSON value to be converted into the appropriate format
158///
159/// # Returns
160///
161/// A `Namespace` enum variant containing the typed representation of the data
162///
163/// # Errors
164///
165/// This function will return an error if:
166/// - XML format is detected (not yet supported)
167/// - Text format content cannot be extracted from the JSON value
168/// - JSON or YAML parsing fails
169///
170/// # Examples
171///
172/// ```ignore
173/// use serde_json::json;
174/// use apollo_client::namespace::get_namespace;
175///
176/// let json_data = json!({"key": "value"});
177/// let namespace = get_namespace("config.json", json_data);
178/// // Returns Namespace::Json variant
179///
180/// let props_data = json!({"app.name": "MyApp", "app.version": "1.0"});
181/// let namespace = get_namespace("application", props_data);
182/// // Returns Namespace::Properties variant
183/// ```
184pub(crate) fn get_namespace(namespace: &str, value: serde_json::Value) -> Result<Namespace, Error> {
185 match get_namespace_type(namespace) {
186 NamespaceType::Properties => Ok(Namespace::Properties(properties::Properties::from(value))),
187 NamespaceType::Json => Ok(Namespace::Json(json::Json::try_from(value)?)),
188 NamespaceType::Yaml => Ok(Namespace::Yaml(yaml::Yaml::try_from(value)?)),
189 NamespaceType::Text => {
190 // Extract text content from the JSON value
191 let text_content = match value.get("content") {
192 Some(serde_json::Value::String(s)) => s.clone(),
193 _ => {
194 return Err(Error::Text(
195 "Failed to get text content from JSON value".to_string(),
196 ));
197 }
198 };
199 Ok(Namespace::Text(text_content))
200 }
201 NamespaceType::Xml => {
202 // XML format is not yet implemented
203 Err(Error::Xml("XML format is not yet supported".to_string()))
204 }
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn test_get_namespace_type_properties() {
214 // Test cases that should return Properties type
215 assert_eq!(get_namespace_type("application"), NamespaceType::Properties);
216 assert_eq!(get_namespace_type("config"), NamespaceType::Properties);
217 assert_eq!(get_namespace_type("database"), NamespaceType::Properties);
218 assert_eq!(
219 get_namespace_type("app-settings"),
220 NamespaceType::Properties
221 );
222 }
223
224 #[test]
225 fn test_get_namespace_type_json() {
226 // Test cases that should return Json type
227 assert_eq!(get_namespace_type("config.json"), NamespaceType::Json);
228 assert_eq!(get_namespace_type("settings.json"), NamespaceType::Json);
229 assert_eq!(get_namespace_type("app.config.json"), NamespaceType::Json);
230 assert_eq!(get_namespace_type("data.JSON"), NamespaceType::Json); // Test case insensitive
231 }
232
233 #[test]
234 fn test_get_namespace_type_yaml() {
235 // Test cases that should return Yaml type
236 assert_eq!(get_namespace_type("config.yaml"), NamespaceType::Yaml);
237 assert_eq!(get_namespace_type("settings.yml"), NamespaceType::Yaml);
238 assert_eq!(get_namespace_type("app.config.yaml"), NamespaceType::Yaml);
239 assert_eq!(get_namespace_type("data.YAML"), NamespaceType::Yaml); // Test case insensitive
240 assert_eq!(get_namespace_type("config.YML"), NamespaceType::Yaml); // Test case insensitive
241 }
242
243 #[test]
244 fn test_get_namespace_type_xml() {
245 // Test cases that should return Xml type
246 assert_eq!(get_namespace_type("config.xml"), NamespaceType::Xml);
247 assert_eq!(get_namespace_type("settings.xml"), NamespaceType::Xml);
248 assert_eq!(get_namespace_type("app.config.xml"), NamespaceType::Xml);
249 assert_eq!(get_namespace_type("data.XML"), NamespaceType::Xml); // Test case insensitive
250 }
251
252 #[test]
253 fn test_get_namespace_type_text() {
254 // Test cases that should return Text type
255 assert_eq!(get_namespace_type("readme.txt"), NamespaceType::Text);
256 assert_eq!(get_namespace_type("notes.txt"), NamespaceType::Text);
257 assert_eq!(get_namespace_type("config.TXT"), NamespaceType::Text); // Test case insensitive
258 }
259
260 #[test]
261 fn test_get_namespace_type_unsupported_extensions() {
262 // Test cases with unsupported extensions that should default to Text
263 assert_eq!(get_namespace_type("config.ini"), NamespaceType::Text);
264 assert_eq!(get_namespace_type("settings.cfg"), NamespaceType::Text);
265 assert_eq!(get_namespace_type("app.properties"), NamespaceType::Text);
266 assert_eq!(get_namespace_type("data.csv"), NamespaceType::Text);
267 assert_eq!(get_namespace_type("config.toml"), NamespaceType::Text);
268 assert_eq!(get_namespace_type("settings.conf"), NamespaceType::Text);
269 assert_eq!(get_namespace_type("app.unknown"), NamespaceType::Text);
270 assert_eq!(get_namespace_type("file.xyz"), NamespaceType::Text);
271 }
272
273 #[test]
274 fn test_get_namespace_type_edge_cases() {
275 // Test edge cases
276 assert_eq!(get_namespace_type(""), NamespaceType::Properties); // Empty string
277 assert_eq!(get_namespace_type(".json"), NamespaceType::Json); // Leading dot
278 assert_eq!(get_namespace_type("file."), NamespaceType::Text); // Trailing dot with no extension
279 assert_eq!(get_namespace_type("file..json"), NamespaceType::Json); // Double dots
280 assert_eq!(
281 get_namespace_type("config.json.backup"),
282 NamespaceType::Text
283 ); // Multiple extensions
284 }
285}