#![allow(missing_docs)]
use noyalib::{Flattened, Value, from_str, to_string};
#[derive(Debug, serde::Deserialize, serde::Serialize, PartialEq, Clone)]
struct Inner {
port: u16,
host: String,
}
#[derive(Debug, serde::Deserialize, serde::Serialize, PartialEq, Clone)]
struct Config {
name: String,
inner: Flattened<Inner>,
}
const YAML: &str = "\
name: noyalib
inner:
port: 8080
host: localhost
extra: not-in-Inner
";
#[test]
fn typed_view_extracts_declared_fields() {
let cfg: Config = from_str(YAML).unwrap();
assert_eq!(cfg.inner.value.port, 8080);
assert_eq!(cfg.inner.value.host, "localhost");
}
#[test]
fn raw_view_preserves_undeclared_keys() {
let cfg: Config = from_str(YAML).unwrap();
let raw = match &cfg.inner.raw {
Value::Mapping(m) => m,
other => panic!("expected mapping, got {other:?}"),
};
assert!(raw.contains_key("port"));
assert!(raw.contains_key("host"));
assert!(raw.contains_key("extra"));
assert_eq!(raw["extra"].as_str(), Some("not-in-Inner"));
}
#[test]
fn deref_provides_typed_view() {
let cfg: Config = from_str(YAML).unwrap();
assert_eq!(cfg.inner.port, 8080);
assert_eq!(cfg.inner.host, "localhost");
}
#[test]
fn round_trip_through_serialize_then_deserialize() {
let cfg: Config = from_str(YAML).unwrap();
let yaml = to_string(&cfg).unwrap();
assert!(yaml.contains("name: noyalib"));
assert!(yaml.contains("port: 8080"));
assert!(yaml.contains("host: localhost"));
assert!(!yaml.contains("extra"));
}
#[test]
fn into_value_consumes_to_inner_t() {
let cfg: Config = from_str(YAML).unwrap();
let inner: Inner = cfg.inner.into_value();
assert_eq!(inner.port, 8080);
}
#[test]
fn flatten_attribute_works_with_wrapper() {
#[derive(Debug, serde::Deserialize)]
struct Outer {
version: u8,
#[serde(flatten)]
body: Flattened<Inner>,
}
let yaml = "\
version: 1
port: 8080
host: localhost
custom_field: extra
debug: true
";
let outer: Outer = from_str(yaml).unwrap();
assert_eq!(outer.version, 1);
assert_eq!(outer.body.port, 8080);
assert_eq!(outer.body.host, "localhost");
let raw = match &outer.body.raw {
Value::Mapping(m) => m,
other => panic!("expected mapping, got {other:?}"),
};
assert!(!raw.contains_key("version"));
assert!(raw.contains_key("port"));
assert!(raw.contains_key("host"));
assert!(raw.contains_key("custom_field"));
assert!(raw.contains_key("debug"));
}
#[test]
fn flattened_inside_sequence() {
#[derive(Debug, serde::Deserialize)]
#[allow(dead_code)]
struct Item {
id: u32,
details: Flattened<Inner>,
}
let yaml = "\
- id: 1
details:
port: 1
host: a
note: first
- id: 2
details:
port: 2
host: b
note: second
";
let items: Vec<Item> = from_str(yaml).unwrap();
assert_eq!(items.len(), 2);
assert_eq!(items[0].details.port, 1);
assert_eq!(items[1].details.host, "b");
let r0 = match &items[0].details.raw {
Value::Mapping(m) => m,
_ => panic!(),
};
assert_eq!(r0["note"].as_str(), Some("first"));
}
#[test]
fn flattened_with_failing_typed_projection_errors() {
let yaml = "\
name: app
inner:
port: not-a-number
host: localhost
";
let res: Result<Config, _> = from_str(yaml);
assert!(res.is_err());
}
#[test]
fn flattened_with_simple_scalar_target() {
let v: Flattened<u16> = from_str("8080").unwrap();
assert_eq!(*v, 8080);
assert_eq!(v.raw.as_i64(), Some(8080));
}
#[test]
fn flattened_constructor_helper() {
let v = Flattened::new(42_u16, Value::from(42_i64));
assert_eq!(v.value, 42);
assert_eq!(v.raw.as_i64(), Some(42));
}
#[test]
fn flattened_clone_eq() {
let cfg: Config = from_str(YAML).unwrap();
let clone = cfg.inner.clone();
assert_eq!(clone, cfg.inner);
}
#[test]
fn flattened_as_value_and_as_raw_borrows() {
let f = Flattened::new(42_u16, Value::from(42_i64));
assert_eq!(f.as_value(), &42);
assert_eq!(f.as_raw().as_i64(), Some(42));
assert_eq!(f.value, 42);
}
#[test]
fn flattened_into_value_consumes_to_typed() {
let f = Flattened::new("hello".to_string(), Value::from("hello"));
let typed: String = f.into_value();
assert_eq!(typed, "hello");
}
#[test]
fn flattened_serializes_as_typed_view_only() {
#[derive(serde::Serialize, serde::Deserialize, Debug)]
struct Inner {
port: u16,
}
let raw: Value = from_str("port: 8080\n").unwrap();
let f = Flattened::new(Inner { port: 8080 }, raw);
let yaml = to_string(&f).unwrap();
assert_eq!(yaml.trim(), "port: 8080");
}
#[test]
fn flattened_deserialize_error_propagates_from_inner_type() {
let res: Result<Flattened<u16>, _> = from_str("-1");
assert!(
res.is_err(),
"negative integer must not coerce into u16 via Flattened",
);
}
#[test]
fn flattened_deref_resolves_through_to_inner() {
let raw: Value = from_str("[1, 2, 3]").unwrap();
let f = Flattened::new(vec![1_i32, 2, 3], raw);
assert_eq!(f.len(), 3);
assert_eq!(f.first(), Some(&1));
}