use std::path::{Path, PathBuf};
use crate::memory::error::{MemoryEngineResult, MemoryError};
use super::types::{MemorySourceEntry, SourceKind};
pub fn validate_entry(entry: &MemorySourceEntry) -> Result<(), String> {
if entry.id.is_empty() {
return Err("id is required".to_string());
}
if entry.label.is_empty() {
return Err("label is required".to_string());
}
match entry.kind {
SourceKind::Composio => {
require_field(&entry.toolkit, "toolkit")?;
require_field(&entry.connection_id, "connection_id")?;
}
SourceKind::Conversation => {
}
SourceKind::Folder => {
require_field(&entry.path, "path")?;
}
SourceKind::GithubRepo => {
require_field(&entry.url, "url")?;
}
SourceKind::TwitterQuery => {
require_field(&entry.query, "query")?;
}
SourceKind::RssFeed => {
require_field(&entry.url, "url")?;
}
SourceKind::WebPage => {
require_field(&entry.url, "url")?;
}
}
Ok(())
}
fn require_field(value: &Option<String>, name: &str) -> Result<(), String> {
match value {
Some(v) if !v.is_empty() => Ok(()),
_ => Err(format!("{name} is required for this source kind")),
}
}
pub fn ensure_within_base(base: &Path, target: &Path) -> MemoryEngineResult<PathBuf> {
let canonical_base = std::fs::canonicalize(base)?;
let canonical_target = std::fs::canonicalize(target)?;
if !canonical_target.starts_with(&canonical_base) {
return Err(MemoryError::PathEscape("path traversal denied".to_string()));
}
Ok(canonical_target)
}
#[cfg(test)]
#[path = "validation_tests.rs"]
mod tests;