Skip to main content

openapi_nexus/generators/rust/aioduct/
config.rs

1//! Rust aioduct generator-specific configuration.
2
3pub use crate::generators::rust::common::config::RustGeneratorConfig as RustAioductConfig;
4
5use serde::{Deserialize, Serialize};
6
7const DEFAULT_AIODUCT_VERSION: &str = "0.1.8";
8
9#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
10#[serde(rename_all = "snake_case")]
11pub enum AioductRuntime {
12    #[default]
13    Tokio,
14    Smol,
15    Compio,
16}
17
18#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
19#[serde(rename_all = "kebab-case")]
20pub enum AioductTls {
21    #[default]
22    RustlsRing,
23    RustlsAwsLcRs,
24    #[serde(rename = "false", alias = "disabled", alias = "none")]
25    Disabled,
26}
27
28#[derive(Debug, Clone, Default, Serialize, Deserialize)]
29pub struct AioductFeatureConfig {
30    #[serde(default)]
31    pub version: Option<String>,
32    #[serde(default)]
33    pub runtime: Option<AioductRuntime>,
34    #[serde(default)]
35    pub tls: Option<AioductTls>,
36    #[serde(default)]
37    pub compression: Option<Vec<String>>,
38    #[serde(default)]
39    pub features: Option<Vec<String>>,
40}
41
42impl AioductFeatureConfig {
43    pub fn version(&self) -> &str {
44        self.version.as_deref().unwrap_or(DEFAULT_AIODUCT_VERSION)
45    }
46
47    pub fn resolved_features(&self) -> Vec<String> {
48        let mut feats = Vec::new();
49
50        // Runtime (exactly one required)
51        match self.runtime.as_ref().unwrap_or(&AioductRuntime::Tokio) {
52            AioductRuntime::Tokio => feats.push("tokio".to_string()),
53            AioductRuntime::Smol => feats.push("smol".to_string()),
54            AioductRuntime::Compio => feats.push("compio".to_string()),
55        }
56
57        // TLS
58        match self.tls.as_ref().unwrap_or(&AioductTls::RustlsRing) {
59            AioductTls::RustlsRing => {
60                feats.push("rustls".to_string());
61                feats.push("rustls-ring".to_string());
62            }
63            AioductTls::RustlsAwsLcRs => {
64                feats.push("rustls".to_string());
65                feats.push("rustls-aws-lc-rs".to_string());
66            }
67            AioductTls::Disabled => {}
68        }
69
70        // JSON (always required for generated SDK)
71        feats.push("json".to_string());
72
73        // Compression
74        if let Some(compression) = &self.compression {
75            for c in compression {
76                let normalized = c.to_lowercase();
77                if matches!(normalized.as_str(), "gzip" | "brotli" | "zstd" | "deflate") {
78                    feats.push(normalized);
79                }
80            }
81        }
82
83        // Pass-through extras
84        if let Some(extras) = &self.features {
85            for f in extras {
86                if !feats.contains(f) {
87                    feats.push(f.clone());
88                }
89            }
90        }
91
92        feats
93    }
94
95    pub fn features_toml_array(&self) -> String {
96        let feats = self.resolved_features();
97        let quoted: Vec<String> = feats.iter().map(|f| format!("\"{f}\"")).collect();
98        format!("[{}]", quoted.join(", "))
99    }
100}
101
102pub fn default_aioduct_features() -> AioductFeatureConfig {
103    AioductFeatureConfig::default()
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn default_resolves_to_tokio_rustls_ring_json() {
112        let config = AioductFeatureConfig::default();
113        assert_eq!(
114            config.resolved_features(),
115            vec!["tokio", "rustls", "rustls-ring", "json"]
116        );
117    }
118
119    #[test]
120    fn default_version_is_current() {
121        let config = AioductFeatureConfig::default();
122        assert_eq!(config.version(), "0.1.8");
123    }
124
125    #[test]
126    fn custom_version_override() {
127        let config = AioductFeatureConfig {
128            version: Some("0.2.0".to_string()),
129            ..Default::default()
130        };
131        assert_eq!(config.version(), "0.2.0");
132    }
133
134    #[test]
135    fn smol_runtime() {
136        let config = AioductFeatureConfig {
137            runtime: Some(AioductRuntime::Smol),
138            ..Default::default()
139        };
140        let feats = config.resolved_features();
141        assert!(feats.contains(&"smol".to_string()));
142        assert!(!feats.contains(&"tokio".to_string()));
143    }
144
145    #[test]
146    fn aws_lc_rs_tls() {
147        let config = AioductFeatureConfig {
148            tls: Some(AioductTls::RustlsAwsLcRs),
149            ..Default::default()
150        };
151        let feats = config.resolved_features();
152        assert!(feats.contains(&"rustls".to_string()));
153        assert!(feats.contains(&"rustls-aws-lc-rs".to_string()));
154        assert!(!feats.contains(&"rustls-ring".to_string()));
155    }
156
157    #[test]
158    fn tls_disabled() {
159        let config = AioductFeatureConfig {
160            tls: Some(AioductTls::Disabled),
161            ..Default::default()
162        };
163        let feats = config.resolved_features();
164        assert!(!feats.contains(&"rustls".to_string()));
165        assert!(!feats.contains(&"rustls-ring".to_string()));
166    }
167
168    #[test]
169    fn compression_features() {
170        let config = AioductFeatureConfig {
171            compression: Some(vec!["gzip".to_string(), "brotli".to_string()]),
172            ..Default::default()
173        };
174        let feats = config.resolved_features();
175        assert!(feats.contains(&"gzip".to_string()));
176        assert!(feats.contains(&"brotli".to_string()));
177    }
178
179    #[test]
180    fn invalid_compression_ignored() {
181        let config = AioductFeatureConfig {
182            compression: Some(vec!["lz4".to_string(), "gzip".to_string()]),
183            ..Default::default()
184        };
185        let feats = config.resolved_features();
186        assert!(feats.contains(&"gzip".to_string()));
187        assert!(!feats.contains(&"lz4".to_string()));
188    }
189
190    #[test]
191    fn passthrough_features() {
192        let config = AioductFeatureConfig {
193            features: Some(vec!["tracing".to_string(), "http3".to_string()]),
194            ..Default::default()
195        };
196        let feats = config.resolved_features();
197        assert!(feats.contains(&"tracing".to_string()));
198        assert!(feats.contains(&"http3".to_string()));
199    }
200
201    #[test]
202    fn passthrough_no_duplicates() {
203        let config = AioductFeatureConfig {
204            features: Some(vec!["tokio".to_string(), "json".to_string()]),
205            ..Default::default()
206        };
207        let feats = config.resolved_features();
208        assert_eq!(feats.iter().filter(|f| *f == "tokio").count(), 1);
209        assert_eq!(feats.iter().filter(|f| *f == "json").count(), 1);
210    }
211
212    #[test]
213    fn features_toml_array_format() {
214        let config = AioductFeatureConfig::default();
215        assert_eq!(
216            config.features_toml_array(),
217            r#"["tokio", "rustls", "rustls-ring", "json"]"#
218        );
219    }
220
221    #[test]
222    fn deserialize_from_toml() {
223        let toml_str = r#"
224            version = "0.2.0"
225            runtime = "smol"
226            tls = "rustls-aws-lc-rs"
227            compression = ["gzip", "zstd"]
228            features = ["tracing"]
229        "#;
230        let config: AioductFeatureConfig = toml::from_str(toml_str).unwrap();
231        assert_eq!(config.version(), "0.2.0");
232        assert_eq!(config.runtime, Some(AioductRuntime::Smol));
233        assert_eq!(config.tls, Some(AioductTls::RustlsAwsLcRs));
234        assert_eq!(
235            config.compression,
236            Some(vec!["gzip".to_string(), "zstd".to_string()])
237        );
238        assert_eq!(config.features, Some(vec!["tracing".to_string()]));
239    }
240
241    #[test]
242    fn deserialize_tls_disabled() {
243        let toml_str = r#"tls = "false""#;
244        let config: AioductFeatureConfig = toml::from_str(toml_str).unwrap();
245        assert_eq!(config.tls, Some(AioductTls::Disabled));
246    }
247}