pinboard_rs/api/
error.rs

1// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
2// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
3// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
4// option. This file may not be copied, modified, or distributed
5// except according to those terms.
6
7use std::any;
8use std::error::Error;
9
10use thiserror::Error;
11
12/// Errors which may occur when using API endpoints.
13#[derive(Debug, Error)]
14#[non_exhaustive]
15pub enum ApiError<E>
16where
17    E: Error + Send + Sync + 'static,
18{
19    /// The client encountered an error
20    #[error("client error: {}", source)]
21    Client {
22        /// The client error.
23        source: E,
24    },
25
26    /// The URL failed to parse.
27    #[error("failed to parse url: {}", source)]
28    UrlParse {
29        /// The source of the error.
30        #[from]
31        source: url::ParseError,
32    },
33
34    /// Body data could not be created
35    #[error("failed to create form data: {}", source)]
36    Body {
37        /// the source of the error.
38        #[from]
39        source: BodyError,
40    },
41
42    /// JSON deserialization failed
43    #[error("could not parse JSON response: {}", source)]
44    Json {
45        /// the source of the error.
46        #[from]
47        source: serde_json::Error,
48    },
49
50    /// Pinboard returned an error without JSON information.
51    #[error("pinboard internal server error {}", status)]
52    PinboardService {
53        /// The status code for the return.
54        status: http::StatusCode,
55        /// The error data from Pinboard.
56        data: Vec<u8>,
57    },
58
59    /// Failed to parse and expected data type from JSON.
60    #[error("could not parse {} data from JSON: {}", typename, source)]
61    DataType {
62        /// The source of the error
63        source: serde_json::Error,
64        /// The name of the type that could not be deserialized
65        typename: &'static str,
66    },
67
68    /// Pinboard returned an Error
69    #[error("{}", msg)]
70    Pinboard {
71        /// The error message
72        msg: String,
73    },
74
75    /// Pinboard returned an Error
76    #[error("{}", obj)]
77    PinboardObject {
78        /// The error message
79        obj: serde_json::Value,
80    },
81
82    /// Unrecognized Pinboard Error
83    #[error("{}", obj)]
84    PinboardUnrecognized {
85        /// The full object from Pinboard
86        obj: serde_json::Value,
87    },
88}
89
90/// Errors which may occur when creating form data.
91#[derive(Debug, Error)]
92#[non_exhaustive]
93pub enum BodyError {
94    /// Body data could not be serialized from form parameters
95    #[error("failed to URL encode form parameters: {}", source)]
96    UrlEncoded {
97        /// the source of the error
98        #[from]
99        source: serde_urlencoded::ser::Error,
100    },
101}
102
103impl<E> ApiError<E>
104where
105    E: Error + Send + Sync + 'static,
106{
107    /// Generate an ApiError from a client
108    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        // TODO: This is now how pinboard returns errors
121        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}