use std::io;
use std::path::PathBuf;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, AgentMemoryError>;
#[derive(Debug, Error)]
pub enum AgentMemoryError {
#[error(transparent)]
Validation(#[from] ValidationError),
#[error(transparent)]
Config(#[from] ConfigError),
#[error(transparent)]
Store(#[from] StoreError),
#[error(transparent)]
Lock(#[from] LockError),
#[error(transparent)]
NotFound(#[from] NotFoundError),
#[error("I/O error: {source}")]
Io {
#[source]
source: io::Error,
},
#[error("overflow while {context}")]
Overflow {
context: &'static str,
},
#[error("internal invariant violation: {message}")]
Internal {
message: &'static str,
},
}
impl AgentMemoryError {
#[must_use]
pub fn io(source: io::Error) -> Self {
Self::Io { source }
}
#[must_use]
pub const fn overflow(context: &'static str) -> Self {
Self::Overflow { context }
}
#[must_use]
pub const fn internal(message: &'static str) -> Self {
Self::Internal { message }
}
}
impl From<io::Error> for AgentMemoryError {
fn from(source: io::Error) -> Self {
Self::Io { source }
}
}
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum ValidationError {
#[error("{field} must not be empty")]
Empty {
field: &'static str,
},
#[error("{field} exceeds maximum length: actual={actual}, max={max}")]
TooLong {
field: &'static str,
actual: usize,
max: usize,
},
#[error("{field} is below minimum length: actual={actual}, min={min}")]
TooShort {
field: &'static str,
actual: usize,
min: usize,
},
#[error("{field} contains invalid character {character:?} at byte index {index}")]
InvalidCharacter {
field: &'static str,
character: char,
index: usize,
},
#[error("{field} contains invalid encoding")]
InvalidEncoding {
field: &'static str,
},
#[error("invalid path for {field}: {reason}")]
InvalidPath {
field: &'static str,
reason: &'static str,
},
#[error("invalid segment in {field}: {reason}")]
InvalidSegment {
field: &'static str,
reason: &'static str,
},
#[error("invalid {field}: {reason}")]
InvalidFormat {
field: &'static str,
reason: &'static str,
},
}
impl ValidationError {
#[must_use]
pub const fn empty(field: &'static str) -> Self {
Self::Empty { field }
}
#[must_use]
pub const fn too_long(field: &'static str, actual: usize, max: usize) -> Self {
Self::TooLong { field, actual, max }
}
#[must_use]
pub const fn too_short(field: &'static str, actual: usize, min: usize) -> Self {
Self::TooShort { field, actual, min }
}
#[must_use]
pub const fn invalid_path(field: &'static str, reason: &'static str) -> Self {
Self::InvalidPath { field, reason }
}
#[must_use]
pub const fn invalid_segment(field: &'static str, reason: &'static str) -> Self {
Self::InvalidSegment { field, reason }
}
#[must_use]
pub const fn invalid_format(field: &'static str, reason: &'static str) -> Self {
Self::InvalidFormat { field, reason }
}
}
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum ConfigError {
#[error("missing required configuration field: {field}")]
MissingField {
field: &'static str,
},
#[error("unsupported configuration version: {version}")]
UnsupportedVersion {
version: u32,
},
#[error("malformed configuration: {reason}")]
Malformed {
reason: &'static str,
},
#[error("invalid project name: {reason}")]
InvalidProjectName {
reason: &'static str,
},
#[error("invalid store path: {reason}")]
InvalidStorePath {
reason: &'static str,
},
#[error("failed to parse configuration: {message}")]
Parse {
message: String,
},
}
#[derive(Debug, Error)]
pub enum StoreError {
#[error("store is not initialized")]
NotInitialized,
#[error("store file does not exist: {path}")]
MissingFile {
path: PathBuf,
},
#[error("store file is malformed: {reason}")]
Malformed {
reason: String,
},
#[error("unsupported store format version: {version}")]
UnsupportedVersion {
version: u32,
},
#[error("failed to prepare store path {path}: {source}")]
PreparePath {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("failed to read store {path}: {source}")]
Read {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("failed to write store {path}: {source}")]
Write {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("atomic persist failed for {path}: {reason}")]
AtomicPersist {
path: PathBuf,
reason: String,
},
#[error(transparent)]
Lock(#[from] LockError),
#[error("failed to serialize store: {message}")]
Serialize {
message: String,
},
#[error("failed to deserialize store: {message}")]
Deserialize {
message: String,
},
}
impl StoreError {
#[must_use]
pub fn missing_file(path: impl Into<PathBuf>) -> Self {
Self::MissingFile { path: path.into() }
}
#[must_use]
pub fn malformed(reason: impl Into<String>) -> Self {
Self::Malformed {
reason: reason.into(),
}
}
#[must_use]
pub fn read(path: impl Into<PathBuf>, source: io::Error) -> Self {
Self::Read {
path: path.into(),
source,
}
}
#[must_use]
pub fn write(path: impl Into<PathBuf>, source: io::Error) -> Self {
Self::Write {
path: path.into(),
source,
}
}
#[must_use]
pub fn atomic_persist(path: impl Into<PathBuf>, reason: impl Into<String>) -> Self {
Self::AtomicPersist {
path: path.into(),
reason: reason.into(),
}
}
}
#[derive(Debug, Error)]
pub enum LockError {
#[error("store lock is already held: {path}")]
AlreadyHeld {
path: PathBuf,
},
#[error("timed out while acquiring lock: {path}")]
Timeout {
path: PathBuf,
},
#[error("failed to acquire lock for {path}: {source}")]
Acquire {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("failed to release lock for {path}: {source}")]
Release {
path: PathBuf,
#[source]
source: io::Error,
},
}
#[derive(Debug, Error, Clone, PartialEq, Eq)]
#[error("{kind} not found: {identifier}")]
pub struct NotFoundError {
pub kind: &'static str,
pub identifier: String,
}
impl NotFoundError {
#[must_use]
pub fn new(kind: &'static str, identifier: impl Into<String>) -> Self {
Self {
kind,
identifier: identifier.into(),
}
}
}