1use serde::{Deserialize, Serialize};
2use std::fmt;
3use thiserror::Error;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum InteractionKind {
13 Authorization,
14 Unrecognized,
15}
16
17impl InteractionKind {
18 pub const fn as_str(self) -> &'static str {
19 match self {
20 Self::Authorization => "authorization",
21 Self::Unrecognized => "unrecognized",
22 }
23 }
24
25 pub fn from_wire(kind: &str) -> Self {
26 match kind {
27 "authorization" => Self::Authorization,
28 _ => Self::Unrecognized,
29 }
30 }
31}
32
33impl Serialize for InteractionKind {
34 fn serialize<S: serde::Serializer>(
35 &self,
36 serializer: S,
37 ) -> std::result::Result<S::Ok, S::Error> {
38 serializer.serialize_str(self.as_str())
39 }
40}
41
42impl<'de> Deserialize<'de> for InteractionKind {
43 fn deserialize<D: serde::Deserializer<'de>>(
44 deserializer: D,
45 ) -> std::result::Result<Self, D::Error> {
46 Ok(Self::from_wire(&String::deserialize(deserializer)?))
47 }
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53#[serde(deny_unknown_fields)]
54pub struct InteractionReference {
55 pub kind: InteractionKind,
56 pub id: String,
57 #[serde(deserialize_with = "crate::protocol::deserialize_required_nullable")]
58 pub expires_at_unix_ms: Option<u64>,
59}
60
61impl InteractionReference {
62 pub fn authorization(id: impl Into<String>, expires_at_unix_ms: Option<u64>) -> Self {
63 Self {
64 kind: InteractionKind::Authorization,
65 id: id.into(),
66 expires_at_unix_ms,
67 }
68 }
69
70 fn validate(&self) -> std::result::Result<(), &'static str> {
71 if self.id.is_empty()
72 || self.id.len() > 128
73 || !self
74 .id
75 .bytes()
76 .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.'))
77 {
78 return Err("interaction id has an invalid format");
79 }
80 if self.expires_at_unix_ms == Some(0) {
81 return Err("interaction expiry must be positive");
82 }
83 Ok(())
84 }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum ErrorKind {
96 ParseError,
97 InvalidRequest,
98 MethodNotFound,
99 InvalidParams,
100 Internal,
101 UnsupportedVersion,
102 CapabilityRequired,
103 DeadlineExceeded,
104 Cancelled,
105 Unavailable,
106 PermissionDenied,
107 InteractionRequired,
108 Conflict,
109 OperationFailed,
110 MessageTooLarge,
111 RepresentationMismatch,
112 Unrecognized,
120}
121
122impl ErrorKind {
123 pub const fn as_str(self) -> &'static str {
126 match self {
127 Self::ParseError => "parse_error",
128 Self::InvalidRequest => "invalid_request",
129 Self::MethodNotFound => "method_not_found",
130 Self::InvalidParams => "invalid_params",
131 Self::Internal => "internal",
132 Self::UnsupportedVersion => "unsupported_version",
133 Self::CapabilityRequired => "capability_required",
134 Self::DeadlineExceeded => "deadline_exceeded",
135 Self::Cancelled => "cancelled",
136 Self::Unavailable => "unavailable",
137 Self::PermissionDenied => "permission_denied",
138 Self::InteractionRequired => "interaction_required",
139 Self::Conflict => "conflict",
140 Self::OperationFailed => "operation_failed",
141 Self::MessageTooLarge => "message_too_large",
142 Self::RepresentationMismatch => "representation_mismatch",
143 Self::Unrecognized => "unrecognized",
144 }
145 }
146
147 pub fn from_wire(kind: &str) -> Self {
150 Self::DEFINED
151 .iter()
152 .copied()
153 .find(|defined| defined.as_str() == kind)
154 .unwrap_or(Self::Unrecognized)
155 }
156
157 pub fn from_code(code: i32) -> Option<Self> {
162 Self::DEFINED
163 .iter()
164 .copied()
165 .find(|defined| defined.code() == code)
166 }
167
168 pub const DEFINED: &'static [Self] = &[
171 Self::ParseError,
172 Self::InvalidRequest,
173 Self::MethodNotFound,
174 Self::InvalidParams,
175 Self::Internal,
176 Self::UnsupportedVersion,
177 Self::CapabilityRequired,
178 Self::DeadlineExceeded,
179 Self::Cancelled,
180 Self::Unavailable,
181 Self::PermissionDenied,
182 Self::InteractionRequired,
183 Self::Conflict,
184 Self::OperationFailed,
185 Self::MessageTooLarge,
186 Self::RepresentationMismatch,
187 ];
188
189 pub const fn code(self) -> i32 {
190 match self {
191 Self::ParseError => -32700,
192 Self::InvalidRequest => -32600,
193 Self::MethodNotFound => -32601,
194 Self::InvalidParams => -32602,
195 Self::Internal => -32603,
196 Self::UnsupportedVersion => -32000,
197 Self::CapabilityRequired => -32001,
198 Self::DeadlineExceeded => -32002,
199 Self::Cancelled => -32003,
200 Self::Unavailable => -32004,
201 Self::PermissionDenied => -32005,
202 Self::InteractionRequired => -32006,
203 Self::Conflict => -32007,
204 Self::OperationFailed => -32008,
205 Self::MessageTooLarge => -32009,
206 Self::RepresentationMismatch => -32010,
207 Self::Unrecognized => 0,
210 }
211 }
212
213 pub const fn message(self) -> &'static str {
214 match self {
215 Self::ParseError => "parse error",
216 Self::InvalidRequest => "invalid request",
217 Self::MethodNotFound => "method not found",
218 Self::InvalidParams => "invalid params",
219 Self::Internal => "internal error",
220 Self::UnsupportedVersion => "unsupported version",
221 Self::CapabilityRequired => "capability required",
222 Self::DeadlineExceeded => "deadline exceeded",
223 Self::Cancelled => "cancelled",
224 Self::Unavailable => "unavailable",
225 Self::PermissionDenied => "permission denied",
226 Self::InteractionRequired => "interaction required",
227 Self::Conflict => "conflict",
228 Self::OperationFailed => "operation failed",
229 Self::MessageTooLarge => "message too large",
230 Self::RepresentationMismatch => "representation mismatch",
231 Self::Unrecognized => "unrecognized error",
232 }
233 }
234
235 pub const fn retryable_by_default(self) -> bool {
236 matches!(self, Self::Unavailable)
237 }
238}
239
240impl fmt::Display for ErrorKind {
241 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
242 formatter.write_str(self.message())
243 }
244}
245
246impl Serialize for ErrorKind {
247 fn serialize<S: serde::Serializer>(
248 &self,
249 serializer: S,
250 ) -> std::result::Result<S::Ok, S::Error> {
251 serializer.serialize_str(self.as_str())
252 }
253}
254
255impl<'de> Deserialize<'de> for ErrorKind {
256 fn deserialize<D: serde::Deserializer<'de>>(
257 deserializer: D,
258 ) -> std::result::Result<Self, D::Error> {
259 Ok(Self::from_wire(&String::deserialize(deserializer)?))
265 }
266}
267
268#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
270#[serde(deny_unknown_fields)]
271pub struct ErrorData {
272 pub kind: ErrorKind,
273 pub retryable: bool,
274 #[serde(skip_serializing_if = "Option::is_none")]
275 pub retry_after_ms: Option<u64>,
276 #[serde(skip_serializing_if = "Option::is_none")]
277 pub interaction: Option<InteractionReference>,
278}
279
280#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
282#[serde(deny_unknown_fields)]
283pub struct RpcError {
284 pub code: i32,
285 pub message: String,
286 pub data: ErrorData,
287}
288
289impl RpcError {
290 pub fn new(kind: ErrorKind) -> Self {
291 Self {
292 code: kind.code(),
293 message: kind.message().to_string(),
294 data: ErrorData {
295 kind,
296 retryable: kind.retryable_by_default(),
297 retry_after_ms: None,
298 interaction: None,
299 },
300 }
301 }
302
303 pub fn interaction_required(reference: Option<InteractionReference>) -> Self {
304 let mut error = Self::new(ErrorKind::InteractionRequired);
305 error.data.interaction = reference;
306 error
307 }
308
309 pub fn unavailable(retry_after_ms: Option<u64>) -> Self {
310 let mut error = Self::new(ErrorKind::Unavailable);
311 error.data.retry_after_ms = retry_after_ms;
312 error
313 }
314
315 pub fn validate(&self) -> std::result::Result<(), &'static str> {
316 if self.data.kind == ErrorKind::Unrecognized {
317 if ErrorKind::from_code(self.code).is_some() {
321 return Err("defined error code arrived with an undefined kind");
322 }
323 } else if self.code != self.data.kind.code() {
324 return Err("error code does not match error kind");
325 }
326 if self.message.is_empty() || self.message.len() > 256 {
327 return Err("error message has an invalid byte length");
328 }
329 if let Some(retry_after_ms) = self.data.retry_after_ms {
330 if !matches!(
334 self.data.kind,
335 ErrorKind::Unavailable | ErrorKind::Unrecognized
336 ) {
337 return Err("retry_after_ms is only valid for unavailable");
338 }
339 if retry_after_ms == 0 {
340 return Err("retry_after_ms must be positive");
341 }
342 }
343 if let Some(interaction) = &self.data.interaction {
344 if self.data.kind != ErrorKind::InteractionRequired {
345 return Err("interaction is only valid for interaction_required");
346 }
347 interaction.validate()?;
348 }
349 Ok(())
350 }
351}
352
353#[derive(Debug, Error)]
354pub enum Error {
355 #[error("I/O error")]
356 Io(#[from] std::io::Error),
357 #[error("protocol error: {0}")]
358 Protocol(&'static str),
359 #[error("protocol error: {0}")]
360 ProtocolOwned(String),
361 #[error("remote {0:?}")]
362 Remote(RpcError),
363 #[error("request cancelled")]
364 Cancelled,
365 #[error("request deadline exceeded")]
366 DeadlineExceeded,
367 #[error("endpoint unavailable")]
368 Unavailable,
369 #[error("session is closed")]
370 Closed,
371}
372
373impl Error {
374 pub const fn rpc_kind(&self) -> Option<ErrorKind> {
377 match self {
378 Self::Remote(error) => Some(error.data.kind),
379 Self::Cancelled => Some(ErrorKind::Cancelled),
380 Self::DeadlineExceeded => Some(ErrorKind::DeadlineExceeded),
381 Self::Unavailable => Some(ErrorKind::Unavailable),
382 Self::Io(_) | Self::Protocol(_) | Self::ProtocolOwned(_) | Self::Closed => None,
383 }
384 }
385
386 pub const fn stable_message(&self) -> &'static str {
388 match self {
389 Self::Io(_) => "I/O error",
390 Self::Protocol(_) | Self::ProtocolOwned(_) => "protocol error",
391 Self::Remote(_) => "remote error",
392 Self::Cancelled => "cancelled",
393 Self::DeadlineExceeded => "deadline exceeded",
394 Self::Unavailable => "unavailable",
395 Self::Closed => "session closed",
396 }
397 }
398}
399
400pub type Result<T> = std::result::Result<T, Error>;
401
402#[cfg(test)]
403mod tests {
404 use super::*;
405
406 #[test]
407 fn rpc_kind_preserves_closed_remote_semantics() {
408 for kind in [
409 ErrorKind::InteractionRequired,
410 ErrorKind::PermissionDenied,
411 ErrorKind::Conflict,
412 ErrorKind::Unavailable,
413 ] {
414 let error = if kind == ErrorKind::Unavailable {
415 Error::Unavailable
416 } else {
417 Error::Remote(RpcError::new(kind))
418 };
419 assert_eq!(error.rpc_kind(), Some(kind));
420 assert_eq!(kind.to_string(), kind.message());
421 assert!(!kind.as_str().contains(' '));
422 }
423 assert_eq!(Error::Closed.rpc_kind(), None);
424 assert_eq!(Error::Protocol("bad frame").rpc_kind(), None);
425 }
426
427 #[test]
432 fn an_undefined_kind_decodes_instead_of_failing_the_frame() {
433 let later: RpcError = serde_json::from_str(
434 r#"{"code":-32011,"message":"dynamic session required",
435 "data":{"kind":"dynamic_session_required","retryable":false}}"#,
436 )
437 .unwrap();
438 assert_eq!(later.data.kind, ErrorKind::Unrecognized);
439 later.validate().unwrap();
440 assert!(!later.data.kind.retryable_by_default());
442
443 let mismatched: RpcError = serde_json::from_str(
446 r#"{"code":-32005,"message":"permission denied",
447 "data":{"kind":"something_else","retryable":false}}"#,
448 )
449 .unwrap();
450 assert_eq!(mismatched.data.kind, ErrorKind::Unrecognized);
451 assert!(mismatched.validate().is_err());
452
453 for kind in ErrorKind::DEFINED {
455 assert_eq!(ErrorKind::from_wire(kind.as_str()), *kind);
456 assert_eq!(ErrorKind::from_code(kind.code()), Some(*kind));
457 }
458 assert_eq!(ErrorKind::from_code(-32011), None);
459 }
460
461 #[test]
462 fn retry_after_must_be_positive() {
463 assert!(RpcError::unavailable(Some(1)).validate().is_ok());
464 assert!(RpcError::unavailable(Some(0)).validate().is_err());
465 }
466
467 #[test]
468 fn interaction_references_are_bounded_and_kind_specific() {
469 let reference = InteractionReference::authorization("apr_7K3M", Some(1));
470 let error = RpcError::interaction_required(Some(reference.clone()));
471 error.validate().unwrap();
472 let encoded = serde_json::to_string(&error).unwrap();
473 let decoded: RpcError = serde_json::from_str(&encoded).unwrap();
474 assert_eq!(decoded.data.interaction, Some(reference));
475
476 let mut wrong_kind = RpcError::new(ErrorKind::PermissionDenied);
477 wrong_kind.data.interaction = Some(InteractionReference::authorization("apr_1", None));
478 assert!(wrong_kind.validate().is_err());
479
480 assert!(
481 RpcError::interaction_required(Some(InteractionReference::authorization(
482 "terminal escape \u{1b}",
483 None,
484 )))
485 .validate()
486 .is_err()
487 );
488 }
489
490 #[test]
491 fn later_interaction_kinds_decode_without_becoming_authority() {
492 let error: RpcError = serde_json::from_str(
493 r#"{"code":-32006,"message":"interaction required",
494 "data":{"kind":"interaction_required","retryable":false,
495 "interaction":{"kind":"later_kind","id":"ref_1",
496 "expires_at_unix_ms":null}}}"#,
497 )
498 .unwrap();
499 assert_eq!(
500 error.data.interaction.as_ref().unwrap().kind,
501 InteractionKind::Unrecognized
502 );
503 error.validate().unwrap();
504 }
505}