#![allow(missing_docs)]
use noyalib::{Spanned, Value, from_str};
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, PartialEq)]
struct Common {
image: String,
version: String,
}
#[derive(Debug, Deserialize, PartialEq)]
struct Service {
name: String,
#[serde(flatten)]
common: Common,
replicas: u32,
}
#[test]
fn flatten_with_anchor_alias_expansion() {
let yaml = "
defaults: &defaults
image: registry.example.com/api
version: 1.0.0
services:
- name: api
<<: *defaults
replicas: 3
- name: worker
<<: *defaults
replicas: 5
";
#[derive(Debug, Deserialize, PartialEq)]
struct Doc {
services: Vec<Service>,
#[allow(dead_code)]
defaults: Common,
}
let doc: Doc = from_str(yaml).unwrap();
assert_eq!(doc.services.len(), 2);
assert_eq!(doc.services[0].common.image, "registry.example.com/api");
assert_eq!(doc.services[0].common.version, "1.0.0");
assert_eq!(doc.services[0].replicas, 3);
assert_eq!(doc.services[1].common.image, "registry.example.com/api");
assert_eq!(doc.services[1].replicas, 5);
}
#[derive(Debug, Deserialize)]
struct SpannedFlatten {
#[allow(dead_code)]
name: Spanned<String>,
#[serde(flatten)]
#[allow(dead_code)]
common: Common,
}
#[test]
fn flatten_pairs_with_spanned_field() {
let yaml = "
name: api
image: registry.example.com/x
version: 1.0.0
";
let doc: SpannedFlatten = from_str(yaml).unwrap();
assert_eq!(doc.name.value, "api");
assert!(doc.name.start.line() >= 1);
assert_eq!(doc.common.image, "registry.example.com/x");
}
#[test]
fn multiple_flattened_fields_compose() {
#[derive(Debug, Deserialize, PartialEq)]
struct Resources {
cpu: String,
memory: String,
}
#[derive(Debug, Deserialize, PartialEq)]
struct Combined {
name: String,
#[serde(flatten)]
common: Common,
#[serde(flatten)]
resources: Resources,
}
let yaml = "
name: api
image: r/api
version: \"1.0.0\"
cpu: 500m
memory: 256Mi
";
let doc: Combined = from_str(yaml).unwrap();
assert_eq!(doc.common.image, "r/api");
assert_eq!(doc.resources.cpu, "500m");
assert_eq!(doc.resources.memory, "256Mi");
}
#[derive(Debug, Deserialize, PartialEq)]
#[serde(untagged)]
enum Resource {
Pod {
containers: Vec<String>,
},
Service {
ports: Vec<u16>,
},
ConfigMap {
data: std::collections::BTreeMap<String, String>,
},
}
#[test]
fn untagged_enum_distinguishes_pod_from_service() {
let pod_yaml = "containers:\n - api\n - worker\n";
let svc_yaml = "ports:\n - 80\n - 443\n";
let cfg_yaml = "data:\n log_level: info\n region: us-west\n";
let pod: Resource = from_str(pod_yaml).unwrap();
let svc: Resource = from_str(svc_yaml).unwrap();
let cfg: Resource = from_str(cfg_yaml).unwrap();
assert!(matches!(pod, Resource::Pod { .. }));
assert!(matches!(svc, Resource::Service { .. }));
assert!(matches!(cfg, Resource::ConfigMap { .. }));
}
#[derive(Debug, Deserialize, PartialEq)]
#[serde(tag = "kind")]
enum K8sResource {
Pod { containers: u32 },
Service { ports: Vec<u16> },
Job { parallelism: u32 },
}
#[test]
fn internally_tagged_enum_round_trips() {
let yaml = "kind: Service\nports:\n - 80\n - 443\n";
let r: K8sResource = from_str(yaml).unwrap();
assert_eq!(
r,
K8sResource::Service {
ports: vec![80, 443]
}
);
}
#[derive(Debug, Deserialize, Serialize, PartialEq)]
#[serde(tag = "kind", content = "spec")]
enum AdjResource {
Pod { containers: u32 },
Service { ports: Vec<u16> },
}
#[test]
fn adjacently_tagged_enum_round_trips() {
let yaml = "kind: Service\nspec:\n ports:\n - 8080\n";
let r: AdjResource = from_str(yaml).unwrap();
assert_eq!(r, AdjResource::Service { ports: vec![8080] });
}
#[test]
fn mixed_enum_strategies_in_one_document() {
#[derive(Debug, Deserialize)]
struct MixedDoc {
worker: Resource,
primary: K8sResource,
secondary: AdjResource,
}
let yaml = "
worker:
containers:
- producer
- consumer
primary:
kind: Job
parallelism: 4
secondary:
kind: Pod
spec:
containers: 2
";
let doc: MixedDoc = from_str(yaml).unwrap();
assert!(matches!(doc.worker, Resource::Pod { .. }));
assert_eq!(doc.primary, K8sResource::Job { parallelism: 4 });
assert_eq!(doc.secondary, AdjResource::Pod { containers: 2 });
}
#[test]
fn untagged_enum_picks_the_first_matching_variant() {
#[derive(Debug, Deserialize, PartialEq)]
#[serde(untagged)]
enum Either {
Left { x: i32, y: i32 },
Right { lat: f64, lon: f64 },
}
let yaml_left = "x: 1\ny: 2\n";
let yaml_right = "lat: 51.5\nlon: -0.1\n";
assert_eq!(
from_str::<Either>(yaml_left).unwrap(),
Either::Left { x: 1, y: 2 }
);
let r: Either = from_str(yaml_right).unwrap();
if let Either::Right { lat, lon } = r {
assert!((lat - 51.5).abs() < 1e-6);
assert!((lon + 0.1).abs() < 1e-6);
} else {
panic!("expected Right variant");
}
}
#[test]
fn flatten_with_value_residue_captures_unknown_keys() {
#[derive(Debug, Deserialize)]
struct Envelope {
name: String,
version: String,
#[serde(flatten)]
extras: std::collections::HashMap<String, Value>,
}
let yaml = "
name: noyalib
version: 0.0.1
license: MIT
authors:
- Sebastien
audit:
owner: platform
";
let env: Envelope = from_str(yaml).unwrap();
assert_eq!(env.name, "noyalib");
assert_eq!(env.version, "0.0.1");
assert!(env.extras.contains_key("license"));
assert!(env.extras.contains_key("authors"));
assert!(env.extras.contains_key("audit"));
}
#[test]
fn anchor_alias_in_typed_struct() {
let yaml = "
default_port: &port 8080
services:
api:
port: *port
worker:
port: *port
";
use std::collections::BTreeMap;
#[derive(Debug, Deserialize)]
struct Doc {
services: BTreeMap<String, Endpoint>,
#[allow(dead_code)]
default_port: u16,
}
#[derive(Debug, Deserialize)]
struct Endpoint {
port: u16,
}
let doc: Doc = from_str(yaml).unwrap();
assert_eq!(doc.services["api"].port, 8080);
assert_eq!(doc.services["worker"].port, 8080);
}