use std::collections::BTreeSet;
use async_trait::async_trait;
use crate::{
ContextError, PlatformContextProvider, RequestContext, RequestId, RequestMetadata,
ScheduledContextProvider, SubjectId, TenantId,
};
#[derive(Debug, Clone)]
pub struct StaticContextProvider {
pub tenant_id: TenantId,
pub subject_id: SubjectId,
pub roles: BTreeSet<String>,
pub entitlements: BTreeSet<String>,
pub locale: String,
}
impl StaticContextProvider {
pub fn new(tenant_id: &str, subject_id: &str) -> Self {
Self {
tenant_id: tenant_id.parse().expect("StaticContextProvider tenant_id"),
subject_id: subject_id
.parse()
.expect("StaticContextProvider subject_id"),
roles: BTreeSet::new(),
entitlements: BTreeSet::new(),
locale: "en".into(),
}
}
pub fn with_roles(mut self, roles: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.roles = roles.into_iter().map(Into::into).collect();
self
}
pub fn with_entitlements(
mut self,
entitlements: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
self.entitlements = entitlements.into_iter().map(Into::into).collect();
self
}
fn context(&self, request_id: RequestId, locale: &str) -> RequestContext {
RequestContext {
tenant_id: self.tenant_id.clone(),
subject_id: self.subject_id.clone(),
roles: self.roles.clone(),
locale: locale.to_owned(),
request_id,
entitlements: self.entitlements.clone(),
}
}
}
#[async_trait]
impl PlatformContextProvider for StaticContextProvider {
async fn resolve(&self, metadata: &RequestMetadata) -> Result<RequestContext, ContextError> {
let locale = metadata
.0
.get("x-locale")
.map(String::as_str)
.unwrap_or(&self.locale);
let request_id = metadata
.0
.get("x-request-id")
.cloned()
.unwrap_or_else(|| format!("static-{}", self.roles.len()))
.parse()
.map_err(|error: crate::EmptyId| ContextError::Rejected(error.to_string()))?;
Ok(self.context(request_id, locale))
}
}
#[async_trait]
impl ScheduledContextProvider for StaticContextProvider {
async fn resolve_scheduled(
&self,
tenant_id: &TenantId,
subject_id: &SubjectId,
request_id: &RequestId,
locale: &str,
) -> Result<RequestContext, ContextError> {
if tenant_id != &self.tenant_id || subject_id != &self.subject_id {
return Err(ContextError::Rejected(format!(
"static provider only serves {}/{}",
self.tenant_id, self.subject_id
)));
}
Ok(self.context(request_id.clone(), locale))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn static_provider_serves_one_identity() {
let provider = StaticContextProvider::new("tenant", "subject").with_roles(["admin"]);
let resolved = provider.resolve(&RequestMetadata::default()).await.unwrap();
assert_eq!(resolved.tenant_id, "tenant");
assert!(resolved.roles.contains("admin"));
assert!(provider
.resolve_scheduled(
&"other".parse().unwrap(),
&"subject".parse().unwrap(),
&"r".parse().unwrap(),
"en",
)
.await
.is_err());
}
}