switchgear-service 0.1.2

Service layer and API implementations for Switchgear LNURL load balancer
Documentation
use crate::api::service::HasServiceErrorSource;
use axum::http::header::InvalidHeaderValue;
use axum::http::{HeaderMap, HeaderValue};
use axum::{
    http::StatusCode,
    response::{IntoResponse, Response},
};
use log::error;
use std::fmt::Display;
use thiserror::Error;

#[macro_export]
macro_rules! crud_error_from_service {
    ($error:expr) => {
        CrudError::from_service_error(module_path!(), &format!("{}:{}", file!(), line!()), $error)
    };
}

pub enum WwwAuthenticateError {
    MissingToken,
    InvalidToken,
}

impl Display for WwwAuthenticateError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            WwwAuthenticateError::MissingToken => write!(f, "missing_token"),
            WwwAuthenticateError::InvalidToken => write!(f, "invalid_token"),
        }
    }
}

#[derive(Debug, Error)]
#[error("{status}")]
pub struct CrudError {
    status: StatusCode,
    headers: HeaderMap,
}

impl CrudError {
    pub fn not_found() -> Self {
        Self {
            status: StatusCode::NOT_FOUND,
            headers: Default::default(),
        }
    }

    pub fn conflict(location: HeaderValue) -> Self {
        Self {
            status: StatusCode::CONFLICT,
            headers: HeaderMap::from_iter(vec![(axum::http::header::LOCATION, location)]),
        }
    }

    pub fn unauthorized(realm: &str, error: WwwAuthenticateError) -> Self {
        let value = match error {
            WwwAuthenticateError::MissingToken => {
                HeaderValue::from_str(&format!(r#"Bearer realm="{realm}""#))
            }
            WwwAuthenticateError::InvalidToken => {
                HeaderValue::from_str(&format!(r#"Bearer realm="{realm}", error="{error}""#))
            }
        };

        let value = value.unwrap_or_else(|_| HeaderValue::from_static("Bearer"));

        Self {
            status: StatusCode::UNAUTHORIZED,
            headers: HeaderMap::from_iter(vec![(axum::http::header::WWW_AUTHENTICATE, value)]),
        }
    }

    pub fn from_service_error<E>(service: &str, location: &str, error: E) -> Self
    where
        E: HasServiceErrorSource + std::fmt::Display,
    {
        error!(target:service, "{error} at: {location}");
        Self {
            status: error.get_service_error_source().to_http_status(),
            headers: Default::default(),
        }
    }
}

impl From<InvalidHeaderValue> for CrudError {
    fn from(_: InvalidHeaderValue) -> Self {
        Self {
            status: StatusCode::INTERNAL_SERVER_ERROR,
            headers: Default::default(),
        }
    }
}

impl IntoResponse for CrudError {
    fn into_response(self) -> Response {
        (self.headers, self.status).into_response()
    }
}