pub use crate::generators::rust::common::config::RustGeneratorConfig as RustAioductConfig;
use serde::{Deserialize, Serialize};
const DEFAULT_AIODUCT_VERSION: &str = "0.1.8";
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum AioductRuntime {
#[default]
Tokio,
Smol,
Compio,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum AioductTls {
#[default]
RustlsRing,
RustlsAwsLcRs,
#[serde(rename = "false", alias = "disabled", alias = "none")]
Disabled,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AioductFeatureConfig {
#[serde(default)]
pub version: Option<String>,
#[serde(default)]
pub runtime: Option<AioductRuntime>,
#[serde(default)]
pub tls: Option<AioductTls>,
#[serde(default)]
pub compression: Option<Vec<String>>,
#[serde(default)]
pub features: Option<Vec<String>>,
}
impl AioductFeatureConfig {
pub fn version(&self) -> &str {
self.version.as_deref().unwrap_or(DEFAULT_AIODUCT_VERSION)
}
pub fn resolved_features(&self) -> Vec<String> {
let mut feats = Vec::new();
match self.runtime.as_ref().unwrap_or(&AioductRuntime::Tokio) {
AioductRuntime::Tokio => feats.push("tokio".to_string()),
AioductRuntime::Smol => feats.push("smol".to_string()),
AioductRuntime::Compio => feats.push("compio".to_string()),
}
match self.tls.as_ref().unwrap_or(&AioductTls::RustlsRing) {
AioductTls::RustlsRing => {
feats.push("rustls".to_string());
feats.push("rustls-ring".to_string());
}
AioductTls::RustlsAwsLcRs => {
feats.push("rustls".to_string());
feats.push("rustls-aws-lc-rs".to_string());
}
AioductTls::Disabled => {}
}
feats.push("json".to_string());
if let Some(compression) = &self.compression {
for c in compression {
let normalized = c.to_lowercase();
if matches!(normalized.as_str(), "gzip" | "brotli" | "zstd" | "deflate") {
feats.push(normalized);
}
}
}
if let Some(extras) = &self.features {
for f in extras {
if !feats.contains(f) {
feats.push(f.clone());
}
}
}
feats
}
pub fn features_toml_array(&self) -> String {
let feats = self.resolved_features();
let quoted: Vec<String> = feats.iter().map(|f| format!("\"{f}\"")).collect();
format!("[{}]", quoted.join(", "))
}
}
pub fn default_aioduct_features() -> AioductFeatureConfig {
AioductFeatureConfig::default()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_resolves_to_tokio_rustls_ring_json() {
let config = AioductFeatureConfig::default();
assert_eq!(
config.resolved_features(),
vec!["tokio", "rustls", "rustls-ring", "json"]
);
}
#[test]
fn default_version_is_current() {
let config = AioductFeatureConfig::default();
assert_eq!(config.version(), "0.1.8");
}
#[test]
fn custom_version_override() {
let config = AioductFeatureConfig {
version: Some("0.2.0".to_string()),
..Default::default()
};
assert_eq!(config.version(), "0.2.0");
}
#[test]
fn smol_runtime() {
let config = AioductFeatureConfig {
runtime: Some(AioductRuntime::Smol),
..Default::default()
};
let feats = config.resolved_features();
assert!(feats.contains(&"smol".to_string()));
assert!(!feats.contains(&"tokio".to_string()));
}
#[test]
fn aws_lc_rs_tls() {
let config = AioductFeatureConfig {
tls: Some(AioductTls::RustlsAwsLcRs),
..Default::default()
};
let feats = config.resolved_features();
assert!(feats.contains(&"rustls".to_string()));
assert!(feats.contains(&"rustls-aws-lc-rs".to_string()));
assert!(!feats.contains(&"rustls-ring".to_string()));
}
#[test]
fn tls_disabled() {
let config = AioductFeatureConfig {
tls: Some(AioductTls::Disabled),
..Default::default()
};
let feats = config.resolved_features();
assert!(!feats.contains(&"rustls".to_string()));
assert!(!feats.contains(&"rustls-ring".to_string()));
}
#[test]
fn compression_features() {
let config = AioductFeatureConfig {
compression: Some(vec!["gzip".to_string(), "brotli".to_string()]),
..Default::default()
};
let feats = config.resolved_features();
assert!(feats.contains(&"gzip".to_string()));
assert!(feats.contains(&"brotli".to_string()));
}
#[test]
fn invalid_compression_ignored() {
let config = AioductFeatureConfig {
compression: Some(vec!["lz4".to_string(), "gzip".to_string()]),
..Default::default()
};
let feats = config.resolved_features();
assert!(feats.contains(&"gzip".to_string()));
assert!(!feats.contains(&"lz4".to_string()));
}
#[test]
fn passthrough_features() {
let config = AioductFeatureConfig {
features: Some(vec!["tracing".to_string(), "http3".to_string()]),
..Default::default()
};
let feats = config.resolved_features();
assert!(feats.contains(&"tracing".to_string()));
assert!(feats.contains(&"http3".to_string()));
}
#[test]
fn passthrough_no_duplicates() {
let config = AioductFeatureConfig {
features: Some(vec!["tokio".to_string(), "json".to_string()]),
..Default::default()
};
let feats = config.resolved_features();
assert_eq!(feats.iter().filter(|f| *f == "tokio").count(), 1);
assert_eq!(feats.iter().filter(|f| *f == "json").count(), 1);
}
#[test]
fn features_toml_array_format() {
let config = AioductFeatureConfig::default();
assert_eq!(
config.features_toml_array(),
r#"["tokio", "rustls", "rustls-ring", "json"]"#
);
}
#[test]
fn deserialize_from_toml() {
let toml_str = r#"
version = "0.2.0"
runtime = "smol"
tls = "rustls-aws-lc-rs"
compression = ["gzip", "zstd"]
features = ["tracing"]
"#;
let config: AioductFeatureConfig = toml::from_str(toml_str).unwrap();
assert_eq!(config.version(), "0.2.0");
assert_eq!(config.runtime, Some(AioductRuntime::Smol));
assert_eq!(config.tls, Some(AioductTls::RustlsAwsLcRs));
assert_eq!(
config.compression,
Some(vec!["gzip".to_string(), "zstd".to_string()])
);
assert_eq!(config.features, Some(vec!["tracing".to_string()]));
}
#[test]
fn deserialize_tls_disabled() {
let toml_str = r#"tls = "false""#;
let config: AioductFeatureConfig = toml::from_str(toml_str).unwrap();
assert_eq!(config.tls, Some(AioductTls::Disabled));
}
}