1use std::fmt;
2use std::time::Duration;
3
4#[derive(Clone, Debug, PartialEq, Eq)]
5pub struct RateLimitEvent {
7 pub worker_id: usize,
9 pub attempt: u32,
11 pub delay: Duration,
13 pub retry_after: Option<Duration>,
15}
16
17#[derive(Clone, Debug, PartialEq, Eq)]
19pub enum PoolError {
20 Spawn(String),
22 Rpc(String),
24 RateLimited {
26 retry_after: Option<Duration>,
28 },
29 QuotaExceeded,
31 WorkerCrashed {
33 worker_id: usize,
35 message: String,
37 },
38 ParseVerdict(String),
40 Timeout {
42 worker_id: usize,
44 timeout: Duration,
46 },
47 NoLiveWorkers,
49 Closed,
51 VisionApi(String),
53}
54
55impl fmt::Display for PoolError {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 match self {
58 Self::Spawn(message) => write!(f, "spawn acp process: {message}"),
59 Self::Rpc(message) => write!(f, "acp rpc error: {message}"),
60 Self::RateLimited { retry_after } => {
61 write!(f, "acp rate limited")?;
62 if let Some(retry_after) = retry_after {
63 write!(f, " retry_after={retry_after:?}")?;
64 }
65 Ok(())
66 }
67 Self::QuotaExceeded => write!(f, "acp usage quota exceeded"),
68 Self::WorkerCrashed { worker_id, message } => {
69 write!(f, "rubric worker {worker_id} crashed: {message}")
70 }
71 Self::ParseVerdict(message) => write!(f, "parse rubric verdict: {message}"),
72 Self::Timeout { worker_id, timeout } => {
73 write!(f, "rubric worker {worker_id} timed out after {timeout:?}")
74 }
75 Self::NoLiveWorkers => write!(f, "no live rubric workers"),
76 Self::Closed => write!(f, "rubric pool is closed"),
77 Self::VisionApi(message) => write!(f, "vision api error: {message}"),
78 }
79 }
80}
81
82impl std::error::Error for PoolError {}
83
84#[derive(Debug)]
86#[non_exhaustive]
87pub enum RubricError {
88 ReadPng {
90 path: std::path::PathBuf,
92 source: std::io::Error,
94 },
95 Pool(PoolError),
97 ParseVerdict {
99 text: String,
101 source: serde_json::Error,
103 },
104 Assertion {
106 name: String,
108 reason: String,
110 anomalies: Vec<String>,
112 },
113}
114
115impl fmt::Display for RubricError {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 match self {
118 Self::ReadPng { path, source } => {
119 write!(f, "read png {}: {source}", path.display())
120 }
121 Self::Pool(error) => error.fmt(f),
122 Self::ParseVerdict { text, source } => {
123 write!(f, "parse verdict from {text:?}: {source}")
124 }
125 Self::Assertion {
126 name,
127 reason,
128 anomalies,
129 } => {
130 write!(f, "[{name}] {reason} (anomalies: {anomalies:?})")
131 }
132 }
133 }
134}
135
136impl std::error::Error for RubricError {
137 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
138 match self {
139 Self::ReadPng { source, .. } => Some(source),
140 Self::Pool(error) => Some(error),
141 Self::ParseVerdict { source, .. } => Some(source),
142 Self::Assertion { .. } => None,
143 }
144 }
145}
146
147impl From<PoolError> for RubricError {
148 fn from(error: PoolError) -> Self {
149 Self::Pool(error)
150 }
151}