1use crate::enums::FactStatus;
7use crate::value_objects::{FactId, Timestamp};
8
9#[derive(Debug, thiserror::Error)]
15pub enum DomainError {
16 #[error("fact id format invalid: {0}")]
17 InvalidFactId(String),
18
19 #[error("memory key unsafe or invalid: {0}")]
20 UnsafeMemoryKey(String),
21
22 #[error("session id format invalid: {0}")]
23 InvalidSessionId(String),
24
25 #[error("confidence out of range [0,1]: {0}")]
26 ConfidenceOutOfRange(f32),
27
28 #[error("heat out of range [0,1]: {0}")]
29 HeatOutOfRange(f32),
30
31 #[error("cosine out of range [-1,1]: {0}")]
32 CosineOutOfRange(f32),
33
34 #[error("fact content empty")]
35 EmptyFactContent,
36
37 #[error("embedding empty")]
38 EmptyEmbedding,
39
40 #[error("timestamp out of representable range: {0}")]
41 InvalidTimestamp(String),
42
43 #[error("illegal status transition: {from} -> {to}")]
44 IllegalStatusTransition { from: FactStatus, to: FactStatus },
45
46 #[error("invariant violation: Accepted fact must have confidence >= {threshold}, got {actual}")]
47 ConfidenceBelowAcceptThreshold { threshold: f32, actual: f32 },
48
49 #[error("invariant violation: valid_until ({until}) <= valid_from ({from})")]
50 ValidUntilBeforeValidFrom { from: Timestamp, until: Timestamp },
51
52 #[error("invariant violation: fact cannot conflict with itself: {0}")]
53 SelfConflict(FactId),
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59 use crate::enums::FactStatus;
60 use crate::value_objects::{FactId, Timestamp};
61
62 #[test]
63 fn display_invalid_fact_id_contains_input() {
64 let err = DomainError::InvalidFactId("bad".to_string());
65 assert_eq!(err.to_string(), "fact id format invalid: bad");
66 }
67
68 #[test]
69 fn display_unsafe_memory_key_contains_input() {
70 let err = DomainError::UnsafeMemoryKey("../etc".to_string());
71 assert_eq!(err.to_string(), "memory key unsafe or invalid: ../etc");
72 }
73
74 #[test]
75 fn display_confidence_out_of_range_includes_value() {
76 let err = DomainError::ConfidenceOutOfRange(1.5);
77 assert_eq!(err.to_string(), "confidence out of range [0,1]: 1.5");
78 }
79
80 #[test]
81 fn display_empty_fact_content_is_static_text() {
82 let err = DomainError::EmptyFactContent;
83 assert_eq!(err.to_string(), "fact content empty");
84 }
85
86 #[test]
87 fn display_illegal_transition_includes_both_statuses() {
88 let err = DomainError::IllegalStatusTransition {
89 from: FactStatus::Accepted,
90 to: FactStatus::Pending,
91 };
92 assert_eq!(
93 err.to_string(),
94 "illegal status transition: accepted -> pending"
95 );
96 }
97
98 #[test]
99 fn display_confidence_below_threshold_includes_threshold_and_actual() {
100 let err = DomainError::ConfidenceBelowAcceptThreshold {
101 threshold: 0.7,
102 actual: 0.5,
103 };
104 assert_eq!(
105 err.to_string(),
106 "invariant violation: Accepted fact must have confidence >= 0.7, got 0.5"
107 );
108 }
109
110 #[test]
111 fn display_self_conflict_includes_fact_id() {
112 let id = FactId::from_content("x");
113 let err = DomainError::SelfConflict(id.clone());
114 assert_eq!(
115 err.to_string(),
116 format!(
117 "invariant violation: fact cannot conflict with itself: {}",
118 id
119 )
120 );
121 }
122
123 #[test]
124 fn display_valid_until_before_valid_from_mentions_invariant() {
125 let from = Timestamp::from_unix_secs(1000).unwrap();
126 let until = Timestamp::from_unix_secs(500).unwrap();
127 let err = DomainError::ValidUntilBeforeValidFrom { from, until };
128 let msg = err.to_string();
129 assert!(msg.contains("valid_until"), "msg = {msg}");
133 assert!(msg.contains("valid_from"), "msg = {msg}");
134 assert!(msg.contains("invariant violation"), "msg = {msg}");
135 }
136}