tenaxum 0.1.1

Tenant-scoped helpers for Axum + sqlx + Postgres. Tenacious about row-level isolation.
Documentation
use axum::extract::FromRequestParts;
use axum::http::request::Parts;
use axum::http::StatusCode;
use uuid::Uuid;

/// A validated tenant identifier extracted from the request.
///
/// Implements [`FromRequestParts`] by reading
/// [`Extension<TenantId>`](axum::Extension) off the request. Your own auth
/// middleware is expected to insert the extension; if it is missing the
/// extractor returns `500 Internal Server Error` because that indicates a
/// server wiring bug, not a client problem.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TenantId(pub Uuid);

impl TenantId {
    pub fn new(id: Uuid) -> Self {
        Self(id)
    }

    pub fn into_uuid(self) -> Uuid {
        self.0
    }
}

impl From<Uuid> for TenantId {
    fn from(id: Uuid) -> Self {
        Self(id)
    }
}

impl From<TenantId> for Uuid {
    fn from(t: TenantId) -> Uuid {
        t.0
    }
}

impl std::fmt::Display for TenantId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}

impl<S> FromRequestParts<S> for TenantId
where
    S: Send + Sync,
{
    type Rejection = (StatusCode, &'static str);

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
        parts
            .extensions
            .get::<TenantId>()
            .copied()
            .ok_or((
                StatusCode::INTERNAL_SERVER_ERROR,
                "tenaxum: TenantId extension missing — your auth layer must insert it",
            ))
    }
}