use std::sync::Arc;
use systemprompt_identifiers::{ApiKeyId, ModelId, ProviderId, RouteId, UserId};
use systemprompt_models::attribution::RequestAttribution;
use systemprompt_traits::DatabaseHandle;
use crate::context::ExtensionContext;
#[derive(Debug, Clone)]
pub struct GatewayGuardRequest<'a> {
pub user_id: &'a UserId,
pub model: &'a ModelId,
pub route_id: Option<&'a RouteId>,
pub provider: &'a ProviderId,
pub streaming: bool,
pub attribution: &'a RequestAttribution,
pub api_key_id: Option<&'a ApiKeyId>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum GatewayDenyKind {
#[default]
Quota,
Forbidden,
Unavailable,
}
#[derive(Debug, Clone)]
pub struct GatewayDenyReason {
pub message: String,
pub retry_after_seconds: i32,
pub kind: GatewayDenyKind,
}
impl GatewayDenyReason {
pub fn unavailable(message: impl Into<String>) -> Self {
Self {
message: message.into(),
retry_after_seconds: 5,
kind: GatewayDenyKind::Unavailable,
}
}
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
retry_after_seconds: 0,
kind: GatewayDenyKind::Quota,
}
}
pub fn forbidden(message: impl Into<String>) -> Self {
Self {
message: message.into(),
retry_after_seconds: 0,
kind: GatewayDenyKind::Forbidden,
}
}
}
#[async_trait::async_trait]
pub trait GatewayRequestGuard: Send + Sync {
async fn check(
&self,
db: &dyn DatabaseHandle,
request: &GatewayGuardRequest<'_>,
) -> Result<(), GatewayDenyReason>;
}
#[derive(Debug, Clone, Copy)]
pub struct GatewayRequestGuardRegistration {
pub factory: fn(&dyn ExtensionContext) -> Arc<dyn GatewayRequestGuard>,
}
inventory::collect!(GatewayRequestGuardRegistration);
#[macro_export]
macro_rules! register_gateway_guard {
(factory = $factory:expr) => {
$crate::__inventory::submit! {
$crate::GatewayRequestGuardRegistration {
factory: $factory,
}
}
};
($guard_type:ty) => {
$crate::__inventory::submit! {
$crate::GatewayRequestGuardRegistration {
factory: |_| ::std::sync::Arc::new(<$guard_type>::default())
as ::std::sync::Arc<dyn $crate::GatewayRequestGuard>,
}
}
};
}
#[derive(Clone, Default)]
pub struct GatewayGuards(Vec<Arc<dyn GatewayRequestGuard>>);
impl std::fmt::Debug for GatewayGuards {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GatewayGuards")
.field("guards", &self.0.len())
.finish()
}
}
impl GatewayGuards {
#[must_use]
pub fn discover(ctx: &dyn ExtensionContext) -> Self {
Self(
inventory::iter::<GatewayRequestGuardRegistration>
.into_iter()
.map(|registration| (registration.factory)(ctx))
.collect(),
)
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub async fn run(
&self,
db: &dyn DatabaseHandle,
request: &GatewayGuardRequest<'_>,
) -> Result<(), GatewayDenyReason> {
for guard in &self.0 {
guard.check(db, request).await?;
}
Ok(())
}
}