about summary refs log tree commit diff
path: root/kittybox-rs/util/src/error.rs
blob: 7edf176c86171089907e18ee158146b680497d01 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
use serde::{Deserialize, Serialize};
use http::StatusCode;
use axum_core::response::{Response, IntoResponse};

#[derive(Serialize, Deserialize, PartialEq, Debug)]
#[serde(rename_all = "snake_case")]
/// Kinds of errors that can happen within a Micropub operation.
pub enum ErrorType {
    /// An erroneous attempt to create something that already exists.
    AlreadyExists,
    /// Current user is expressly forbidden from performing this action.
    Forbidden,
    /// The Micropub server experienced an internal error.
    InternalServerError,
    /// The request was invalid or malformed.
    InvalidRequest,
    /// The provided OAuth2 scopes were insufficient to allow performing this action.
    InvalidScope,
    /// There was no token or other means of authorization in the request.
    NotAuthorized,
    /// Whatever was requested was not found.
    NotFound,
    /// The request payload was of a type unsupported by the Micropub endpoint.
    UnsupportedMediaType,
}

/// Representation of the Micropub API error.
#[derive(Serialize, Deserialize, Debug)]
pub struct MicropubError {
    pub error: ErrorType,
    // TODO use Cow<'static, str> to save on heap allocations
    pub error_description: String,
}

impl std::error::Error for MicropubError {}

impl std::fmt::Display for MicropubError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("Micropub error: ")?;
        f.write_str(&self.error_description)
    }
}

impl From<serde_json::Error> for MicropubError {
    fn from(err: serde_json::Error) -> Self {
        use ErrorType::*;
        Self {
            error: InvalidRequest,
            error_description: err.to_string(),
        }
    }
}

impl MicropubError {
    pub fn new(error: ErrorType, error_description: &str) -> Self {
        Self {
            error,
            error_description: error_description.to_owned(),
        }
    }
}

impl From<&MicropubError> for StatusCode {
    fn from(err: &MicropubError) -> Self {
        use ErrorType::*;
        match err.error {
            AlreadyExists => StatusCode::CONFLICT,
            Forbidden => StatusCode::FORBIDDEN,
            InternalServerError => StatusCode::INTERNAL_SERVER_ERROR,
            InvalidRequest => StatusCode::BAD_REQUEST,
            InvalidScope => StatusCode::UNAUTHORIZED,
            NotAuthorized => StatusCode::UNAUTHORIZED,
            NotFound => StatusCode::NOT_FOUND,
            UnsupportedMediaType => StatusCode::UNSUPPORTED_MEDIA_TYPE,
        }
    }
}
impl From<MicropubError> for StatusCode {
    fn from(err: MicropubError) -> Self {
        (&err).into()
    }
}

impl IntoResponse for MicropubError {
    fn into_response(self) -> Response {
        IntoResponse::into_response((
            StatusCode::from(&self),
            [("Content-Type", "application/json")],
            serde_json::to_string(&self).unwrap(),
        ))
    }
}