#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PropertyState<T> {
Rendered(T),
NotRendered,
Inherit,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResolvedProperty<T> {
Value(T),
NotRendered,
}
impl<T> ResolvedProperty<T> {
pub fn value(self) -> Option<T> {
match self {
ResolvedProperty::Value(v) => Some(v),
ResolvedProperty::NotRendered => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RootPropertyState<T>(PropertyState<T>);
impl<T> RootPropertyState<T> {
pub fn new(state: PropertyState<T>) -> Self {
assert!(
!matches!(state, PropertyState::Inherit),
"PropertyState::Inherit is illegal on a parentless (master-level) node — it has no parent to defer to"
);
Self(state)
}
pub fn get(&self) -> &PropertyState<T> {
&self.0
}
}
pub fn resolve_property_chain<T: Clone>(root: &RootPropertyState<T>, chain: &[PropertyState<T>]) -> ResolvedProperty<T> {
for state in chain.iter().rev() {
match state {
PropertyState::Rendered(v) => return ResolvedProperty::Value(v.clone()),
PropertyState::NotRendered => return ResolvedProperty::NotRendered,
PropertyState::Inherit => continue,
}
}
match root.get() {
PropertyState::Rendered(v) => ResolvedProperty::Value(v.clone()),
PropertyState::NotRendered => ResolvedProperty::NotRendered,
PropertyState::Inherit => unreachable!("RootPropertyState::new already rejects Inherit"),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn root(state: PropertyState<u32>) -> RootPropertyState<u32> {
RootPropertyState::new(state)
}
#[test]
fn tri_state_resolution_table() {
struct Case {
name: &'static str,
master: PropertyState<u32>,
chain: Vec<PropertyState<u32>>,
expected: ResolvedProperty<u32>,
}
let cases = [
Case {
name: "instance Set beats layout Set",
master: PropertyState::Rendered(1),
chain: vec![PropertyState::Rendered(2), PropertyState::Rendered(3)],
expected: ResolvedProperty::Value(3),
},
Case {
name: "layout Set beats master when instance Inherits",
master: PropertyState::Rendered(1),
chain: vec![PropertyState::Rendered(2), PropertyState::Inherit],
expected: ResolvedProperty::Value(2),
},
Case {
name: "Inherit walks all the way up to master when every level between defers",
master: PropertyState::Rendered(1),
chain: vec![PropertyState::Inherit, PropertyState::Inherit],
expected: ResolvedProperty::Value(1),
},
Case {
name: "an empty chain resolves directly to the root",
master: PropertyState::Rendered(9),
chain: vec![],
expected: ResolvedProperty::Value(9),
},
Case {
name: "NotRendered is a terminal answer distinct from Inherit — never collapses back to master's value",
master: PropertyState::Rendered(1),
chain: vec![PropertyState::NotRendered, PropertyState::Inherit],
expected: ResolvedProperty::NotRendered,
},
Case {
name: "every level inheriting falls through to a NotRendered root",
master: PropertyState::NotRendered,
chain: vec![PropertyState::Inherit],
expected: ResolvedProperty::NotRendered,
},
];
for case in cases {
let resolved = resolve_property_chain(&root(case.master), &case.chain);
assert_eq!(resolved, case.expected, "case failed: {}", case.name);
}
}
#[test]
#[should_panic(expected = "illegal on a parentless")]
fn inherit_on_a_root_level_node_panics_at_construction() {
let _ = RootPropertyState::new(PropertyState::<u32>::Inherit);
}
#[test]
fn resolved_property_value_helper_converts_to_option() {
assert_eq!(ResolvedProperty::Value(5).value(), Some(5));
assert_eq!(ResolvedProperty::<u32>::NotRendered.value(), None);
}
}