#[path = "support.rs"]
mod support;
use noyalib::{from_str, to_string};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
enum Resource {
Service {
name: String,
port: u16,
protocol: String,
},
ConfigMap {
name: String,
data: std::collections::BTreeMap<String, String>,
},
Secret {
name: String,
encoded: String,
},
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
enum Step {
Simple(String),
Detailed {
run: String,
#[serde(default)]
timeout: Option<u32>,
#[serde(default)]
env: std::collections::BTreeMap<String, String>,
},
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Pipeline {
name: String,
steps: Vec<Step>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
enum ConfigValue {
Text(String),
Number(i64),
Flag(bool),
List(Vec<String>),
}
fn main() {
support::header("noyalib -- untagged");
support::task_with_output("Infer K8s resource type from fields", || {
let yaml = r#"
- name: web
port: 80
protocol: TCP
- name: app-config
data:
DB_HOST: localhost
DB_PORT: "5432"
- name: api-key
encoded: c2VjcmV0
"#;
let resources: Vec<Resource> = from_str(yaml).unwrap();
resources
.iter()
.map(|r| match r {
Resource::Service { name, port, .. } => {
format!("{name:<12} -> Service (port {port})")
}
Resource::ConfigMap { name, data } => {
format!("{name:<12} -> ConfigMap ({} keys)", data.len())
}
Resource::Secret { name, .. } => {
format!("{name:<12} -> Secret")
}
})
.collect()
});
support::task_with_output("CI/CD pipeline: string or detailed step", || {
let yaml = r#"
name: build
steps:
- cargo check
- run: cargo test
timeout: 300
- cargo clippy
- run: cargo publish
env:
CARGO_TOKEN: secret
"#;
let pipeline: Pipeline = from_str(yaml).unwrap();
pipeline
.steps
.iter()
.map(|s| match s {
Step::Simple(cmd) => format!("simple: {cmd}"),
Step::Detailed { run, timeout, env } => {
let extras: Vec<String> = [
timeout.map(|t| format!("timeout={t}s")),
if env.is_empty() {
None
} else {
Some(format!("{} env vars", env.len()))
},
]
.into_iter()
.flatten()
.collect();
if extras.is_empty() {
format!("detailed: {run}")
} else {
format!("detailed: {run} ({})", extras.join(", "))
}
}
})
.collect()
});
support::task_with_output("Polymorphic config values", || {
let yaml = "host: localhost\nport: 8080\ndebug: true\ntags:\n - web\n - api\n";
let v: std::collections::BTreeMap<String, ConfigValue> = from_str(yaml).unwrap();
v.iter()
.map(|(k, v)| match v {
ConfigValue::Text(s) => format!("{k:<6} = \"{s}\" (Text)"),
ConfigValue::Number(n) => format!("{k:<6} = {n} (Number)"),
ConfigValue::Flag(b) => format!("{k:<6} = {b} (Flag)"),
ConfigValue::List(l) => format!("{k:<6} = [{}] (List)", l.join(", ")),
})
.collect()
});
support::task_with_output("Untagged enum roundtrip", || {
let pipeline = Pipeline {
name: "deploy".into(),
steps: vec![
Step::Simple("cargo build --release".into()),
Step::Detailed {
run: "deploy.sh".into(),
timeout: Some(600),
env: std::collections::BTreeMap::new(),
},
],
};
let yaml = to_string(&pipeline).unwrap();
let rt: Pipeline = from_str(&yaml).unwrap();
vec![
format!("match = {}", pipeline == rt),
format!("steps = {}", rt.steps.len()),
]
});
support::summary(4);
}