use crate::domain::model::body::Body;
use crate::domain::model::decision_record::{DecisionRecord, DrStatus};
use crate::domain::model::entry_locator::EntryLocator;
use crate::domain::model::entry_origin::EntryOrigin;
use crate::domain::model::event::{Event, EventAction};
use crate::domain::model::record_kind::RecordKind;
use crate::domain::model::temporal::timestamp::Timestamp;
use crate::domain::model::title::Title;
use crate::domain::usecases::decision_record::{
DecisionRecordIdGenerator, DecisionRecordRepository,
};
pub fn create_decision_record(
repo: &dyn DecisionRecordRepository,
id_gen: &dyn DecisionRecordIdGenerator,
kind: RecordKind,
title: Title,
body: Body,
now: Timestamp,
links: crate::domain::model::decision_record::RecordLinks,
) -> anyhow::Result<DecisionRecord> {
let id = id_gen.next_id()?;
let initial = DrStatus::INITIAL;
let record = DecisionRecord {
id,
kind,
title,
description: None,
status: initial,
date: now.to_iso_date(),
tags: crate::domain::model::tag_list::TagList::new(),
aliases: Vec::new(),
content: body,
events: [Event {
timestamp: now,
action: EventAction::Created {
state: crate::domain::model::event::State::new(initial.as_str())
.expect("DrStatus names are valid State"),
},
}]
.into_iter()
.collect(),
links,
relates: crate::domain::model::relates::Relates::default(),
origin: EntryOrigin::Local,
location: EntryLocator::default(),
};
repo.save(&record)?;
Ok(record)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::domain::usecases::decision_record::tests::{
adr, FakeDecisionRecordRepository, RecordFixture, SequentialDecisionRecordIdGenerator,
};
#[test]
fn creating_a_record_sets_proposed_status_and_a_created_event() {
scenario()
.when_create(adr("Use Rust"), "2026-03-11")
.then_status("proposed")
.then_kind("adr")
.then_date("2026-03-11")
.then_has_created_event();
}
#[test]
fn creating_a_record_with_an_empty_title_is_rejected() {
let result = Title::new(" ");
assert!(result.is_err());
}
fn scenario() -> Scenario {
Scenario {
repo: FakeDecisionRecordRepository::with_records(vec![]),
}
}
struct Scenario {
repo: FakeDecisionRecordRepository,
}
impl Scenario {
fn when_create(self, fixture: RecordFixture, date: &str) -> CreateOutcome {
let ts = format!("{date}T00:00:00Z");
let now = Timestamp::new(&ts).unwrap_or_else(|_| panic!("invalid timestamp {ts:?}"));
let kind = RecordKind::new(&fixture.kind)
.unwrap_or_else(|_| panic!("invalid kind {:?}", fixture.kind));
let title = Title::new(&fixture.title)
.unwrap_or_else(|_| panic!("invalid title {:?}", fixture.title));
let body = fixture.body.as_deref().map(Body::new).unwrap_or_default();
let id_gen = SequentialDecisionRecordIdGenerator::starting_at(
self.repo.list().unwrap().len() as u32 + 1,
);
let result = create_decision_record(
&self.repo,
&id_gen,
kind,
title,
body,
now,
crate::domain::model::decision_record::RecordLinks::new(),
);
CreateOutcome { result }
}
}
struct CreateOutcome {
result: anyhow::Result<DecisionRecord>,
}
impl CreateOutcome {
fn then_status(self, expected: &str) -> Self {
let record = self.result.as_ref().expect("expected Ok, got Err");
assert_eq!(record.status.as_str(), expected);
self
}
fn then_kind(self, expected: &str) -> Self {
let record = self.result.as_ref().expect("expected Ok, got Err");
assert_eq!(record.kind.as_str(), expected);
self
}
fn then_date(self, expected: &str) -> Self {
let record = self.result.as_ref().expect("expected Ok, got Err");
assert_eq!(record.date.as_str(), expected);
self
}
fn then_has_created_event(self) -> Self {
let record = self.result.as_ref().expect("expected Ok, got Err");
assert_eq!(record.events.len(), 1);
assert!(
record.events[0].action.is_created(),
"expected Created event, got {:?}",
record.events[0].action
);
self
}
}
}