use forma_derive::{Deserialize, Serialize};
use forma_json::{from_str, to_string};
#[test]
fn test_bool() {
assert_eq!(to_string(&true).unwrap(), "true");
assert_eq!(to_string(&false).unwrap(), "false");
assert_eq!(from_str::<bool>("true").unwrap(), true);
assert_eq!(from_str::<bool>("false").unwrap(), false);
}
#[test]
fn test_integers() {
assert_eq!(to_string(&42i32).unwrap(), "42");
assert_eq!(to_string(&-7i64).unwrap(), "-7");
assert_eq!(to_string(&0u8).unwrap(), "0");
assert_eq!(to_string(&255u8).unwrap(), "255");
assert_eq!(from_str::<i32>("42").unwrap(), 42);
assert_eq!(from_str::<i64>("-7").unwrap(), -7);
assert_eq!(from_str::<u64>("12345").unwrap(), 12345);
}
#[test]
fn test_floats() {
assert_eq!(to_string(&3.14f64).unwrap(), "3.14");
let v: f64 = from_str("3.14").unwrap();
assert!((v - 3.14).abs() < f64::EPSILON);
let v: f64 = from_str("1e10").unwrap();
assert!((v - 1e10).abs() < 1.0);
}
#[test]
fn test_float_nan_error() {
assert!(to_string(&f64::NAN).is_err());
assert!(to_string(&f64::INFINITY).is_err());
}
#[test]
fn test_char() {
assert_eq!(to_string(&'A').unwrap(), "\"A\"");
assert_eq!(from_str::<char>("\"A\"").unwrap(), 'A');
}
#[test]
fn test_string() {
assert_eq!(to_string(&"hello").unwrap(), "\"hello\"");
assert_eq!(
to_string(&String::from("world")).unwrap(),
"\"world\""
);
assert_eq!(from_str::<String>("\"hello\"").unwrap(), "hello");
}
#[test]
fn test_string_escapes() {
assert_eq!(to_string(&"a\"b").unwrap(), "\"a\\\"b\"");
assert_eq!(to_string(&"a\\b").unwrap(), "\"a\\\\b\"");
assert_eq!(to_string(&"a\nb").unwrap(), "\"a\\nb\"");
assert_eq!(to_string(&"a\tb").unwrap(), "\"a\\tb\"");
assert_eq!(from_str::<String>("\"a\\\"b\"").unwrap(), "a\"b");
assert_eq!(from_str::<String>("\"a\\\\b\"").unwrap(), "a\\b");
assert_eq!(from_str::<String>("\"a\\nb\"").unwrap(), "a\nb");
assert_eq!(from_str::<String>("\"a\\tb\"").unwrap(), "a\tb");
assert_eq!(from_str::<String>("\"a\\/b\"").unwrap(), "a/b");
}
#[test]
fn test_unicode_escape() {
assert_eq!(from_str::<String>("\"\\u0041\"").unwrap(), "A");
assert_eq!(
from_str::<String>("\"\\uD83D\\uDE00\"").unwrap(),
"\u{1F600}"
);
}
#[test]
fn test_option() {
assert_eq!(to_string(&Some(42)).unwrap(), "42");
assert_eq!(to_string(&None::<i32>).unwrap(), "null");
assert_eq!(from_str::<Option<i32>>("42").unwrap(), Some(42));
assert_eq!(from_str::<Option<i32>>("null").unwrap(), None);
}
#[test]
fn test_unit() {
assert_eq!(to_string(&()).unwrap(), "null");
assert_eq!(from_str::<()>("null").unwrap(), ());
}
#[test]
fn test_vec() {
assert_eq!(to_string(&vec![1, 2, 3]).unwrap(), "[1,2,3]");
assert_eq!(from_str::<Vec<i32>>("[1,2,3]").unwrap(), vec![1, 2, 3]);
assert_eq!(from_str::<Vec<i32>>("[]").unwrap(), Vec::<i32>::new());
}
#[test]
fn test_nested_vec() {
let v = vec![vec![1, 2], vec![3]];
assert_eq!(to_string(&v).unwrap(), "[[1,2],[3]]");
assert_eq!(from_str::<Vec<Vec<i32>>>("[[1,2],[3]]").unwrap(), v);
}
#[test]
fn test_tuple() {
assert_eq!(to_string(&(1, "two", true)).unwrap(), "[1,\"two\",true]");
assert_eq!(
from_str::<(i32, String, bool)>("[1,\"two\",true]").unwrap(),
(1, "two".into(), true)
);
}
#[test]
fn test_btreemap() {
use std::collections::BTreeMap;
let mut m = BTreeMap::new();
m.insert("a".to_string(), 1);
m.insert("b".to_string(), 2);
assert_eq!(to_string(&m).unwrap(), "{\"a\":1,\"b\":2}");
assert_eq!(
from_str::<BTreeMap<String, i32>>("{\"a\":1,\"b\":2}").unwrap(),
m
);
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Point {
x: i32,
y: i32,
}
#[test]
fn test_struct() {
let p = Point { x: 1, y: 2 };
assert_eq!(to_string(&p).unwrap(), "{\"x\":1,\"y\":2}");
assert_eq!(
from_str::<Point>("{\"x\":1,\"y\":2}").unwrap(),
p
);
}
#[test]
fn test_struct_whitespace() {
let p: Point = from_str(" { \"x\" : 1 , \"y\" : 2 } ").unwrap();
assert_eq!(p, Point { x: 1, y: 2 });
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Line {
start: Point,
end: Point,
}
#[test]
fn test_nested_struct() {
let line = Line {
start: Point { x: 0, y: 0 },
end: Point { x: 10, y: 20 },
};
let json = to_string(&line).unwrap();
assert_eq!(
json,
"{\"start\":{\"x\":0,\"y\":0},\"end\":{\"x\":10,\"y\":20}}"
);
assert_eq!(from_str::<Line>(&json).unwrap(), line);
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
enum Shape {
Circle { radius: f64 },
Rectangle { width: f64, height: f64 },
Point,
}
#[test]
fn test_enum_struct_variant() {
let s = Shape::Circle { radius: 5.0 };
let json = to_string(&s).unwrap();
assert_eq!(json, "{\"Circle\":{\"radius\":5}}");
assert_eq!(from_str::<Shape>(&json).unwrap(), s);
}
#[test]
fn test_enum_unit_variant() {
let s = Shape::Point;
assert_eq!(to_string(&s).unwrap(), "\"Point\"");
assert_eq!(from_str::<Shape>("\"Point\"").unwrap(), s);
}
#[test]
fn test_enum_struct_variant_multi() {
let s = Shape::Rectangle {
width: 3.0,
height: 4.0,
};
let json = to_string(&s).unwrap();
assert_eq!(json, "{\"Rectangle\":{\"width\":3,\"height\":4}}");
assert_eq!(from_str::<Shape>(&json).unwrap(), s);
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
enum Value {
Int(i64),
Text(String),
Flag(bool),
}
#[test]
fn test_enum_newtype_variant() {
let v = Value::Int(42);
let json = to_string(&v).unwrap();
assert_eq!(json, "{\"Int\":42}");
assert_eq!(from_str::<Value>(&json).unwrap(), v);
let v = Value::Text("hello".into());
let json = to_string(&v).unwrap();
assert_eq!(json, "{\"Text\":\"hello\"}");
assert_eq!(from_str::<Value>(&json).unwrap(), v);
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
enum Pair {
Ints(i32, i32),
Strs(String, String),
}
#[test]
fn test_enum_tuple_variant() {
let p = Pair::Ints(1, 2);
let json = to_string(&p).unwrap();
assert_eq!(json, "{\"Ints\":[1,2]}");
assert_eq!(from_str::<Pair>(&json).unwrap(), p);
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[forma(rename_all = "camelCase")]
struct Config {
max_retries: u32,
timeout_ms: u64,
}
#[test]
fn test_rename_all() {
let c = Config {
max_retries: 3,
timeout_ms: 5000,
};
let json = to_string(&c).unwrap();
assert_eq!(json, "{\"maxRetries\":3,\"timeoutMs\":5000}");
assert_eq!(from_str::<Config>(&json).unwrap(), c);
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct WithSkip {
visible: i32,
#[forma(skip)]
hidden: i32,
}
#[test]
fn test_skip() {
let v = WithSkip {
visible: 42,
hidden: 99,
};
let json = to_string(&v).unwrap();
assert_eq!(json, "{\"visible\":42}");
let de: WithSkip = from_str(&json).unwrap();
assert_eq!(de.visible, 42);
assert_eq!(de.hidden, 0); }
fn is_zero(v: &u32) -> bool {
*v == 0
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Sparse {
#[forma(skip_serializing_if = "is_zero")]
#[forma(default)]
count: u32,
name: String,
}
#[test]
fn test_skip_serializing_if() {
let s = Sparse {
count: 0,
name: "test".into(),
};
assert_eq!(to_string(&s).unwrap(), "{\"name\":\"test\"}");
let s = Sparse {
count: 5,
name: "test".into(),
};
assert_eq!(to_string(&s).unwrap(), "{\"count\":5,\"name\":\"test\"}");
}
#[test]
fn test_default_missing_field() {
let s: Sparse = from_str("{\"name\":\"test\"}").unwrap();
assert_eq!(s.count, 0);
assert_eq!(s.name, "test");
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct MaybeNamed {
name: Option<String>,
value: i32,
}
#[test]
fn test_option_field() {
let v = MaybeNamed {
name: Some("hello".into()),
value: 42,
};
let json = to_string(&v).unwrap();
assert_eq!(json, "{\"name\":\"hello\",\"value\":42}");
assert_eq!(from_str::<MaybeNamed>(&json).unwrap(), v);
let v = MaybeNamed {
name: None,
value: 42,
};
let json = to_string(&v).unwrap();
assert_eq!(json, "{\"name\":null,\"value\":42}");
assert_eq!(from_str::<MaybeNamed>(&json).unwrap(), v);
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[forma(transparent)]
struct Wrapper(i32);
#[test]
fn test_transparent() {
assert_eq!(to_string(&Wrapper(42)).unwrap(), "42");
assert_eq!(from_str::<Wrapper>("42").unwrap(), Wrapper(42));
}
#[test]
fn test_bytes_as_array() {
let bytes: Vec<u8> = vec![1, 2, 3];
assert_eq!(to_string(&bytes).unwrap(), "[1,2,3]");
assert_eq!(from_str::<Vec<u8>>("[1,2,3]").unwrap(), bytes);
}
#[test]
fn test_hashmap_string_keys() {
use std::collections::HashMap;
let mut m = HashMap::new();
m.insert("key".to_string(), 42);
let json = to_string(&m).unwrap();
assert_eq!(json, "{\"key\":42}");
assert_eq!(
from_str::<HashMap<String, i32>>(&json).unwrap(),
m
);
}
#[test]
fn test_whitespace() {
let v: Vec<i32> = from_str(" [ 1 , 2 , 3 ] ").unwrap();
assert_eq!(v, vec![1, 2, 3]);
let p: Point = from_str("\n{\n \"x\": 1,\n \"y\": 2\n}\n").unwrap();
assert_eq!(p, Point { x: 1, y: 2 });
}
#[test]
fn test_trailing_data_error() {
assert!(from_str::<i32>("42 extra").is_err());
}
#[test]
fn test_eof_error() {
assert!(from_str::<i32>("").is_err());
assert!(from_str::<String>("\"unclosed").is_err());
}
#[test]
fn test_syntax_error() {
assert!(from_str::<Vec<i32>>("[1, 2,]").is_err());
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Person {
name: String,
age: u32,
emails: Vec<String>,
address: Option<Address>,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Address {
street: String,
city: String,
zip: String,
}
#[test]
fn test_complex_roundtrip() {
let person = Person {
name: "Alice".into(),
age: 30,
emails: vec!["alice@example.com".into(), "a@b.c".into()],
address: Some(Address {
street: "123 Main St".into(),
city: "Springfield".into(),
zip: "12345".into(),
}),
};
let json = to_string(&person).unwrap();
let back: Person = from_str(&json).unwrap();
assert_eq!(back, person);
}
#[test]
fn test_complex_no_address() {
let person = Person {
name: "Bob".into(),
age: 25,
emails: vec![],
address: None,
};
let json = to_string(&person).unwrap();
let back: Person = from_str(&json).unwrap();
assert_eq!(back, person);
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct AliasTest {
#[forma(alias = "firstName")]
name: String,
}
#[test]
fn test_alias() {
let v: AliasTest = from_str("{\"firstName\":\"Alice\"}").unwrap();
assert_eq!(v.name, "Alice");
let v: AliasTest = from_str("{\"name\":\"Bob\"}").unwrap();
assert_eq!(v.name, "Bob");
}
#[test]
fn test_integer_map_keys() {
use std::collections::BTreeMap;
let mut m = BTreeMap::new();
m.insert(1u32, "one".to_string());
m.insert(2, "two".to_string());
let json = to_string(&m).unwrap();
assert_eq!(json, "{\"1\":\"one\",\"2\":\"two\"}");
}