use crate::{Dimension, Error, Result};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq)]
pub struct CortexUri {
pub dimension: Dimension,
pub category: String,
pub subcategory: Option<String>,
pub resource: Option<String>,
pub params: HashMap<String, String>,
}
impl CortexUri {
pub fn new(dimension: Dimension) -> Self {
Self {
dimension,
category: String::new(),
subcategory: None,
resource: None,
params: HashMap::new(),
}
}
pub fn user_preferences(name: &str) -> String {
format!("cortex://user/preferences/{}.md", name)
}
pub fn user_entities(name: &str) -> String {
format!("cortex://user/entities/{}.md", name)
}
pub fn user_events(name: &str) -> String {
format!("cortex://user/events/{}.md", name)
}
pub fn agent_cases(name: &str) -> String {
format!("cortex://agent/cases/{}.md", name)
}
pub fn agent_skills(name: &str) -> String {
format!("cortex://agent/skills/{}.md", name)
}
pub fn session(session_id: &str) -> String {
format!("cortex://session/{}", session_id)
}
pub fn session_timeline(session_id: &str, _date: &str, time: &str) -> String {
format!("cortex://session/{}/timeline/{}.md", session_id, time)
}
pub fn to_file_path(&self, root: &Path) -> PathBuf {
let mut path = root.to_path_buf();
path.push(self.dimension.as_str());
if !self.category.is_empty() {
path.push(&self.category);
}
if let Some(ref sub) = self.subcategory {
path.push(sub);
}
if let Some(ref res) = self.resource {
path.push(res);
}
path
}
pub fn directory_uri(&self) -> String {
let mut uri = format!("cortex://{}", self.dimension.as_str());
if !self.category.is_empty() {
uri.push('/');
uri.push_str(&self.category);
}
if let Some(ref sub) = self.subcategory {
uri.push('/');
uri.push_str(sub);
}
uri
}
pub fn to_uri_string(&self) -> String {
let mut uri = format!("cortex://{}", self.dimension.as_str());
if !self.category.is_empty() {
uri.push('/');
uri.push_str(&self.category);
}
if let Some(ref sub) = self.subcategory {
uri.push('/');
uri.push_str(sub);
}
if let Some(ref res) = self.resource {
uri.push('/');
uri.push_str(res);
}
if !self.params.is_empty() {
uri.push('?');
let params: Vec<String> = self
.params
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect();
uri.push_str(¶ms.join("&"));
}
uri
}
}
pub struct UriParser;
impl UriParser {
pub fn parse(uri: &str) -> Result<CortexUri> {
if !uri.starts_with("cortex://") {
return Err(Error::InvalidScheme);
}
let uri_without_scheme = &uri[9..]; let (path_part, query_part) = uri_without_scheme
.split_once('?')
.map(|(p, q)| (p, Some(q)))
.unwrap_or((uri_without_scheme, None));
let parts: Vec<&str> = path_part.split('/').filter(|s| !s.is_empty()).collect();
if parts.is_empty() {
return Err(Error::InvalidPath);
}
let dimension = Dimension::from_str(parts[0])
.ok_or_else(|| Error::InvalidDimension(parts[0].to_string()))?;
let category = parts.get(1).map(|s| s.to_string()).unwrap_or_default();
let (subcategory, resource) = if parts.len() <= 2 {
(None, None)
} else if parts.len() == 3 {
if parts[2].contains('.') {
(None, Some(parts[2].to_string()))
} else {
(Some(parts[2].to_string()), None)
}
} else {
let sub = parts[2].to_string();
let res = parts[3..].join("/");
(Some(sub), Some(res))
};
let params = Self::parse_query_params(query_part);
Ok(CortexUri {
dimension,
category,
subcategory,
resource,
params,
})
}
fn parse_query_params(query: Option<&str>) -> HashMap<String, String> {
query
.map(|q| {
q.split('&')
.filter_map(|pair| {
let mut parts = pair.split('=');
Some((parts.next()?.to_string(), parts.next()?.to_string()))
})
.collect()
})
.unwrap_or_default()
}
}