use crate::error::{ErrorCode, NeomemxError, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ScopeIdentifiers {
pub user: Option<String>,
pub agent: Option<String>,
pub session: Option<String>,
}
impl ScopeIdentifiers {
pub fn for_user(user_id: impl Into<String>) -> Self {
Self {
user: Some(user_id.into()),
agent: None,
session: None,
}
}
pub fn for_agent(agent_id: impl Into<String>) -> Self {
Self {
user: None,
agent: Some(agent_id.into()),
session: None,
}
}
pub fn for_session(session_id: impl Into<String>) -> Self {
Self {
user: None,
agent: None,
session: Some(session_id.into()),
}
}
pub fn with_user(mut self, user_id: impl Into<String>) -> Self {
self.user = Some(user_id.into());
self
}
pub fn with_agent(mut self, agent_id: impl Into<String>) -> Self {
self.agent = Some(agent_id.into());
self
}
pub fn with_session(mut self, session_id: impl Into<String>) -> Self {
self.session = Some(session_id.into());
self
}
pub fn validate(&self) -> Result<()> {
if self.is_empty() {
return Err(NeomemxError::validation(
"At least one scope identifier (user, agent, or session) must be provided",
ErrorCode::ValidationRequired,
None,
Some("Provide at least one identifier: user, agent, or session".to_string()),
));
}
Ok(())
}
pub fn is_empty(&self) -> bool {
self.user.is_none() && self.agent.is_none() && self.session.is_none()
}
pub fn to_filter_map(&self) -> HashMap<String, serde_json::Value> {
let mut map = HashMap::new();
if let Some(ref user) = self.user {
map.insert("user_id".to_string(), serde_json::json!(user));
}
if let Some(ref agent) = self.agent {
map.insert("agent_id".to_string(), serde_json::json!(agent));
}
if let Some(ref session) = self.session {
map.insert("session_id".to_string(), serde_json::json!(session));
}
map
}
}
impl Default for ScopeIdentifiers {
fn default() -> Self {
Self {
user: None,
agent: None,
session: None,
}
}
}
#[derive(Debug, Clone)]
pub struct ScopeFilter {
pub user: Option<String>,
pub agent: Option<String>,
pub session: Option<String>,
pub additional: HashMap<String, serde_json::Value>,
}
impl ScopeFilter {
pub fn new() -> Self {
Self {
user: None,
agent: None,
session: None,
additional: HashMap::new(),
}
}
pub fn from_scope(scope: &ScopeIdentifiers) -> Self {
let mut filter = Self::new();
filter.user = scope.user.clone();
filter.agent = scope.agent.clone();
filter.session = scope.session.clone();
filter
}
pub fn to_map(&self) -> HashMap<String, serde_json::Value> {
let mut map = self.additional.clone();
if let Some(ref user) = self.user {
map.insert("user_id".to_string(), serde_json::json!(user));
}
if let Some(ref agent) = self.agent {
map.insert("agent_id".to_string(), serde_json::json!(agent));
}
if let Some(ref session) = self.session {
map.insert("session_id".to_string(), serde_json::json!(session));
}
map
}
}
impl Default for ScopeFilter {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scope_creation() {
let scope = ScopeIdentifiers::for_user("user_123");
assert_eq!(scope.user, Some("user_123".to_string()));
assert!(scope.agent.is_none());
assert!(scope.session.is_none());
}
#[test]
fn test_scope_chaining() {
let scope = ScopeIdentifiers::for_user("user_123")
.with_agent("agent_456")
.with_session("session_789");
assert_eq!(scope.user, Some("user_123".to_string()));
assert_eq!(scope.agent, Some("agent_456".to_string()));
assert_eq!(scope.session, Some("session_789".to_string()));
}
#[test]
fn test_scope_validation() {
let empty = ScopeIdentifiers::default();
assert!(empty.validate().is_err());
let valid = ScopeIdentifiers::for_user("user_123");
assert!(valid.validate().is_ok());
}
#[test]
fn test_scope_to_filter_map() {
let scope = ScopeIdentifiers::for_user("user_123").with_agent("agent_456");
let filter = scope.to_filter_map();
assert_eq!(filter.get("user_id"), Some(&serde_json::json!("user_123")));
assert_eq!(
filter.get("agent_id"),
Some(&serde_json::json!("agent_456"))
);
}
}