fieldx 0.2.3

Procedural macro for constructing structs with lazily initialized fields, builder pattern, and serde support with a focus on declarative syntax.
Documentation
#![cfg(feature = "serde")]
use fieldx::fxstruct;
use serde::Deserialize;
use serde::Serialize;

#[derive(Default, Clone, Debug, PartialEq)]
struct Bar {
    v: String,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
struct Baz {
    cnt: u32,
}

#[fxstruct(
    builder(attributes_impl(allow(dead_code))),
    serde(default("FooDup::default"), shadow_name("FooDup"))
)]
#[derive(Clone, Debug)]
struct Foo {
    #[fieldx(lazy, serde(off))]
    bar: Bar,

    #[fieldx(lazy)]
    baz: Baz,

    #[fieldx(
        lazy,
        get(copy),
        clearer,
        default(Self::init_count()),
        serde(deserialize(off), forward_attrs(a1, b2))
    )]
    count: i32,

    #[fieldx(lazy, get(copy), serde(default(0.0f64)))]
    pi: f64,

    #[fieldx(lazy, get(copy), serde)]
    e: f64,

    #[fieldx(optional, set, get(copy))]
    opt: u64,

    #[fieldx(default(-1122.3344))]
    simple: f64,

    // Of the two defaults serde wins when deserializing.
    #[fieldx(serde( default(-987.654) ), default(12.34), rename("sssimple"))]
    simple2: f64,

    #[fieldx(inner_mut, get)]
    modifiable: String,
}

impl Foo {
    fn build_bar(&self) -> Bar {
        Bar {
            v: "from lazy".to_string(),
        }
    }

    fn build_baz(&self) -> Baz {
        Baz { cnt: 123 }
    }

    fn build_count(&self) -> i32 {
        1
    }

    fn build_pi(&self) -> f64 {
        std::f64::consts::PI
    }

    fn build_e(&self) -> f64 {
        std::f64::consts::E
    }

    fn init_count() -> i32 {
        13
    }
}

#[test]
fn basics() {
    let mut foo = Foo::builder()
        .simple(666.13)
        .modifiable("from builder".to_string())
        .build()
        .expect("Foo builder failed");

    let json = serde_json::to_string(&foo).expect("Foo serialization failure");

    assert_eq!(
        json,
        r#"{"baz":{"cnt":123},"count":13,"pi":3.141592653589793,"e":2.718281828459045,"opt":null,"simple":666.13,"sssimple":12.34,"modifiable":"from builder"}"#,
        "serialized"
    );

    foo.set_opt(12);
    foo.clear_count();

    let json = serde_json::to_string(&foo).expect("Foo serialization failure");

    assert_eq!(
        json,
        r#"{"baz":{"cnt":123},"count":1,"pi":3.141592653589793,"e":2.718281828459045,"opt":12,"simple":666.13,"sssimple":12.34,"modifiable":"from builder"}"#,
        "serialized after changes"
    );

    let json_src = r#"{"baz":{"cnt":9876},"count":112233,"pi":3.141,"opt":null,"simple":-13.666,"sssimple":999.111,"modifiable":"from deserialize"}"#;
    let foo_de = serde_json::from_str::<Foo>(json_src).expect("Foo deserialization failure");

    assert_eq!(
        foo_de.bar(),
        &Bar {
            v: "from lazy".to_string(),
        },
        "bar is not deserializable"
    );
    assert_eq!(
        foo_de.baz(),
        &Baz { cnt: 9876 },
        "a lazy field with struct got deserialized"
    );
    assert_eq!(
        foo_de.count(),
        13,
        "a lazy non-deserializable u32 field gets its default after deserialization"
    );
    #[allow(clippy::approx_constant)]
    let expected = 3.141;
    assert_eq!(foo_de.pi(), expected, "a lazy f64 field – deserialized");
    assert_eq!(
        foo_de.opt(),
        None,
        "an optional u64 field deserializes to None from JSON's 'null'"
    );
    assert_eq!(foo_de.simple, -13.666, "a plain field – deserialized");
    assert_eq!(foo_de.simple2, 999.111, "a renamed plain field – deserialized");
    assert_eq!(*foo_de.modifiable(), "from deserialize".to_string());

    let json_src = r#"{"baz":{"cnt":9876},"opt":31415926}"#;
    let foo_de = serde_json::from_str::<Foo>(json_src).expect("Foo deserialization failure");

    // eprintln!("{:#?}", foo_de);

    assert_eq!(foo_de.opt(), Some(31415926), "an optional u64 field - deserialized");
    assert_eq!(foo_de.pi(), 0f64, "a missing lazy field with serde default");
    assert_eq!(
        foo_de.e(),
        std::f64::consts::E,
        "a missing lazy field gets its lazy value"
    );
    assert_eq!(
        foo_de.simple, -1122.3344,
        "a plain field gets its default after deserialization if missing from JSON"
    );
    assert_eq!(
        foo_de.simple2, -987.654,
        "a plain field gets its serde default after deserialization if missing from JSON"
    );
}