Skip to main content

liter_llm/tenant/
resolver.rs

1use std::collections::HashMap;
2use std::future::Future;
3use std::pin::Pin;
4
5use super::context::TenantId;
6
7/// A resolved virtual-key record returned by [`KeyResolver::resolve`].
8#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
9pub struct ResolvedKey {
10    /// Tenant this key belongs to.
11    pub tenant_id: TenantId,
12    /// Model names this key may access.  Empty means unrestricted.
13    pub allowed_models: Vec<String>,
14    /// Optional per-period spending cap.
15    pub monthly_budget: Option<rust_decimal::Decimal>,
16    /// ISO-4217 currency code for `monthly_budget`, e.g. `"EUR"`.
17    pub currency: Option<String>,
18    /// Arbitrary key-value metadata (e.g. `"tier"`, `"label"`).
19    pub metadata: HashMap<String, String>,
20    /// Whether the key is currently active.
21    pub active: bool,
22}
23
24/// Errors returned by [`KeyResolver::resolve`].
25#[derive(Debug, thiserror::Error)]
26pub enum KeyResolverError {
27    /// No key matching the supplied token exists.
28    #[error("api key not found")]
29    NotFound,
30    /// The key exists but has been deactivated.
31    #[error("api key is inactive")]
32    Inactive,
33    /// A backend-specific error prevented resolution.
34    #[error("key resolver backend error: {0}")]
35    Backend(String),
36}
37
38/// Resolves a raw API token to a [`ResolvedKey`].
39///
40/// Implement this trait to plug in a database-backed, remote, or in-process
41/// key store.  The built-in implementation is [`super::InMemoryKeyResolver`].
42///
43/// All methods return `Pin<Box<dyn Future… + 'static>>` so the returned future
44/// can be stored or spawned onto a Tokio executor without borrowing `self`.
45/// The trait itself requires `'static` ownership; implement via `Arc<Self>` if
46/// you need shared ownership across call sites.
47pub trait KeyResolver: Send + Sync + 'static {
48    /// Resolve `api_key` to its associated [`ResolvedKey`].
49    ///
50    /// Takes `api_key` by value so the returned future is `'static` and can be
51    /// spawned onto a Tokio task or stored without borrowing the resolver.
52    ///
53    /// Returns [`KeyResolverError::NotFound`] when no record matches,
54    /// [`KeyResolverError::Inactive`] when the record exists but
55    /// `active == false`.
56    fn resolve(
57        &self,
58        api_key: String,
59    ) -> Pin<Box<dyn Future<Output = Result<ResolvedKey, KeyResolverError>> + Send + 'static>>;
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65    use crate::tenant::InMemoryKeyResolver;
66
67    /// Compile-time assertion: the future returned by `InMemoryKeyResolver::resolve`
68    /// is `Send + 'static` and can be stored or spawned without borrowing the resolver.
69    #[allow(dead_code)]
70    fn _assert_future_is_static_send<T: Future + Send + 'static>(_: T) {}
71
72    #[allow(dead_code)]
73    fn _check_resolve_future_bounds(resolver: &InMemoryKeyResolver) {
74        _assert_future_is_static_send(resolver.resolve("sk-test".to_owned()));
75    }
76}