use std::{
collections::BTreeSet,
convert::TryFrom,
net::{AddrParseError, IpAddr},
};
use field_names::FieldNames;
#[derive(FieldNames, PartialEq, Eq, PartialOrd, Ord)]
#[allow(dead_code)]
struct Base {
target: IpAddr,
lorem: String,
ipsum: String,
dolor: String,
lorem_computation: String,
#[field_names(skip)]
secret: bool,
}
impl TryFrom<Raw> for Base {
type Error = AddrParseError;
fn try_from(value: Raw) -> Result<Self, Self::Error> {
Ok(Self {
target: value.dest.parse()?,
secret: value.lorem.is_empty(),
lorem_computation: value.lorem.to_ascii_lowercase(),
lorem: value.lorem,
ipsum: value.ipsum,
dolor: value.dolor,
})
}
}
#[derive(FieldNames, PartialEq, Eq, PartialOrd, Ord)]
#[allow(dead_code)]
struct View<'a> {
target: &'a IpAddr,
lorem: &'a str,
ipsum: &'a str,
dolor: &'a str,
}
#[derive(FieldNames)]
#[allow(dead_code)]
struct PartialView<'a> {
lorem: &'a str,
target: &'a IpAddr,
}
#[derive(FieldNames)]
struct Raw {
dest: String,
lorem: String,
ipsum: String,
dolor: String,
}
#[test]
fn base_and_view_in_sync_preserve_source_order() {
const DELIBERATELY_OMITTED_FROM_VIEW: &[&str] = &["lorem_computation"];
let base_fields = Base::FIELDS.iter().collect::<Vec<_>>();
let view_fields = View::FIELDS
.iter()
.chain(DELIBERATELY_OMITTED_FROM_VIEW)
.collect::<Vec<_>>();
assert_eq!(base_fields, view_fields);
}
#[test]
fn base_and_partial_view_in_sync_ignore_source_order() {
let base_fields = Base::FIELDS.iter().collect::<BTreeSet<_>>();
let partial_view_fields = PartialView::FIELDS.iter().collect::<BTreeSet<_>>();
let unexpected_fields = partial_view_fields
.difference(&base_fields)
.collect::<Vec<_>>();
if !unexpected_fields.is_empty() {
panic!("Fields not in base: {:?}", unexpected_fields);
}
}
#[test]
#[should_panic]
fn base_and_raw_not_in_sync() {
const KNOWN_OMISSIONS_FROM_RAW: &[&str] = &["lorem_computation"];
let base_fields = Base::FIELDS.iter().collect::<Vec<_>>();
let raw_fields = Raw::FIELDS
.iter()
.chain(KNOWN_OMISSIONS_FROM_RAW)
.collect::<Vec<_>>();
assert_eq!(base_fields, raw_fields);
}