borrowize 0.2.0

Derive borrowed view structs from owned Rust structs.
Documentation
use borrowize::View;
use std::path::Path;

#[derive(Debug, PartialEq, Eq)]
struct Tag(&'static str);

#[derive(View)]
struct User {
    id: u64,
    name: String,
    tags: Vec<Tag>,
}

#[test]
fn creates_default_borrowed_view() {
    let user = User {
        id: 7,
        name: "Ada".to_owned(),
        tags: vec![Tag("admin"), Tag("editor")],
    };

    let view = user.view();

    assert_eq!(*view.id, 7);
    assert_eq!(view.name, "Ada");
    assert_eq!(view.tags, [Tag("admin"), Tag("editor")]);
}

#[test]
fn manual_default_view_matches_automatic_view() {
    let user = User {
        id: 8,
        name: "Grace".to_owned(),
        tags: vec![Tag("operator")],
    };
    let automatic = user.view();
    let manual = UserView {
        id: &user.id,
        name: user.name.as_str(),
        tags: user.tags.as_slice(),
    };

    assert_user_views_eq(&manual, &automatic);
}

fn assert_user_views_eq(left: &UserView<'_>, right: &UserView<'_>) {
    assert_eq!(left.id, right.id);
    assert_eq!(left.name, right.name);
    assert_eq!(left.tags, right.tags);
}

#[derive(View)]
struct CommonTypes<'a> {
    label: &'a str,
    bytes: [u8; 3],
    maybe_name: Option<String>,
    maybe_id: Option<u64>,
    boxed: Box<u64>,
    path: std::path::PathBuf,
}

#[test]
fn maps_common_owned_types() {
    let value = CommonTypes {
        label: "source",
        bytes: [1, 2, 3],
        maybe_name: Some("Grace".to_owned()),
        maybe_id: Some(11),
        boxed: Box::new(13),
        path: Path::new("/tmp/borrowize").to_path_buf(),
    };

    let view = value.view();

    assert_eq!(view.label, "source");
    assert_eq!(view.bytes, &[1, 2, 3]);
    assert_eq!(view.maybe_name, Some("Grace"));
    assert_eq!(view.maybe_id, Some(&11));
    assert_eq!(*view.boxed, 13);
    assert_eq!(view.path, Path::new("/tmp/borrowize"));
}

#[test]
fn supports_manual_view_construction_with_visibility_overrides() {
    let (id, name) = visibility_boundaries::sibling::public_field_access();
    let restricted_data = visibility_boundaries::sibling::restricted_field_access();

    assert_eq!(*id, 21);
    assert_eq!(name, "manual");
    assert_eq!(restricted_data, &[3, 5, 8]);
}

mod visibility_boundaries {
    pub mod source {
        use borrowize::View;

        #[derive(View)]
        #[borrowize(view_visibility = "pub", field_visibility = "pub", no_method)]
        #[allow(dead_code)]
        pub struct PublicSource {
            id: u64,
            name: String,
        }

        #[derive(View)]
        #[borrowize(
            view_name = "RestrictedView",
            view_visibility = "pub",
            field_visibility = "pub",
            no_method
        )]
        #[allow(dead_code)]
        pub struct RestrictedSource {
            label: String,
            #[borrowize(borrowed_type = "&'borrowize [u8]", visibility = "pub(super)")]
            data: Vec<u8>,
        }
    }

    pub mod sibling {
        const ID: u64 = 21;
        const DATA: &[u8] = &[3, 5, 8];

        pub fn public_field_access() -> (&'static u64, &'static str) {
            let view = super::source::PublicSourceView {
                id: &ID,
                name: "manual",
            };

            (view.id, view.name)
        }

        pub fn restricted_field_access() -> &'static [u8] {
            let view = super::source::RestrictedView {
                label: "restricted",
                data: DATA,
            };

            view.data
        }
    }
}

#[derive(View)]
#[borrowize(method_name = "as_encoded_view", method_visibility = "pub")]
struct Encoded {
    #[borrowize(
        borrowed_type = "&'borrowize str",
        generation_expression = "::std::str::from_utf8(&self.bytes).expect(\"test input is utf8\")"
    )]
    bytes: Vec<u8>,
}

#[test]
fn supports_method_and_generation_expression_overrides() {
    let encoded = Encoded {
        bytes: b"payload".to_vec(),
    };

    let view = encoded.as_encoded_view();

    assert_eq!(view.bytes, "payload");
}

#[derive(View)]
enum Event<'a> {
    Created {
        id: u64,
        name: String,
        tags: Vec<Tag>,
        borrowed: &'a Tag,
        maybe_name: Option<String>,
        maybe_borrowed: Option<&'a Tag>,
    },
    Empty,
}

#[test]
fn creates_borrowed_view_for_named_enum_variants() {
    let tag = Tag("primary");
    let event = Event::Created {
        id: 34,
        name: "enum".to_owned(),
        tags: vec![Tag("first"), Tag("second")],
        borrowed: &tag,
        maybe_name: Some("maybe".to_owned()),
        maybe_borrowed: Some(&tag),
    };

    let view = event.view();

    match view {
        EventView::Created {
            id,
            name,
            tags,
            borrowed,
            maybe_name,
            maybe_borrowed,
        } => {
            assert_eq!(*id, 34);
            assert_eq!(name, "enum");
            assert_eq!(tags, [Tag("first"), Tag("second")]);
            assert_eq!(borrowed, &tag);
            assert_eq!(maybe_name, Some("maybe"));
            assert_eq!(maybe_borrowed, Some(&tag));
        }
        EventView::Empty => panic!("expected created view"),
    }
}

#[test]
fn creates_borrowed_view_for_unit_enum_variants() {
    let event = Event::Empty;

    let view = event.view();

    match view {
        EventView::Empty => {}
        EventView::Created { .. } => panic!("expected empty view"),
    }
}

#[derive(View)]
#[allow(dead_code)]
enum GenericEnum<'a, T, const N: usize>
where
    T: 'a,
{
    Items {
        values: Vec<T>,
        borrowed: &'a T,
        fixed: [u8; N],
    },
    Empty,
}

#[test]
fn preserves_generics_and_where_clauses_on_enums() {
    let item = 89;
    let value = GenericEnum::Items {
        values: vec![3, 5, 8],
        borrowed: &item,
        fixed: [1, 2, 3],
    };

    let view = value.view();

    match view {
        GenericEnumView::Items {
            values,
            borrowed,
            fixed,
        } => {
            assert_eq!(values, &[3, 5, 8]);
            assert_eq!(*borrowed, 89);
            assert_eq!(fixed, &[1, 2, 3]);
        }
        GenericEnumView::Empty => panic!("expected items view"),
    }
}

#[derive(View)]
#[borrowize(view_name = "WireView", method_name = "as_wire_view")]
#[allow(dead_code)]
enum Wire {
    Data {
        #[borrowize(
            borrowed_type = "&'borrowize str",
            generation_expression = "::std::str::from_utf8(bytes).expect(\"test input is utf8\")"
        )]
        bytes: Vec<u8>,
    },
    Empty,
}

#[test]
fn supports_enum_field_overrides() {
    let value = Wire::Data {
        bytes: b"payload".to_vec(),
    };

    let view = value.as_wire_view();

    match view {
        WireView::Data { bytes } => assert_eq!(bytes, "payload"),
        WireView::Empty => panic!("expected data view"),
    }
}

#[derive(View)]
struct Generic<'a, T, const N: usize>
where
    T: 'a,
{
    name: String,
    items: Vec<T>,
    borrowed: &'a T,
    fixed: [u8; N],
}

#[test]
fn preserves_generics_and_where_clauses() {
    let item = 42;
    let value = Generic {
        name: "generic".to_owned(),
        items: vec![1, 2],
        borrowed: &item,
        fixed: [9, 8],
    };

    let view = value.view();

    assert_eq!(view.name, "generic");
    assert_eq!(view.items, &[1, 2]);
    assert_eq!(*view.borrowed, 42);
    assert_eq!(view.fixed, &[9, 8]);
}