apollo_rust_client/namespace/
yaml.rs

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