1use std::any;
8use std::error::Error;
9
10use thiserror::Error;
11
12#[derive(Debug, Error)]
14#[non_exhaustive]
15pub enum ApiError<E>
16where
17 E: Error + Send + Sync + 'static,
18{
19 #[error("client error: {}", source)]
21 Client {
22 source: E,
24 },
25
26 #[error("failed to parse url: {}", source)]
28 UrlParse {
29 #[from]
31 source: url::ParseError,
32 },
33
34 #[error("failed to create form data: {}", source)]
36 Body {
37 #[from]
39 source: BodyError,
40 },
41
42 #[error("could not parse JSON response: {}", source)]
44 Json {
45 #[from]
47 source: serde_json::Error,
48 },
49
50 #[error("pinboard internal server error {}", status)]
52 PinboardService {
53 status: http::StatusCode,
55 data: Vec<u8>,
57 },
58
59 #[error("could not parse {} data from JSON: {}", typename, source)]
61 DataType {
62 source: serde_json::Error,
64 typename: &'static str,
66 },
67
68 #[error("{}", msg)]
70 Pinboard {
71 msg: String,
73 },
74
75 #[error("{}", obj)]
77 PinboardObject {
78 obj: serde_json::Value,
80 },
81
82 #[error("{}", obj)]
84 PinboardUnrecognized {
85 obj: serde_json::Value,
87 },
88}
89
90#[derive(Debug, Error)]
92#[non_exhaustive]
93pub enum BodyError {
94 #[error("failed to URL encode form parameters: {}", source)]
96 UrlEncoded {
97 #[from]
99 source: serde_urlencoded::ser::Error,
100 },
101}
102
103impl<E> ApiError<E>
104where
105 E: Error + Send + Sync + 'static,
106{
107 pub fn client(source: E) -> Self {
109 ApiError::Client { source }
110 }
111
112 pub(crate) fn server_error(status: http::StatusCode, body: &bytes::Bytes) -> Self {
113 Self::PinboardService {
114 status,
115 data: body.into_iter().copied().collect(),
116 }
117 }
118
119 pub(crate) fn from_pinboard(value: serde_json::Value) -> Self {
120 let error_message = value.pointer("/error_message");
122
123 if let Some(error_message) = error_message {
124 if let Some(msg) = error_message.as_str() {
125 ApiError::Pinboard { msg: msg.into() }
126 } else {
127 ApiError::PinboardObject {
128 obj: error_message.clone(),
129 }
130 }
131 } else {
132 ApiError::PinboardUnrecognized { obj: value }
133 }
134 }
135
136 pub(crate) fn data_type<T>(source: serde_json::Error) -> Self {
137 ApiError::DataType {
138 source,
139 typename: any::type_name::<T>(),
140 }
141 }
142}