road-runner-common 0.2.2

Shared Rust utilities for exchange ecosystem backend services.
Documentation
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::error::AppError;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserContext {
    pub user_id: Uuid,
    pub email: String,

    #[serde(default)]
    pub roles: Vec<String>,

    #[serde(default)]
    pub scopes: Vec<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub tenant_id: Option<String>,
}

impl UserContext {
    pub fn has_role(&self, role: &str) -> bool {
        self.roles.iter().any(|r| r == role)
    }

    pub fn require_role(&self, role: &str) -> Result<(), AppError> {
        if self.has_role(role) {
            Ok(())
        } else {
            Err(AppError::Forbidden {
                message: format!("Missing role: {}", role),
            })
        }
    }
}

#[cfg(feature = "web")]
mod web_impl {
    use super::*;
    use actix_web::{dev::Payload, FromRequest, HttpMessage, HttpRequest};
    use futures_util::future::{ready, Ready};

    /// Actix extractor:
    /// Middleware'in `req.extensions_mut().insert(UserContext)` yaptığı varsayımıyla çalışır.
    impl FromRequest for UserContext {
        type Error = AppError;
        type Future = Ready<Result<Self, Self::Error>>;

        fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
            match req.extensions().get::<UserContext>().cloned() {
                Some(ctx) => ready(Ok(ctx)),
                None => ready(Err(AppError::Unauthorized {
                    message: "Missing user context".to_string(),
                })),
            }
        }
    }
}