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}