by_loco_openapi/
config.rs1use std::collections::BTreeMap;
2use std::sync::OnceLock;
3
4use loco_rs::Error;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8static OPENAPI_CONFIG: OnceLock<Option<OpenAPIConfig>> = OnceLock::new();
9
10#[derive(Debug)]
12pub struct InitializerConfig<'a>(&'a Option<BTreeMap<String, Value>>);
13
14impl<'a> From<&'a Option<BTreeMap<String, Value>>> for InitializerConfig<'a> {
15 fn from(initializers: &'a Option<BTreeMap<String, Value>>) -> Self {
16 InitializerConfig(initializers)
17 }
18}
19
20impl<'a> From<InitializerConfig<'a>> for Option<OpenAPIConfig> {
21 fn from(config: InitializerConfig<'a>) -> Self {
22 config
23 .0
24 .as_ref()
25 .and_then(|m| m.get("openapi"))
26 .cloned()
27 .and_then(|json| serde_json::from_value(json).ok())
28 }
29}
30
31pub fn set_openapi_config(
37 config: Option<OpenAPIConfig>,
38) -> Result<Option<&'static OpenAPIConfig>, Error> {
39 Ok(OPENAPI_CONFIG.get_or_init(|| config).as_ref())
40}
41
42pub fn get_openapi_config() -> Option<&'static OpenAPIConfig> {
43 OPENAPI_CONFIG.get().unwrap_or(&None).as_ref()
44}
45
46#[derive(Debug, Clone, Deserialize, Serialize)]
65#[cfg_attr(test, derive(PartialEq, Eq))]
66pub struct OpenAPIConfig {
67 #[cfg(feature = "redoc")]
76 #[serde(flatten)]
77 pub redoc: Option<OpenAPIType>,
78 #[cfg(feature = "scalar")]
87 #[serde(flatten)]
88 pub scalar: Option<OpenAPIType>,
89 #[cfg(feature = "swagger")]
99 #[serde(flatten)]
100 pub swagger: Option<OpenAPIType>,
101}
102
103#[derive(Debug, Clone, Deserialize, Serialize)]
105#[cfg_attr(test, derive(PartialEq, Eq))]
106pub enum OpenAPIType {
107 #[cfg(feature = "redoc")]
116 #[serde(rename = "redoc")]
117 Redoc {
118 url: String,
120 spec_json_url: Option<String>,
122 spec_yaml_url: Option<String>,
124 },
125 #[cfg(feature = "scalar")]
134 #[serde(rename = "scalar")]
135 Scalar {
136 url: String,
138 spec_json_url: Option<String>,
140 spec_yaml_url: Option<String>,
142 },
143 #[cfg(feature = "swagger")]
153 #[serde(rename = "swagger")]
154 Swagger {
155 url: String,
158 spec_json_url: String,
160 spec_yaml_url: Option<String>,
162 },
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 #[cfg(any(feature = "swagger", feature = "redoc", feature = "scalar"))]
169 use serde_json::json;
170
171 #[cfg(any(feature = "swagger", feature = "redoc", feature = "scalar"))]
173 fn create_mock_config() -> BTreeMap<String, Value> {
174 let mut config = BTreeMap::new();
175
176 let mut openapi_config = serde_json::Map::new();
178
179 #[cfg(feature = "swagger")]
181 {
182 openapi_config.insert(
183 "swagger".to_string(),
184 json!({
185 "url": "/swagger",
186 "spec_json_url": "/api-docs/openapi.json"
187 }),
188 );
189 }
190
191 #[cfg(feature = "redoc")]
193 {
194 openapi_config.insert(
195 "redoc".to_string(),
196 json!({
197 "url": "/redoc",
198 "spec_json_url": "/redoc/openapi.json",
199 "spec_yaml_url": "/redoc/openapi.yaml"
200 }),
201 );
202 }
203
204 #[cfg(feature = "scalar")]
206 {
207 openapi_config.insert(
208 "scalar".to_string(),
209 json!({
210 "url": "/scalar",
211 "spec_json_url": "/scalar/openapi.json",
212 "spec_yaml_url": "/scalar/openapi.yaml"
213 }),
214 );
215 }
216
217 config.insert("openapi".to_string(), Value::Object(openapi_config));
218 config
219 }
220
221 #[test]
222 #[cfg(any(feature = "swagger", feature = "redoc", feature = "scalar"))]
223 fn test_data_conversion() {
224 let initializers = Some(create_mock_config());
226
227 let initializer_config: InitializerConfig = (&initializers).into();
229 let openapi_config: Option<OpenAPIConfig> = initializer_config.into();
230
231 assert!(
233 openapi_config.is_some(),
234 "OpenAPIConfig should be created successfully"
235 );
236
237 let config = openapi_config.unwrap();
239
240 #[cfg(feature = "swagger")]
241 {
242 let swagger = config.swagger.as_ref();
243 assert!(swagger.is_some(), "Swagger config should be present");
244
245 let expected = OpenAPIType::Swagger {
246 url: "/swagger".to_string(),
247 spec_json_url: "/api-docs/openapi.json".to_string(),
248 spec_yaml_url: None,
249 };
250 assert_eq!(swagger, Some(&expected));
251 }
252
253 #[cfg(feature = "redoc")]
254 {
255 let redoc = config.redoc.as_ref();
256 assert!(redoc.is_some(), "Redoc config should be present");
257
258 let expected = OpenAPIType::Redoc {
259 url: "/redoc".to_string(),
260 spec_json_url: Some("/redoc/openapi.json".to_string()),
261 spec_yaml_url: Some("/redoc/openapi.yaml".to_string()),
262 };
263 assert_eq!(redoc, Some(&expected));
264 }
265
266 #[cfg(feature = "scalar")]
267 {
268 let scalar = config.scalar.as_ref();
269 assert!(scalar.is_some(), "Scalar config should be present");
270
271 let expected = OpenAPIType::Scalar {
272 url: "/scalar".to_string(),
273 spec_json_url: Some("/scalar/openapi.json".to_string()),
274 spec_yaml_url: Some("/scalar/openapi.yaml".to_string()),
275 };
276 assert_eq!(scalar, Some(&expected));
277 }
278 }
279
280 #[test]
281 fn test_none_conversion() {
282 let initializers: Option<BTreeMap<String, Value>> = None;
284
285 let openapi_config: Option<OpenAPIConfig> = InitializerConfig::from(&initializers).into();
287
288 assert!(openapi_config.is_none(), "OpenAPIConfig should be None");
290 }
291}