openapi_nexus/generators/rust/aioduct/
config.rs1pub use crate::generators::rust::common::config::RustGeneratorConfig as RustAioductConfig;
4
5use serde::{Deserialize, Serialize};
6
7const DEFAULT_AIODUCT_VERSION: &str = "0.2";
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 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 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 feats.push("json".to_string());
72
73 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 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.2");
123 }
124
125 #[test]
126 fn custom_version_override() {
127 let config = AioductFeatureConfig {
128 version: Some("0.2".to_string()),
129 ..Default::default()
130 };
131 assert_eq!(config.version(), "0.2");
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"
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");
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}