mod de;
mod error;
mod ser;
pub use de::from_str;
pub use error::{Error, Result};
pub use ser::to_string;
#[cfg(test)]
mod tests {
use serde::{Deserialize, Serialize};
use crate::{Error, from_str, to_string};
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Record {
foo: String,
baz: u64,
cool: bool,
message: String,
}
#[test]
fn basic_deserialize() {
let line = r#"foo=bar baz=42 cool=true message="hello world""#;
let got: Record = from_str(line).unwrap();
assert_eq!(
got,
Record {
foo: "bar".into(),
baz: 42,
cool: true,
message: "hello world".into(),
}
);
}
#[test]
fn round_trip_is_canonical() {
let record = Record {
foo: "bar".into(),
baz: 42,
cool: true,
message: "hello world".into(),
};
let line = to_string(&record).unwrap();
assert_eq!(line, r#"foo=bar baz=42 cool=true message="hello world""#);
assert_eq!(from_str::<Record>(&line).unwrap(), record);
}
#[test]
fn type_decides_interpretation() {
#[derive(Deserialize, PartialEq, Debug)]
struct AsString {
x: String,
}
#[derive(Deserialize, PartialEq, Debug)]
struct AsInt {
x: u64,
}
assert_eq!(from_str::<AsString>("x=42").unwrap().x, "42");
assert_eq!(from_str::<AsInt>("x=42").unwrap().x, 42);
assert_eq!(from_str::<AsInt>(r#"x="42""#).unwrap().x, 42);
assert_eq!(from_str::<AsString>(r#"x="42""#).unwrap().x, "42");
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct WithOption {
a: u32,
b: Option<String>,
}
#[test]
fn option_present_and_missing() {
assert_eq!(
from_str::<WithOption>("a=1 b=hi").unwrap(),
WithOption { a: 1, b: Some("hi".into()) }
);
assert_eq!(
from_str::<WithOption>("a=1").unwrap(),
WithOption { a: 1, b: None }
);
}
#[test]
fn option_serialization_skips_none() {
assert_eq!(to_string(&WithOption { a: 1, b: None }).unwrap(), "a=1");
assert_eq!(
to_string(&WithOption { a: 1, b: Some("x".into()) }).unwrap(),
"a=1 b=x"
);
}
#[test]
fn unknown_keys_are_ignored() {
#[derive(Deserialize, PartialEq, Debug)]
struct OnlyA {
a: u32,
}
let got: OnlyA = from_str(r#"a=1 extra=zzz junk="a b""#).unwrap();
assert_eq!(got, OnlyA { a: 1 });
}
#[test]
fn quoting_and_escaping_round_trip() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct M {
m: String,
}
let input = r#"m="he said \"hi\" \\ ok""#;
let got: M = from_str(input).unwrap();
assert_eq!(got.m, r#"he said "hi" \ ok"#);
assert_eq!(to_string(&got).unwrap(), input);
}
#[test]
fn empty_value_round_trips() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct E {
a: String,
}
assert_eq!(from_str::<E>("a=").unwrap().a, "");
assert_eq!(from_str::<E>(r#"a="""#).unwrap().a, "");
assert_eq!(to_string(&E { a: String::new() }).unwrap(), r#"a="""#);
}
#[test]
fn char_and_numbers() {
#[derive(Deserialize, PartialEq, Debug)]
struct C {
c: char,
n: i32,
f: f64,
}
let got: C = from_str("c=x n=-5 f=2.5").unwrap();
assert_eq!(got, C { c: 'x', n: -5, f: 2.5 });
}
#[test]
fn error_cases() {
#[derive(Deserialize, Debug)]
struct N {
#[allow(dead_code)]
n: u64,
}
#[derive(Deserialize, Debug)]
struct B {
#[allow(dead_code)]
cool: bool,
}
#[derive(Deserialize, Debug)]
struct Ch {
#[allow(dead_code)]
c: char,
}
assert_eq!(from_str::<N>("n=abc").unwrap_err(), Error::ParseInt("abc".into()));
assert_eq!(
from_str::<B>("cool=yes").unwrap_err(),
Error::ParseBool("yes".into())
);
assert_eq!(
from_str::<Ch>("c=xy").unwrap_err(),
Error::ParseChar("xy".into())
);
assert_eq!(from_str::<N>("n").unwrap_err(), Error::ExpectedEquals);
}
#[test]
fn top_level_guard() {
assert_eq!(from_str::<u64>("a=1").unwrap_err(), Error::TopLevelNotMap);
assert_eq!(to_string(&5u64).unwrap_err(), Error::TopLevelNotMap);
}
}