error.rs 75 lignes · 2656 octets
//! Federation-specific error type.

use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};

use rustwarden_core::error::AppError;

/// Errors produced by federation handlers (WebFinger / Actor / NodeInfo).
///
/// Mapped to HTTP status codes via [`IntoResponse`]:
///
/// | Variant | HTTP status |
/// |---|---|
/// | `UserNotFound` | 404 |
/// | `DomainMismatch` | 404 |
/// | `InvalidResource` | 400 |
/// | `NotAcceptable` | 406 |
/// | `App(_)` | inherited from [`AppError`] |
#[derive(Debug, thiserror::Error)]
pub enum FediverseError {
    /// The requested local user does not exist or is disabled.
    #[error("user not found")]
    UserNotFound,

    /// The WebFinger `resource` parameter is malformed.
    #[error("invalid WebFinger resource: {0}")]
    InvalidResource(String),

    /// The WebFinger `resource` targets a domain that is not ours.
    #[error("WebFinger domain mismatch")]
    DomainMismatch,

    /// No representation exists matching the request `Accept` header.
    #[error("acceptable representation not available")]
    NotAcceptable,

    /// RFC 0012: signature missing or rejected by the verifier.
    #[error("unauthorized: {0}")]
    Unauthorized(String),

    /// RFC 0012: request body is invalid (bad JSON or missing required fields).
    #[error("bad request: {0}")]
    BadRequest(String),

    /// RFC 0012: activity type is not implemented by this server.
    #[error("activity type not implemented: {0}")]
    Unsupported(String),

    /// F8 (RFC 0027 / `experimental-fediverse`): the source domain is
    /// not allowed by this server's trust mode (allowlist miss or
    /// blocklist hit). 403 is the correct status code: the signature
    /// itself is valid (would be 401 otherwise), policy refuses.
    #[error("forbidden: {0}")]
    Forbidden(String),

    /// Wraps a generic application error from [`AppError`].
    #[error(transparent)]
    App(#[from] AppError),
}

impl IntoResponse for FediverseError {
    fn into_response(self) -> Response {
        match self {
            Self::UserNotFound | Self::DomainMismatch => StatusCode::NOT_FOUND.into_response(),
            Self::InvalidResource(_) | Self::BadRequest(_) => {
                StatusCode::BAD_REQUEST.into_response()
            }
            Self::NotAcceptable => StatusCode::NOT_ACCEPTABLE.into_response(),
            Self::Unauthorized(_) => StatusCode::UNAUTHORIZED.into_response(),
            Self::Unsupported(_) => StatusCode::NOT_IMPLEMENTED.into_response(),
            Self::Forbidden(_) => StatusCode::FORBIDDEN.into_response(),
            Self::App(err) => err.into_response(),
        }
    }
}