use crate::domain::model::entity_ref::EntityRef;
#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize)]
pub struct Relates(Vec<EntityRef>);
impl Relates {
pub fn new() -> Self {
Self::default()
}
fn push(&mut self, target: EntityRef) {
if !self.0.contains(&target) {
self.0.push(target);
}
}
fn remove(&mut self, target: &EntityRef) {
self.0.retain(|t| t != target);
}
pub fn contains(&self, target: &EntityRef) -> bool {
self.0.contains(target)
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn iter(&self) -> impl Iterator<Item = &EntityRef> {
self.0.iter()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RelatesAddition {
Unchanged,
Added { added: EntityRef, list: Relates },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RelatesRemoval {
Unchanged,
Removed { removed: EntityRef, list: Relates },
}
impl Relates {
pub fn with(&self, target: EntityRef) -> RelatesAddition {
if self.contains(&target) {
return RelatesAddition::Unchanged;
}
let mut list = self.clone();
list.push(target.clone());
RelatesAddition::Added {
added: target,
list,
}
}
pub fn without(&self, target: &EntityRef) -> RelatesRemoval {
if !self.contains(target) {
return RelatesRemoval::Unchanged;
}
let mut list = self.clone();
list.remove(target);
RelatesRemoval::Removed {
removed: target.clone(),
list,
}
}
}
impl FromIterator<EntityRef> for Relates {
fn from_iter<I: IntoIterator<Item = EntityRef>>(iter: I) -> Self {
let mut list = Relates::new();
for r in iter {
list.push(r);
}
list
}
}
impl<'a> IntoIterator for &'a Relates {
type Item = &'a EntityRef;
type IntoIter = std::slice::Iter<'a, EntityRef>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
#[cfg(test)]
pub mod strategy {
use super::Relates;
use crate::domain::model::entity_ref::strategy::entity_ref;
use proptest::prelude::*;
pub fn relates() -> impl Strategy<Value = Relates> {
proptest::collection::vec(entity_ref(), 0..5).prop_map(Relates::from_iter)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn r(s: &str) -> EntityRef {
EntityRef::new(s).unwrap()
}
fn list_of(items: &[&str]) -> Relates {
items.iter().map(|s| r(s)).collect()
}
#[test]
fn push_is_idempotent_on_duplicates() {
let mut list = Relates::new();
let target = r("ADR-0001");
list.push(target.clone());
list.push(target);
assert_eq!(list.iter().count(), 1);
}
#[test]
fn remove_is_noop_on_absent_target() {
let mut list = Relates::new();
list.push(r("ADR-0001"));
list.remove(&r("ADR-0099"));
assert_eq!(list.iter().count(), 1);
}
#[test]
fn iter_preserves_insertion_order() {
let list = list_of(&["ADR-0001", "ISSUE-0002", "DDR-0003"]);
let names: Vec<&str> = list.iter().map(EntityRef::as_str).collect();
assert_eq!(names, vec!["ADR-0001", "ISSUE-0002", "DDR-0003"]);
}
#[test]
fn with_is_unchanged_when_target_already_present() {
let list = list_of(&["ADR-0001"]);
assert_eq!(list.with(r("ADR-0001")), RelatesAddition::Unchanged);
}
#[test]
fn with_appends_when_target_is_new() {
let list = list_of(&["ADR-0001"]);
let RelatesAddition::Added { added, list: new } = list.with(r("ADR-0002")) else {
panic!("expected Added");
};
assert_eq!(added, r("ADR-0002"));
assert_eq!(new, list_of(&["ADR-0001", "ADR-0002"]));
}
#[test]
fn with_does_not_mutate_self() {
let list = list_of(&["ADR-0001"]);
let _ = list.with(r("ADR-0002"));
assert_eq!(list, list_of(&["ADR-0001"]));
}
#[test]
fn without_is_unchanged_when_target_absent() {
let list = list_of(&["ADR-0001"]);
assert_eq!(list.without(&r("ADR-0099")), RelatesRemoval::Unchanged);
}
#[test]
fn without_drops_the_target() {
let list = list_of(&["ADR-0001", "ADR-0002", "ADR-0003"]);
let RelatesRemoval::Removed { removed, list: new } = list.without(&r("ADR-0002")) else {
panic!("expected Removed");
};
assert_eq!(removed, r("ADR-0002"));
assert_eq!(new, list_of(&["ADR-0001", "ADR-0003"]));
}
#[test]
fn without_does_not_mutate_self() {
let list = list_of(&["ADR-0001", "ADR-0002"]);
let _ = list.without(&r("ADR-0001"));
assert_eq!(list, list_of(&["ADR-0001", "ADR-0002"]));
}
proptest::proptest! {
#[test]
fn strategy_produces_dedup_list(list in strategy::relates()) {
let mut seen = Vec::new();
for target in list.iter() {
proptest::prop_assert!(!seen.contains(&target), "duplicate in list");
seen.push(target);
}
}
}
}