1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2
3use crate::envelope::deserialize_unique_value;
4use thiserror::Error;
5
6use crate::{
7 CURRENT_PROTOCOL_VERSION, CorrelationId, ProtocolMetadata, ProtocolMetadataError,
8 ProtocolVersion,
9};
10
11pub const MAX_ERROR_MESSAGE_BYTES: usize = 4096;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16#[serde(rename_all = "snake_case")]
17pub enum AgentErrorCode {
18 UnsupportedCommand,
20 UnsupportedRecord,
22 UnsupportedProtocolVersion,
24 InvalidCommand,
26 InvalidInput,
28 SequenceConflict,
30 RateLimited,
32 ProviderUnavailable,
34 Cancelled,
36 Internal,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(rename_all = "snake_case")]
43pub enum RetryClass {
44 Never,
46 Immediate,
48 AfterBackoff,
50}
51
52#[derive(Debug, Clone, PartialEq)]
54pub struct ProtocolError {
55 code: AgentErrorCode,
56 message: String,
57 retry: RetryClass,
58 correlation_id: Option<CorrelationId>,
59 details: ProtocolMetadata,
60}
61
62impl ProtocolError {
63 pub fn new(
70 code: AgentErrorCode,
71 message: impl Into<String>,
72 retry: RetryClass,
73 ) -> Result<Self, ProtocolErrorValidationError> {
74 let message = message.into();
75 validate_message(&message)?;
76 Ok(Self {
77 code,
78 message,
79 retry,
80 correlation_id: None,
81 details: ProtocolMetadata::default(),
82 })
83 }
84
85 pub(crate) fn invalid_command(correlation_id: CorrelationId) -> Self {
86 Self {
87 code: AgentErrorCode::InvalidCommand,
88 message: "command envelope is invalid".to_owned(),
89 retry: RetryClass::Never,
90 correlation_id: Some(correlation_id),
91 details: ProtocolMetadata::default(),
92 }
93 }
94
95 pub(crate) fn invalid_record(correlation_id: CorrelationId) -> Self {
96 Self {
97 code: AgentErrorCode::InvalidInput,
98 message: "durable session record is invalid".to_owned(),
99 retry: RetryClass::Never,
100 correlation_id: Some(correlation_id),
101 details: ProtocolMetadata::default(),
102 }
103 }
104
105 pub(crate) fn unsupported_protocol_version(
106 correlation_id: CorrelationId,
107 received_version: ProtocolVersion,
108 ) -> Self {
109 Self {
110 code: AgentErrorCode::UnsupportedProtocolVersion,
111 message: "protocol version is not supported".to_owned(),
112 retry: RetryClass::Never,
113 correlation_id: Some(correlation_id),
114 details: ProtocolMetadata::protocol_version_details(&received_version.to_string()),
115 }
116 }
117
118 pub(crate) fn unsupported_record(correlation_id: CorrelationId, record_type: &str) -> Self {
119 Self {
120 code: AgentErrorCode::UnsupportedRecord,
121 message: "durable record type is not supported".to_owned(),
122 retry: RetryClass::Never,
123 correlation_id: Some(correlation_id),
124 details: ProtocolMetadata::protocol_compatibility_details(Some(record_type)),
125 }
126 }
127
128 #[must_use]
130 pub fn unsupported_command(correlation_id: CorrelationId) -> Self {
131 let details = ProtocolMetadata::protocol_compatibility_details(None);
132 Self {
133 code: AgentErrorCode::UnsupportedCommand,
134 message: "command type is not supported".to_owned(),
135 retry: RetryClass::Never,
136 correlation_id: Some(correlation_id),
137 details,
138 }
139 }
140
141 #[must_use]
143 pub fn with_correlation_id(mut self, correlation_id: CorrelationId) -> Self {
144 self.correlation_id = Some(correlation_id);
145 self
146 }
147
148 #[must_use]
150 pub fn with_details(mut self, details: ProtocolMetadata) -> Self {
151 self.details = details;
152 self
153 }
154
155 #[must_use]
157 pub const fn code(&self) -> AgentErrorCode {
158 self.code
159 }
160
161 #[must_use]
163 pub fn message(&self) -> &str {
164 &self.message
165 }
166
167 #[must_use]
169 pub const fn retry(&self) -> RetryClass {
170 self.retry
171 }
172
173 #[must_use]
175 pub const fn correlation_id(&self) -> Option<&CorrelationId> {
176 self.correlation_id.as_ref()
177 }
178
179 #[must_use]
181 pub const fn details(&self) -> &ProtocolMetadata {
182 &self.details
183 }
184
185 fn validate(&self) -> Result<(), ProtocolErrorValidationError> {
186 validate_message(&self.message)
187 }
188}
189
190#[derive(Serialize)]
191#[serde(rename_all = "camelCase")]
192struct SerializableProtocolError<'a> {
193 code: AgentErrorCode,
194 message: &'a str,
195 retry: RetryClass,
196 #[serde(skip_serializing_if = "Option::is_none")]
197 correlation_id: &'a Option<CorrelationId>,
198 #[serde(skip_serializing_if = "ProtocolMetadata::is_empty")]
199 details: &'a ProtocolMetadata,
200}
201
202impl Serialize for ProtocolError {
203 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
204 where
205 S: Serializer,
206 {
207 self.validate().map_err(serde::ser::Error::custom)?;
208 SerializableProtocolError {
209 code: self.code,
210 message: &self.message,
211 retry: self.retry,
212 correlation_id: &self.correlation_id,
213 details: &self.details,
214 }
215 .serialize(serializer)
216 }
217}
218
219#[derive(Deserialize)]
220#[serde(rename_all = "camelCase")]
221struct RawProtocolError {
222 code: AgentErrorCode,
223 message: String,
224 retry: RetryClass,
225 #[serde(default)]
226 correlation_id: Option<CorrelationId>,
227 #[serde(default)]
228 details: ProtocolMetadata,
229}
230
231impl<'de> Deserialize<'de> for ProtocolError {
232 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
233 where
234 D: Deserializer<'de>,
235 {
236 let raw = RawProtocolError::deserialize(deserializer)?;
237 let mut error = Self::new(raw.code, raw.message, raw.retry)
238 .map_err(serde::de::Error::custom)?
239 .with_details(raw.details);
240 error.correlation_id = raw.correlation_id;
241 Ok(error)
242 }
243}
244
245#[derive(Debug, Clone, PartialEq, Serialize)]
247#[serde(rename_all = "camelCase")]
248pub struct ProtocolErrorEnvelope {
249 protocol_version: ProtocolVersion,
250 #[serde(rename = "type")]
251 kind: ProtocolErrorEnvelopeType,
252 error: ProtocolError,
253}
254
255impl ProtocolErrorEnvelope {
256 #[must_use]
258 pub const fn new(error: ProtocolError) -> Self {
259 Self {
260 protocol_version: CURRENT_PROTOCOL_VERSION,
261 kind: ProtocolErrorEnvelopeType::ProtocolError,
262 error,
263 }
264 }
265
266 #[must_use]
268 pub const fn protocol_version(&self) -> ProtocolVersion {
269 self.protocol_version
270 }
271
272 #[must_use]
274 pub const fn error(&self) -> &ProtocolError {
275 &self.error
276 }
277}
278
279#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
280#[serde(rename_all = "snake_case")]
281enum ProtocolErrorEnvelopeType {
282 ProtocolError,
283}
284
285#[derive(Deserialize)]
286#[serde(rename_all = "camelCase")]
287struct RawProtocolErrorEnvelope {
288 protocol_version: ProtocolVersion,
289 #[serde(rename = "type")]
290 kind: ProtocolErrorEnvelopeType,
291 error: ProtocolError,
292}
293
294impl<'de> Deserialize<'de> for ProtocolErrorEnvelope {
295 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
296 where
297 D: Deserializer<'de>,
298 {
299 let value = deserialize_unique_value(deserializer)?;
300 let raw = RawProtocolErrorEnvelope::deserialize(value).map_err(serde::de::Error::custom)?;
301 if raw.protocol_version.major() != CURRENT_PROTOCOL_VERSION.major() {
302 return Err(serde::de::Error::custom(
303 "unsupported protocol major version",
304 ));
305 }
306 Ok(Self {
307 protocol_version: raw.protocol_version,
308 kind: raw.kind,
309 error: raw.error,
310 })
311 }
312}
313
314#[derive(Debug, Error)]
316pub enum ProtocolErrorValidationError {
317 #[error("technical error message is invalid")]
319 InvalidMessage,
320 #[error("safe error details are invalid: {0}")]
322 InvalidDetails(#[from] ProtocolMetadataError),
323}
324
325fn validate_message(message: &str) -> Result<(), ProtocolErrorValidationError> {
326 if message.is_empty()
327 || message.len() > MAX_ERROR_MESSAGE_BYTES
328 || message.contains('\0')
329 || message.chars().any(|character| character == '\r')
330 {
331 Err(ProtocolErrorValidationError::InvalidMessage)
332 } else {
333 Ok(())
334 }
335}