use std::collections::BTreeMap;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct SessionContext {
pub actor: Option<SessionActor>,
pub correlation_id: Option<String>,
pub client: SessionClientContext,
pub attributes: BTreeMap<String, String>,
}
impl SessionContext {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_actor(mut self, actor: SessionActor) -> Self {
self.actor = Some(actor);
self
}
#[must_use]
pub fn with_correlation_id(mut self, correlation_id: impl Into<String>) -> Self {
self.correlation_id = Some(correlation_id.into());
self
}
#[must_use]
pub fn with_client(mut self, client: SessionClientContext) -> Self {
self.client = client;
self
}
#[must_use]
pub fn with_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.attributes.insert(key.into(), value.into());
self
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.actor.is_none()
&& self.correlation_id.is_none()
&& self.client.is_empty()
&& self.attributes.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SessionActor {
Subject {
subject_id: String,
},
Delegate {
actor_id: String,
reason: Option<String>,
},
System {
service: String,
},
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct SessionClientContext {
pub user_agent: Option<String>,
pub ip_address: Option<String>,
pub device_id: Option<String>,
}
impl SessionClientContext {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_user_agent(mut self, user_agent: impl Into<String>) -> Self {
self.user_agent = Some(user_agent.into());
self
}
#[must_use]
pub fn with_ip_address(mut self, ip_address: impl Into<String>) -> Self {
self.ip_address = Some(ip_address.into());
self
}
#[must_use]
pub fn with_device_id(mut self, device_id: impl Into<String>) -> Self {
self.device_id = Some(device_id.into());
self
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.user_agent.is_none() && self.ip_address.is_none() && self.device_id.is_none()
}
}
#[cfg(test)]
mod tests {
use super::{SessionActor, SessionClientContext, SessionContext};
#[test]
fn empty_context_reports_empty() {
let context = SessionContext::new();
assert!(context.is_empty());
}
#[test]
fn populated_context_reports_not_empty() {
let context = SessionContext::new()
.with_actor(SessionActor::Subject {
subject_id: "user-123".to_string(),
})
.with_correlation_id("req-456")
.with_client(
SessionClientContext::new()
.with_user_agent("test-agent")
.with_ip_address("127.0.0.1"),
)
.with_attribute("entrypoint", "login");
assert!(!context.is_empty());
}
#[test]
fn empty_client_context_reports_empty() {
let client = SessionClientContext::new();
assert!(client.is_empty());
}
#[test]
fn populated_client_context_reports_not_empty() {
let client = SessionClientContext::new().with_device_id("device-123");
assert!(!client.is_empty());
}
}