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/// Comprehensive error types that can occur when working with namespaces.
34///
35/// This enum covers all possible error conditions that may arise during namespace
36/// operations, from format detection to parsing and type conversion failures.
37///
38/// # Error Categories
39///
40/// - **JSON Errors**: Issues with JSON namespace processing and deserialization
41/// - **YAML Errors**: Issues with YAML namespace processing and deserialization
42/// - **Text Errors**: Issues with text content extraction and processing
43/// - **XML Errors**: Issues with XML format (currently unsupported)
44///
45/// # Examples
46///
47/// ```rust
48/// use apollo_rust_client::namespace::{Namespace, Error};
49/// use serde_json::json;
50///
51/// // Create a sample JSON value
52/// let json_value = json!({
53///     "content": r#"{"name": "test", "value": 42}"#
54/// });
55///
56/// // This would typically be done through the client, but for demonstration:
57/// // The error handling shows how different namespace errors are handled
58/// fn handle_namespace_error(error: Error) {
59///     match error {
60///         Error::Json(json_error) => {
61///             // Handle JSON-specific errors
62///             eprintln!("JSON error: {}", json_error);
63///         }
64///         Error::Yaml(yaml_error) => {
65///             // Handle YAML-specific errors
66///             eprintln!("YAML error: {}", yaml_error);
67///         }
68///         Error::Text(text_error) => {
69///             // Handle text-specific errors
70///             eprintln!("Text error: {}", text_error);
71///         }
72///         Error::Xml(xml_error) => {
73///             // Handle XML-specific errors (currently unsupported)
74///             eprintln!("XML error: {}", xml_error);
75///         }
76///     }
77/// }
78/// ```
79#[derive(Debug, thiserror::Error)]
80pub enum Error {
81    /// Failed to process JSON namespace.
82    ///
83    /// This error occurs when there are issues with JSON format detection,
84    /// parsing, or deserialization operations specific to JSON namespaces.
85    #[error("Failed to get JSON namespace: {0}")]
86    Json(#[from] json::Error),
87
88    /// Failed to process YAML namespace.
89    ///
90    /// This error occurs when there are issues with YAML format detection,
91    /// parsing, or deserialization operations specific to YAML namespaces.
92    #[error("Failed to get YAML namespace: {0}")]
93    Yaml(#[from] yaml::Error),
94
95    /// Failed to process text namespace.
96    ///
97    /// This error occurs when there are issues with text content extraction
98    /// or processing from the configuration data.
99    #[error("Failed to get text namespace: {0}")]
100    Text(String),
101
102    /// Failed to process XML namespace.
103    ///
104    /// This error occurs when XML format is detected but XML processing
105    /// is not yet supported by the library.
106    #[error("Failed to get XML namespace: {0}")]
107    Xml(String),
108    // #[error("Failed to get Properties namespace: {0}")]
109    // Properties(properties::Error),
110}
111
112/// Represents different types of configuration data formats.
113///
114/// This enum provides a unified interface for working with various configuration
115/// formats, allowing the client to handle different data types transparently.
116///
117/// # Examples
118///
119/// ```ignore
120/// use apollo_client::namespace::Namespace;
121///
122/// // Working with JSON data
123/// let json_namespace = Namespace::Json(json_data);
124///
125/// // Working with Properties data
126/// let props_namespace = Namespace::Properties(properties_data);
127///
128/// // Working with plain text
129/// let text_namespace = Namespace::Text("configuration content".to_string());
130/// ```
131#[derive(Clone, Debug)]
132pub enum Namespace {
133    /// Properties format - key-value pairs for application configuration
134    Properties(Properties),
135    /// JSON format - structured data with full object support
136    Json(Json),
137    /// YAML format - structured YAML data with full object support
138    Yaml(Yaml),
139    /// XML format - planned support for XML configuration files
140    // Xml(T),
141    /// Plain text format - raw string content
142    Text(String),
143}
144
145impl From<Namespace> for wasm_bindgen::JsValue {
146    fn from(val: Namespace) -> Self {
147        match val {
148            Namespace::Properties(properties) => properties.into(),
149            Namespace::Json(json) => json.into(),
150            Namespace::Yaml(yaml) => yaml.into(),
151            Namespace::Text(text) => text.into(),
152        }
153    }
154}
155
156/// Internal enum for identifying namespace data formats.
157///
158/// This enum is used internally by the format detection logic to determine
159/// the appropriate format based on namespace naming conventions.
160#[derive(Clone, Debug, PartialEq)]
161enum NamespaceType {
162    /// Properties format (default when no extension is specified)
163    Properties,
164    /// JSON format (detected by `.json` extension)
165    Json,
166    /// YAML format (detected by `.yaml` or `.yml` extensions)
167    Yaml,
168    /// XML format (detected by `.xml` extension)
169    Xml,
170    /// Plain text format (detected by `.txt` extension)
171    Text,
172}
173
174/// Determines the namespace type based on the namespace string.
175///
176/// This function analyzes the namespace string to detect the intended data format
177/// based on file extension conventions. If no extension is present, it defaults
178/// to the Properties format.
179///
180/// # Arguments
181///
182/// * `namespace` - The namespace identifier string, potentially containing a file extension
183///
184/// # Returns
185///
186/// A `NamespaceType` enum variant indicating the detected format
187///
188/// # Examples
189///
190/// ```rust
191/// // These examples show the internal logic (function is private)
192/// // get_namespace_type("app.config") -> NamespaceType::Properties
193/// // get_namespace_type("settings.json") -> NamespaceType::Json
194/// // get_namespace_type("config.yaml") -> NamespaceType::Yaml
195/// // get_namespace_type("data.xml") -> NamespaceType::Xml
196/// // get_namespace_type("readme.txt") -> NamespaceType::Text
197/// ```
198fn get_namespace_type(namespace: &str) -> NamespaceType {
199    let parts = namespace.split('.').collect::<Vec<&str>>();
200    if parts.len() == 1 {
201        NamespaceType::Properties
202    } else {
203        match parts.last().unwrap().to_lowercase().as_str() {
204            "json" => NamespaceType::Json,
205            "yaml" | "yml" => NamespaceType::Yaml,
206            "xml" => NamespaceType::Xml,
207            _ => NamespaceType::Text,
208        }
209    }
210}
211
212/// Creates a `Namespace` instance from a namespace identifier and JSON value.
213///
214/// This function serves as the main entry point for converting raw JSON data
215/// into the appropriate typed namespace representation. It automatically detects
216/// the format based on the namespace string and creates the corresponding variant.
217///
218/// # Arguments
219///
220/// * `namespace` - The namespace identifier string used for format detection
221/// * `value` - The raw JSON value to be converted into the appropriate format
222///
223/// # Returns
224///
225/// * `Ok(Namespace)` - A namespace variant containing the typed representation of the data
226/// * `Err(Error::Json)` - If JSON format processing fails
227/// * `Err(Error::Yaml)` - If YAML format processing fails
228/// * `Err(Error::Text)` - If text format content extraction fails
229/// * `Err(Error::Xml)` - If XML format is detected (not yet supported)
230///
231/// # Errors
232///
233/// This function will return an error if:
234/// - XML format is detected (not yet supported)
235/// - Text format content cannot be extracted from the JSON value
236/// - JSON or YAML parsing fails during format conversion
237/// - The namespace format detection logic encounters unexpected data
238///
239/// # Examples
240///
241/// ```ignore
242/// use serde_json::json;
243/// use apollo_client::namespace::get_namespace;
244///
245/// let json_data = json!({"content": "{\"key\": \"value\"}"});
246/// let namespace = get_namespace("config.json", json_data)?;
247/// // Returns Namespace::Json variant
248///
249/// let props_data = json!({"app.name": "MyApp", "app.version": "1.0"});
250/// let namespace = get_namespace("application", props_data)?;
251/// // Returns Namespace::Properties variant
252/// ```
253pub(crate) fn get_namespace(namespace: &str, value: serde_json::Value) -> Result<Namespace, Error> {
254    match get_namespace_type(namespace) {
255        NamespaceType::Properties => Ok(Namespace::Properties(properties::Properties::from(value))),
256        NamespaceType::Json => Ok(Namespace::Json(json::Json::try_from(value)?)),
257        NamespaceType::Yaml => Ok(Namespace::Yaml(yaml::Yaml::try_from(value)?)),
258        NamespaceType::Text => {
259            // Extract text content from the JSON value
260            let text_content = match value.get("content") {
261                Some(serde_json::Value::String(s)) => s.clone(),
262                _ => {
263                    return Err(Error::Text(
264                        "Failed to get text content from JSON value".to_string(),
265                    ));
266                }
267            };
268            Ok(Namespace::Text(text_content))
269        }
270        NamespaceType::Xml => {
271            // XML format is not yet implemented
272            Err(Error::Xml("XML format is not yet supported".to_string()))
273        }
274    }
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280
281    #[test]
282    fn test_get_namespace_type_properties() {
283        // Test cases that should return Properties type
284        assert_eq!(get_namespace_type("application"), NamespaceType::Properties);
285        assert_eq!(get_namespace_type("config"), NamespaceType::Properties);
286        assert_eq!(get_namespace_type("database"), NamespaceType::Properties);
287        assert_eq!(
288            get_namespace_type("app-settings"),
289            NamespaceType::Properties
290        );
291    }
292
293    #[test]
294    fn test_get_namespace_type_json() {
295        // Test cases that should return Json type
296        assert_eq!(get_namespace_type("config.json"), NamespaceType::Json);
297        assert_eq!(get_namespace_type("settings.json"), NamespaceType::Json);
298        assert_eq!(get_namespace_type("app.config.json"), NamespaceType::Json);
299        assert_eq!(get_namespace_type("data.JSON"), NamespaceType::Json); // Test case insensitive
300    }
301
302    #[test]
303    fn test_get_namespace_type_yaml() {
304        // Test cases that should return Yaml type
305        assert_eq!(get_namespace_type("config.yaml"), NamespaceType::Yaml);
306        assert_eq!(get_namespace_type("settings.yml"), NamespaceType::Yaml);
307        assert_eq!(get_namespace_type("app.config.yaml"), NamespaceType::Yaml);
308        assert_eq!(get_namespace_type("data.YAML"), NamespaceType::Yaml); // Test case insensitive
309        assert_eq!(get_namespace_type("config.YML"), NamespaceType::Yaml); // Test case insensitive
310    }
311
312    #[test]
313    fn test_get_namespace_type_xml() {
314        // Test cases that should return Xml type
315        assert_eq!(get_namespace_type("config.xml"), NamespaceType::Xml);
316        assert_eq!(get_namespace_type("settings.xml"), NamespaceType::Xml);
317        assert_eq!(get_namespace_type("app.config.xml"), NamespaceType::Xml);
318        assert_eq!(get_namespace_type("data.XML"), NamespaceType::Xml); // Test case insensitive
319    }
320
321    #[test]
322    fn test_get_namespace_type_text() {
323        // Test cases that should return Text type
324        assert_eq!(get_namespace_type("readme.txt"), NamespaceType::Text);
325        assert_eq!(get_namespace_type("notes.txt"), NamespaceType::Text);
326        assert_eq!(get_namespace_type("config.TXT"), NamespaceType::Text); // Test case insensitive
327    }
328
329    #[test]
330    fn test_get_namespace_type_unsupported_extensions() {
331        // Test cases with unsupported extensions that should default to Text
332        assert_eq!(get_namespace_type("config.ini"), NamespaceType::Text);
333        assert_eq!(get_namespace_type("settings.cfg"), NamespaceType::Text);
334        assert_eq!(get_namespace_type("app.properties"), NamespaceType::Text);
335        assert_eq!(get_namespace_type("data.csv"), NamespaceType::Text);
336        assert_eq!(get_namespace_type("config.toml"), NamespaceType::Text);
337        assert_eq!(get_namespace_type("settings.conf"), NamespaceType::Text);
338        assert_eq!(get_namespace_type("app.unknown"), NamespaceType::Text);
339        assert_eq!(get_namespace_type("file.xyz"), NamespaceType::Text);
340    }
341
342    #[test]
343    fn test_get_namespace_type_edge_cases() {
344        // Test edge cases
345        assert_eq!(get_namespace_type(""), NamespaceType::Properties); // Empty string
346        assert_eq!(get_namespace_type(".json"), NamespaceType::Json); // Leading dot
347        assert_eq!(get_namespace_type("file."), NamespaceType::Text); // Trailing dot with no extension
348        assert_eq!(get_namespace_type("file..json"), NamespaceType::Json); // Double dots
349        assert_eq!(
350            get_namespace_type("config.json.backup"),
351            NamespaceType::Text
352        ); // Multiple extensions
353    }
354}