use serde::{Deserialize, Serialize};
use http::StatusCode;
use axum_core::response::{Response, IntoResponse};
#[derive(Serialize, Deserialize, PartialEq, Eq, 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 {
/// General kind of an error that occured.
pub error: ErrorType,
/// A human-readable error description intended for application developers.
// 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 {
/// Create a new Micropub error.
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(),
))
}
}