1use std::fmt;
8
9#[derive(Debug)]
10pub enum TriggerError {
11 Malformed { what: String },
13 UnresolvedWorkflow { hash: String, why: String },
15 NoConnector { trigger: String, connector: String },
21 ConnectorFailed { trigger: String, detail: String },
23 ClaimLost { trigger: String },
25 BadSchedule { expr: String, why: String },
28 UnknownTarget { target: String },
30 UnknownMember { trigger: String, member: String },
32 EgressRefused { trigger: String, host: String },
34 Storage { detail: String },
36}
37
38impl TriggerError {
39 pub fn code(&self) -> &'static str {
40 match self {
41 TriggerError::Malformed { .. } => "TRG-E001",
42 TriggerError::UnresolvedWorkflow { .. } => "TRG-E002",
43 TriggerError::NoConnector { .. } => "TRG-E003",
44 TriggerError::ConnectorFailed { .. } => "TRG-E004",
45 TriggerError::ClaimLost { .. } => "TRG-E005",
46 TriggerError::BadSchedule { .. } => "TRG-E006",
47 TriggerError::UnknownTarget { .. } => "TRG-E007",
48 TriggerError::UnknownMember { .. } => "TRG-E008",
49 TriggerError::EgressRefused { .. } => "TRG-E009",
50 TriggerError::Storage { .. } => "TRG-E010",
51 }
52 }
53}
54
55impl fmt::Display for TriggerError {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 match self {
58 TriggerError::Malformed { what } => {
59 write!(f, "TRG-E001: malformed trigger declaration: {what}")
60 }
61 TriggerError::UnresolvedWorkflow { hash, why } => {
62 write!(f, "TRG-E002: workflow {hash} does not resolve: {why}")
63 }
64 TriggerError::NoConnector { trigger, connector } => write!(
65 f,
66 "TRG-E003: trigger {trigger} is due but no connector command is configured \
67 for '{connector}' — pass --connector-cmd, or the poll would silently do nothing"
68 ),
69 TriggerError::ConnectorFailed { trigger, detail } => {
70 write!(f, "TRG-E004: connector for trigger {trigger} failed: {detail}")
71 }
72 TriggerError::ClaimLost { trigger } => write!(
73 f,
74 "TRG-E005: claim on trigger {trigger} was lost — its lease expired and another \
75 evaluator took over; this firing's release was refused"
76 ),
77 TriggerError::BadSchedule { expr, why } => {
78 write!(f, "TRG-E006: schedule {expr:?} is unusable: {why}")
79 }
80 TriggerError::UnknownTarget { target } => {
81 write!(f, "TRG-E007: unknown render target '{target}'")
82 }
83 TriggerError::UnknownMember { trigger, member } => write!(
84 f,
85 "TRG-E008: composite trigger {trigger} references member '{member}' \
86 which it does not declare"
87 ),
88 TriggerError::EgressRefused { trigger, host } => write!(
89 f,
90 "TRG-E009: connector for trigger {trigger} tried to reach '{host}', \
91 which is outside its declared allowed_outbound_hosts"
92 ),
93 TriggerError::Storage { detail } => write!(f, "TRG-E010: store: {detail}"),
94 }
95 }
96}
97
98impl std::error::Error for TriggerError {}
99
100pub type Result<T> = std::result::Result<T, TriggerError>;
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 fn all() -> Vec<TriggerError> {
107 vec![
108 TriggerError::Malformed { what: "x".into() },
109 TriggerError::UnresolvedWorkflow { hash: "h".into(), why: "w".into() },
110 TriggerError::NoConnector { trigger: "t".into(), connector: "c".into() },
111 TriggerError::ConnectorFailed { trigger: "t".into(), detail: "d".into() },
112 TriggerError::ClaimLost { trigger: "t".into() },
113 TriggerError::BadSchedule { expr: "e".into(), why: "w".into() },
114 TriggerError::UnknownTarget { target: "t".into() },
115 TriggerError::UnknownMember { trigger: "t".into(), member: "m".into() },
116 TriggerError::EgressRefused { trigger: "t".into(), host: "h".into() },
117 TriggerError::Storage { detail: "d".into() },
118 ]
119 }
120
121 #[test]
122 fn the_code_leads_the_display_string() {
123 for e in all() {
126 let s = e.to_string();
127 assert!(s.starts_with(e.code()), "{s} should start with {}", e.code());
128 }
129 }
130
131 #[test]
132 fn codes_are_unique_and_contiguous() {
133 let codes: Vec<&str> = all().iter().map(|e| e.code()).collect();
134 let mut sorted = codes.clone();
135 sorted.sort_unstable();
136 sorted.dedup();
137 assert_eq!(sorted.len(), codes.len(), "codes must be unique");
138 for (i, c) in codes.iter().enumerate() {
139 assert_eq!(*c, format!("TRG-E{:03}", i + 1), "codes are append-only and dense");
140 }
141 }
142}