borrowize 0.1.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)]
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]);
}