made_core/events/
task_failed.rs1use serde::{Deserialize, Serialize};
4
5use crate::error::DomainError;
6use crate::events::envelope::EventEnvelope;
7use crate::value_objects::{Specialty, TaskId};
8
9const MAX_REASON_LEN: usize = 4096;
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub struct TaskFailedEvent {
13 #[serde(flatten)]
14 envelope: EventEnvelope,
15 task_id: TaskId,
16 specialty: Specialty,
17 error_kind: String,
18 error_reason: String,
19}
20
21impl TaskFailedEvent {
22 pub fn new(
23 envelope: EventEnvelope,
24 task_id: TaskId,
25 specialty: Specialty,
26 error_kind: impl Into<String>,
27 error_reason: impl Into<String>,
28 ) -> Result<Self, DomainError> {
29 let kind = error_kind.into();
30 if kind.trim().is_empty() {
31 return Err(DomainError::EmptyField {
32 field: "task_failed.error_kind",
33 });
34 }
35 let reason = error_reason.into();
36 if reason.len() > MAX_REASON_LEN {
37 return Err(DomainError::FieldTooLong {
38 field: "task_failed.error_reason",
39 actual: reason.len(),
40 max: MAX_REASON_LEN,
41 });
42 }
43 Ok(Self {
44 envelope,
45 task_id,
46 specialty,
47 error_kind: kind,
48 error_reason: reason,
49 })
50 }
51
52 #[must_use]
53 pub fn envelope(&self) -> &EventEnvelope {
54 &self.envelope
55 }
56 #[must_use]
57 pub fn task_id(&self) -> &TaskId {
58 &self.task_id
59 }
60 #[must_use]
61 pub fn specialty(&self) -> &Specialty {
62 &self.specialty
63 }
64 #[must_use]
65 pub fn error_kind(&self) -> &str {
66 &self.error_kind
67 }
68 #[must_use]
69 pub fn error_reason(&self) -> &str {
70 &self.error_reason
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77 use crate::value_objects::EventId;
78 use time::macros::datetime;
79
80 fn env() -> EventEnvelope {
81 EventEnvelope::new(
82 EventId::new("e").unwrap(),
83 datetime!(2026-04-15 12:00:00 UTC),
84 "s",
85 None,
86 )
87 .unwrap()
88 }
89
90 #[test]
91 fn empty_kind_is_rejected() {
92 let err = TaskFailedEvent::new(
93 env(),
94 TaskId::new("t").unwrap(),
95 Specialty::new("s").unwrap(),
96 " ",
97 "reason",
98 )
99 .unwrap_err();
100 assert!(matches!(
101 err,
102 DomainError::EmptyField {
103 field: "task_failed.error_kind"
104 }
105 ));
106 }
107
108 #[test]
109 fn overlong_reason_is_rejected() {
110 let err = TaskFailedEvent::new(
111 env(),
112 TaskId::new("t").unwrap(),
113 Specialty::new("s").unwrap(),
114 "kind",
115 "x".repeat(MAX_REASON_LEN + 1),
116 )
117 .unwrap_err();
118 assert!(matches!(err, DomainError::FieldTooLong { .. }));
119 }
120
121 #[test]
122 fn well_formed_event_keeps_fields() {
123 let ev = TaskFailedEvent::new(
124 env(),
125 TaskId::new("t").unwrap(),
126 Specialty::new("s").unwrap(),
127 "validator.timeout",
128 "deadline exceeded",
129 )
130 .unwrap();
131 assert_eq!(ev.error_kind(), "validator.timeout");
132 assert_eq!(ev.error_reason(), "deadline exceeded");
133 }
134}