atrium_xrpc/
error.rs

1#![doc = "Error types."]
2use http::{HeaderValue, StatusCode};
3use serde::{Deserialize, Serialize};
4use std::fmt::{self, Debug, Display};
5
6/// An enum of possible error kinds.
7#[derive(thiserror::Error, Debug)]
8pub enum Error<E>
9where
10    E: Debug,
11{
12    #[error("authentication error: {0:?}")]
13    Authentication(HeaderValue),
14    #[error("xrpc response error: {0}")]
15    XrpcResponse(XrpcError<E>),
16    #[error("http request error: {0}")]
17    HttpRequest(#[from] http::Error),
18    #[error("http client error: {0}")]
19    HttpClient(Box<dyn std::error::Error + Send + Sync + 'static>),
20    #[error("serde_json error: {0}")]
21    SerdeJson(#[from] serde_json::Error),
22    #[error("serde_html_form error: {0}")]
23    SerdeHtmlForm(#[from] serde_html_form::ser::Error),
24    #[error("unexpected response type")]
25    UnexpectedResponseType,
26}
27
28/// Type alias to use this library's [`Error`] type in a [`Result`](core::result::Result).
29pub type Result<T, E> = core::result::Result<T, Error<E>>;
30
31/// [A standard error response schema](https://atproto.com/specs/xrpc#error-responses)
32///
33/// ```typescript
34/// export const errorResponseBody = z.object({
35///   error: z.string().optional(),
36///   message: z.string().optional(),
37/// })
38/// export type ErrorResponseBody = z.infer<typeof errorResponseBody>
39/// ```
40#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
41pub struct ErrorResponseBody {
42    pub error: Option<String>,
43    pub message: Option<String>,
44}
45
46impl Display for ErrorResponseBody {
47    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
48        match (&self.error, &self.message) {
49            (Some(err), Some(msg)) => write!(f, "{err}: {msg}"),
50            (Some(err), None) | (None, Some(err)) => write!(f, "{err}"),
51            _ => Ok(()),
52        }
53    }
54}
55
56/// An enum of possible XRPC error kinds.
57///
58/// Error defined in Lexicon schema or other standard error.
59#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
60#[serde(untagged)]
61pub enum XrpcErrorKind<E> {
62    Custom(E),
63    Undefined(ErrorResponseBody),
64}
65
66impl<E: Display> Display for XrpcErrorKind<E> {
67    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
68        match self {
69            Self::Custom(e) => Display::fmt(&e, f),
70            Self::Undefined(e) => Display::fmt(&e, f),
71        }
72    }
73}
74
75/// XRPC response error.
76#[derive(Debug, Clone, PartialEq, Eq)]
77pub struct XrpcError<E> {
78    pub status: StatusCode,
79    pub error: Option<XrpcErrorKind<E>>,
80}
81
82impl<E: Display> Display for XrpcError<E> {
83    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
84        write!(f, "{}", self.status.as_str())?;
85        let Some(error) = &self.error else {
86            return Ok(());
87        };
88        let error = error.to_string();
89        if !error.is_empty() {
90            write!(f, " {error}")?;
91        }
92        Ok(())
93    }
94}