#![deny(missing_docs)]
#![deny(rustdoc::broken_intra_doc_links)]
use std::collections::{BTreeMap, BTreeSet};
use async_trait::async_trait;
pub mod ids;
pub mod testing;
pub use ids::{
ActionIntentId, AssetId, CommandId, EmptyId, InputId, InstanceId, InteractionId, InviteId,
NodeId, ProfileRevisionId, ReleaseId, RequestId, RevisionId, RunId, SessionId, SpaceId,
SubjectId, TenantId, ToolCallId,
};
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct RequestContext {
pub tenant_id: TenantId,
pub subject_id: SubjectId,
pub roles: BTreeSet<String>,
pub locale: String,
pub request_id: RequestId,
pub entitlements: BTreeSet<String>,
}
impl RequestContext {
pub fn validate(&self) -> Result<(), ContextError> {
for (name, value) in [
("tenant_id", self.tenant_id.as_str()),
("subject_id", self.subject_id.as_str()),
("request_id", self.request_id.as_str()),
] {
if value.trim().is_empty() {
return Err(ContextError::Missing(name));
}
}
Ok(())
}
pub fn durable_identity(&self) -> Self {
Self {
tenant_id: self.tenant_id.clone(),
subject_id: self.subject_id.clone(),
roles: BTreeSet::new(),
locale: self.locale.clone(),
request_id: self.request_id.clone(),
entitlements: BTreeSet::new(),
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct RequestMetadata(pub BTreeMap<String, String>);
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum ContextError {
#[error("missing request context field '{0}'")]
Missing(&'static str),
#[error("request context rejected: {0}")]
Rejected(String),
#[error("request context dependency unavailable: {0}")]
Unavailable(String),
}
#[async_trait]
pub trait PlatformContextProvider: Send + Sync {
async fn resolve(&self, metadata: &RequestMetadata) -> Result<RequestContext, ContextError>;
}
#[async_trait]
pub trait ScheduledContextProvider: Send + Sync {
async fn resolve_scheduled(
&self,
tenant_id: &TenantId,
subject_id: &SubjectId,
request_id: &RequestId,
locale: &str,
) -> Result<RequestContext, ContextError>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identity_fields_cannot_be_blank() {
assert_eq!(SubjectId::try_from(""), Err(EmptyId("SubjectId")));
let context = RequestContext {
tenant_id: "tenant".parse().unwrap(),
subject_id: "subject".parse().unwrap(),
roles: BTreeSet::new(),
locale: "en".into(),
request_id: "request".parse().unwrap(),
entitlements: BTreeSet::new(),
};
assert_eq!(context.validate(), Ok(()));
assert_eq!(context.durable_identity().tenant_id, "tenant");
}
}