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}