use axum::extract::FromRequestParts;
use axum::http::request::Parts;
use axum::http::StatusCode;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TenantId(pub String);
impl TenantId {
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_string(self) -> String {
self.0
}
}
impl fmt::Display for TenantId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl AsRef<str> for TenantId {
fn as_ref(&self) -> &str {
&self.0
}
}
impl From<String> for TenantId {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for TenantId {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl From<uuid::Uuid> for TenantId {
fn from(u: uuid::Uuid) -> Self {
Self(u.to_string())
}
}
macro_rules! tenant_id_from_int {
($($t:ty),+ $(,)?) => {
$(
impl From<$t> for TenantId {
fn from(n: $t) -> Self {
Self(n.to_string())
}
}
)+
};
}
tenant_id_from_int!(i32, i64, u32, u64, i128, u128);
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>().cloned().ok_or((
StatusCode::INTERNAL_SERVER_ERROR,
"tenaxum: TenantId extension missing — your auth layer must insert it",
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use axum::http::Request;
#[tokio::test]
async fn extracts_tenant_id_from_request_extensions() {
let mut parts = Request::builder()
.uri("/")
.extension(TenantId::new("acme-co"))
.body(())
.expect("build request")
.into_parts()
.0;
let tenant = TenantId::from_request_parts(&mut parts, &())
.await
.expect("extract tenant");
assert_eq!(tenant.as_str(), "acme-co");
}
#[tokio::test]
async fn missing_extension_is_a_server_error() {
let mut parts = Request::builder()
.uri("/")
.body(())
.expect("build request")
.into_parts()
.0;
let err = TenantId::from_request_parts(&mut parts, &())
.await
.expect_err("missing extension should reject");
assert_eq!(err.0, StatusCode::INTERNAL_SERVER_ERROR);
assert_eq!(
err.1,
"tenaxum: TenantId extension missing — your auth layer must insert it"
);
}
}