staged-builder 0.2.0

A proc macro which generates staged builders for types.
Documentation
use staged_builder::{staged_builder, Validate};
use std::collections::{HashMap, HashSet};
use std::fmt::Display;

#[derive(PartialEq, Debug)]
#[staged_builder]
struct Foo {
    required: bool,
    #[builder(into)]
    required2: String,
    #[builder(default, into)]
    normal_default: String,
    #[builder(default = 42)]
    custom_default: i32,
}

#[test]
fn basic() {
    let actual = Foo::builder().required(true).required2("a").build();
    let expected = Foo {
        required: true,
        required2: "a".to_string(),
        normal_default: "".to_string(),
        custom_default: 42,
    };
    assert_eq!(actual, expected);
}

#[staged_builder]
#[builder(validate)]
struct Validated {
    even: u32,
}

impl Validate for Validated {
    type Error = &'static str;

    fn validate(&self) -> Result<(), Self::Error> {
        if self.even % 2 == 0 {
            Ok(())
        } else {
            Err("is odd")
        }
    }
}

#[test]
fn validate() {
    Validated::builder().even(0).build().unwrap();
    Validated::builder().even(1).build().err().unwrap();
}

#[derive(PartialEq, Debug)]
#[staged_builder]
struct Collections {
    #[builder(list(item(type = u32)))]
    list: Vec<u32>,
    #[builder(set(item(type = &'static str)))]
    set: HashSet<&'static str>,
    #[builder(map(key(type = i32), value(type = bool)))]
    map: HashMap<i32, bool>,
}

#[test]
fn collections() {
    let actual = Collections::builder()
        .push_list(1)
        .push_list(2)
        .insert_set("hi")
        .insert_set("there")
        .insert_map(1, true)
        .insert_map(2, false)
        .build();
    let expected = Collections {
        list: vec![1, 2],
        set: HashSet::from(["hi", "there"]),
        map: HashMap::from([(1, true), (2, false)]),
    };
    assert_eq!(actual, expected);

    let actual = Collections::builder()
        .push_list(0)
        .list([1, 2])
        .set(["hi", "there"])
        .map([(1, true), (2, false)])
        .build();
    assert_eq!(actual, expected);

    let actual = Collections::builder()
        .push_list(1)
        .extend_list([2])
        .insert_set("hi")
        .extend_set(["there"])
        .insert_map(1, true)
        .extend_map([(2, false)])
        .build();
    assert_eq!(actual, expected);
}

#[derive(PartialEq, Debug)]
#[staged_builder]
struct CollectionsInto {
    #[builder(list(item(type = String, into)))]
    list: Vec<String>,
    #[builder(map(key(type = String, into), value(type = Option<i32>, into)))]
    map: HashMap<String, Option<i32>>,
}

#[test]
fn collections_into() {
    let actual = CollectionsInto::builder()
        .push_list("hi")
        .push_list("there")
        .insert_map("foo", 1)
        .insert_map("bar", None)
        .build();
    let expected = CollectionsInto {
        list: vec!["hi".to_string(), "there".to_string()],
        map: HashMap::from([("foo".to_string(), Some(1)), ("bar".to_string(), None)]),
    };
    assert_eq!(actual, expected);

    let actual = CollectionsInto::builder()
        .list(["hi", "there"])
        .map([("foo", Some(1)), ("bar", None)])
        .build();
    assert_eq!(actual, expected);

    let actual = CollectionsInto::builder()
        .push_list("hi")
        .extend_list(["there"])
        .insert_map("foo", 1)
        .extend_map([("bar", None)])
        .build();
    assert_eq!(actual, expected);
}

#[derive(PartialEq, Debug)]
#[staged_builder]
struct Custom {
    #[builder(custom(type = impl Display, convert = to_string))]
    string: String,
    #[builder(list(item(custom(type = impl Display, convert = to_string))))]
    list: Vec<String>,
}

fn to_string(value: impl Display) -> String {
    value.to_string()
}

#[test]
fn custom() {
    let actual = Custom::builder().string(42).push_list(true).build();
    let expected = Custom {
        string: "42".to_string(),
        list: vec!["true".to_string()],
    };
    assert_eq!(actual, expected);
}

#[staged_builder]
#[builder(mod = my_custom_mod)]
struct CustomMod {
    _foo: i32,
}

#[test]
fn custom_mod() {
    CustomMod::builder()._foo(1).build();
    my_custom_mod::Builder::default()._foo(1).build();
}

#[derive(PartialEq, Debug)]
#[staged_builder]
struct ClosureConvert {
    #[builder(custom(type = impl Display, convert = |s| s.to_string()))]
    single: String,
    #[builder(list(item(custom(type = impl Display, convert = |s| s.to_string()))))]
    list: Vec<String>,
}

#[test]
fn closure_convert() {
    let actual = ClosureConvert::builder().single(true).push_list(15).build();
    let expected = ClosureConvert {
        single: "true".to_string(),
        list: vec!["15".to_string()],
    };
    assert_eq!(actual, expected);
}

mod inline {
    use staged_builder::staged_builder;

    #[derive(PartialEq, Debug)]
    #[staged_builder]
    #[builder(inline)]
    struct Inline {
        a: i32,
    }

    #[test]
    fn inline() {
        let builder: Builder<AStage> = Inline::builder();
        let stage: Builder<Complete> = builder.a(1);
        let actual: Inline = stage.build();
        let expected = Inline { a: 1 };
        assert_eq!(actual, expected);
    }
}

#[derive(PartialEq, Debug)]
#[staged_builder]
#[builder(builder = MyBuilder, complete = MyComplete)]
struct CustomNames {
    #[builder(stage = MyAStage)]
    a: i32,
}

#[test]
fn custom_names() {
    let builder: custom_names::MyBuilder<custom_names::MyAStage> = CustomNames::builder();
    let stage: custom_names::MyBuilder<custom_names::MyComplete> = builder.a(1);
    let actual = stage.build();
    let expected = CustomNames { a: 1 };
    assert_eq!(actual, expected);
}

#[derive(PartialEq, Debug)]
#[staged_builder]
#[builder(update)]
struct Update {
    a: i32,
    #[builder(into)]
    b: String,
}

#[test]
fn update() {
    let v = Update::builder().a(1).b("hello").build();
    let actual = update::Builder::from(v).a(2).b("world").build();
    let expected = Update {
        a: 2,
        b: "world".to_string(),
    };
    assert_eq!(actual, expected);
}