forma_json 0.1.0

JSON serialization and deserialization for forma_core.
Documentation
use forma_core::de::Deserialize as _;
use forma_derive::{Deserialize, Serialize};
use forma_json::{from_str, to_string};

// ── #[forma(into = "...")] ──────────────────────────────────────────

#[derive(Debug, Clone, PartialEq, Serialize)]
#[forma(into = "String")]
struct Email(String);

impl From<Email> for String {
    fn from(e: Email) -> String {
        e.0
    }
}

#[test]
fn test_into_serialize() {
    let email = Email("test@example.com".into());
    assert_eq!(to_string(&email).unwrap(), "\"test@example.com\"");
}

// ── #[forma(from = "...")] ──────────────────────────────────────────

#[derive(Debug, PartialEq, Deserialize)]
#[forma(from = "String")]
struct Username(String);

impl From<String> for Username {
    fn from(s: String) -> Self {
        Username(s.to_lowercase())
    }
}

#[test]
fn test_from_deserialize() {
    let u: Username = from_str("\"ADMIN\"").unwrap();
    assert_eq!(u.0, "admin");
}

// ── #[forma(try_from = "...")] ──────────────────────────────────────

#[derive(Debug, PartialEq, Deserialize)]
#[forma(try_from = "u64")]
struct Port(u16);

impl TryFrom<u64> for Port {
    type Error = String;
    fn try_from(v: u64) -> Result<Self, String> {
        if v > 65535 {
            Err(format!("port {v} out of range"))
        } else {
            Ok(Port(v as u16))
        }
    }
}

#[test]
fn test_try_from_deserialize() {
    let p: Port = from_str("8080").unwrap();
    assert_eq!(p.0, 8080);
}

#[test]
fn test_try_from_deserialize_error() {
    let err = from_str::<Port>("99999").unwrap_err();
    assert!(err.to_string().contains("out of range"));
}

// ── #[forma(deny_unknown_fields)] ───────────────────────────────────

#[derive(Debug, PartialEq, Deserialize)]
#[forma(deny_unknown_fields)]
struct Strict {
    x: i32,
    y: i32,
}

#[test]
fn test_deny_unknown_fields() {
    let s: Strict = from_str("{\"x\":1,\"y\":2}").unwrap();
    assert_eq!(s, Strict { x: 1, y: 2 });
}

#[test]
fn test_deny_unknown_fields_error() {
    let err = from_str::<Strict>("{\"x\":1,\"y\":2,\"z\":3}").unwrap_err();
    let msg = err.to_string();
    assert!(msg.contains("unknown field"), "got: {msg}");
}

// ── #[forma(bound = "...")] ─────────────────────────────────────────

use std::fmt;

trait Printable: fmt::Display {}
impl Printable for String {}

#[derive(Debug, PartialEq, Serialize)]
#[forma(bound = "T: fmt::Display")]
struct Labeled<T> {
    #[forma(serialize_with = "serialize_display")]
    label: T,
}

fn serialize_display<T: fmt::Display, S: forma_core::ser::Serializer>(
    value: &T,
    serializer: S,
) -> Result<S::Ok, S::Error> {
    serializer.serialize_str(&value.to_string())
}

#[test]
fn test_bound() {
    let l = Labeled {
        label: 42i32,
    };
    assert_eq!(to_string(&l).unwrap(), "{\"label\":\"42\"}");
}

// ── #[forma(default)] on container ──────────────────────────────────

#[derive(Debug, PartialEq, Deserialize, Default)]
#[forma(default)]
struct Settings {
    width: u32,
    height: u32,
    fullscreen: bool,
}

#[test]
fn test_container_default_partial() {
    let s: Settings = from_str("{\"width\":1920}").unwrap();
    assert_eq!(s.width, 1920);
    assert_eq!(s.height, 0);
    assert_eq!(s.fullscreen, false);
}

#[test]
fn test_container_default_empty() {
    let s: Settings = from_str("{}").unwrap();
    assert_eq!(s, Settings::default());
}

// ── #[forma(serialize_with)] / #[forma(deserialize_with)] ───────────

fn serialize_uppercase<S: forma_core::ser::Serializer>(
    value: &str,
    serializer: S,
) -> Result<S::Ok, S::Error> {
    serializer.serialize_str(&value.to_uppercase())
}

fn deserialize_lowercase<'de, D: forma_core::de::Deserializer<'de>>(
    deserializer: D,
) -> Result<String, D::Error> {
    let s = String::deserialize(deserializer)?;
    Ok(s.to_lowercase())
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct NormalizedName {
    #[forma(serialize_with = "serialize_uppercase")]
    #[forma(deserialize_with = "deserialize_lowercase")]
    name: String,
}

#[test]
fn test_serialize_with() {
    let n = NormalizedName {
        name: "Alice".into(),
    };
    assert_eq!(to_string(&n).unwrap(), "{\"name\":\"ALICE\"}");
}

#[test]
fn test_deserialize_with() {
    let n: NormalizedName = from_str("{\"name\":\"ALICE\"}").unwrap();
    assert_eq!(n.name, "alice");
}

// ── Generics ────────────────────────────────────────────────────────

#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Wrapper<T> {
    inner: T,
}

#[test]
fn test_generics() {
    let w = Wrapper { inner: 42i32 };
    let json = to_string(&w).unwrap();
    assert_eq!(json, "{\"inner\":42}");
    let back: Wrapper<i32> = from_str(&json).unwrap();
    assert_eq!(back, w);
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Pair<A, B> {
    first: A,
    second: B,
}

#[test]
fn test_multi_generic() {
    let p = Pair {
        first: "hello".to_string(),
        second: 42i32,
    };
    let json = to_string(&p).unwrap();
    let back: Pair<String, i32> = from_str(&json).unwrap();
    assert_eq!(back, p);
}

// ── Vec of enums ────────────────────────────────────────────────────

#[derive(Debug, PartialEq, Serialize, Deserialize)]
enum Animal {
    Dog { name: String },
    Cat { name: String, indoor: bool },
}

#[test]
fn test_vec_of_enums() {
    let animals = vec![
        Animal::Dog { name: "Rex".into() },
        Animal::Cat {
            name: "Whiskers".into(),
            indoor: true,
        },
    ];
    let json = to_string(&animals).unwrap();
    let back: Vec<Animal> = from_str(&json).unwrap();
    assert_eq!(back, animals);
}

// ── Nested options ──────────────────────────────────────────────────

#[test]
fn test_nested_option() {
    let v: Option<Option<i32>> = Some(Some(42));
    assert_eq!(to_string(&v).unwrap(), "42");
    let back: Option<Option<i32>> = from_str("42").unwrap();
    assert_eq!(back, v);

    let v: Option<Option<i32>> = Some(None);
    assert_eq!(to_string(&v).unwrap(), "null");

    let v: Option<Option<i32>> = None;
    assert_eq!(to_string(&v).unwrap(), "null");
}

// ── Large numeric ───────────────────────────────────────────────────

#[test]
fn test_i128() {
    let v: i128 = i128::MAX;
    let json = to_string(&v).unwrap();
    let back: i128 = from_str(&json).unwrap();
    assert_eq!(back, v);
}

#[test]
fn test_u128() {
    let v: u128 = u128::MAX;
    let json = to_string(&v).unwrap();
    let back: u128 = from_str(&json).unwrap();
    assert_eq!(back, v);
}