use crate::domain::model::record_ref::DecisionRecordRef;
use super::DecisionRecord;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct DecisionRecordCollection {
records: Vec<DecisionRecord>,
}
impl DecisionRecordCollection {
pub fn new(records: Vec<DecisionRecord>) -> Self {
Self { records }
}
pub fn iter(&self) -> std::slice::Iter<'_, DecisionRecord> {
self.records.iter()
}
pub fn len(&self) -> usize {
self.records.len()
}
pub fn is_empty(&self) -> bool {
self.records.is_empty()
}
pub fn get(&self, id: &DecisionRecordRef) -> Option<&DecisionRecord> {
self.records.iter().find(|r| &r.id == id)
}
pub fn into_vec(self) -> Vec<DecisionRecord> {
self.records
}
}
impl From<Vec<DecisionRecord>> for DecisionRecordCollection {
fn from(records: Vec<DecisionRecord>) -> Self {
Self::new(records)
}
}
impl FromIterator<DecisionRecord> for DecisionRecordCollection {
fn from_iter<I: IntoIterator<Item = DecisionRecord>>(iter: I) -> Self {
Self::new(iter.into_iter().collect())
}
}
impl IntoIterator for DecisionRecordCollection {
type Item = DecisionRecord;
type IntoIter = std::vec::IntoIter<DecisionRecord>;
fn into_iter(self) -> Self::IntoIter {
self.records.into_iter()
}
}
impl<'a> IntoIterator for &'a DecisionRecordCollection {
type Item = &'a DecisionRecord;
type IntoIter = std::slice::Iter<'a, DecisionRecord>;
fn into_iter(self) -> Self::IntoIter {
self.records.iter()
}
}
#[cfg(test)]
pub mod strategy {
use super::DecisionRecordCollection;
use crate::domain::model::decision_record::strategy::decision_record;
use proptest::prelude::*;
pub fn decision_record_collection() -> impl Strategy<Value = DecisionRecordCollection> {
proptest::collection::vec(decision_record(), 0..6).prop_map(DecisionRecordCollection::new)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::domain::model::body::Body;
use crate::domain::model::decision_record::record_link::RecordLinks;
use crate::domain::model::decision_record::DrStatus;
use crate::domain::model::entry_locator::EntryLocator;
use crate::domain::model::entry_origin::EntryOrigin;
use crate::domain::model::event::EventLog;
use crate::domain::model::record_kind::RecordKind;
use crate::domain::model::relates::Relates;
use crate::domain::model::tag_list::TagList;
use crate::domain::model::temporal::iso_date::IsoDate;
use crate::domain::model::title::Title;
use proptest::prelude::*;
fn record(n: u64, title: &str) -> DecisionRecord {
DecisionRecord {
id: DecisionRecordRef::new(format!("ADR-{n:04}")).unwrap(),
kind: RecordKind::new("adr").unwrap(),
title: Title::new(title).unwrap(),
description: None,
status: DrStatus::Proposed,
date: IsoDate::new("2026-01-01").unwrap(),
tags: TagList::new(),
aliases: Vec::new(),
content: Body::default(),
events: EventLog::new(),
links: RecordLinks::new(),
relates: Relates::default(),
origin: EntryOrigin::Local,
location: EntryLocator::default(),
}
}
#[test]
fn empty_collection_is_empty() {
let c = DecisionRecordCollection::default();
assert_eq!(c.len(), 0);
assert!(c.is_empty());
}
#[test]
fn iter_yields_each_record_in_insertion_order() {
let a = record(1, "A");
let b = record(2, "B");
let c = DecisionRecordCollection::new(vec![a.clone(), b.clone()]);
let collected: Vec<&DecisionRecord> = c.iter().collect();
assert_eq!(collected, vec![&a, &b]);
}
#[test]
fn get_returns_the_matching_record() {
let a = record(1, "A");
let c = DecisionRecordCollection::new(vec![a.clone()]);
assert_eq!(c.get(&a.id), Some(&a));
}
#[test]
fn into_vec_returns_the_underlying_storage() {
let a = record(1, "A");
let c = DecisionRecordCollection::new(vec![a.clone()]);
assert_eq!(c.into_vec(), vec![a]);
}
proptest! {
#[test]
fn len_matches_iter_count(c in strategy::decision_record_collection()) {
prop_assert_eq!(c.len(), c.iter().count());
}
#[test]
fn round_trip_through_vec(c in strategy::decision_record_collection()) {
let v = c.clone().into_vec();
prop_assert_eq!(DecisionRecordCollection::new(v), c);
}
}
}