use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DhtError {
NotFound {
agent_uri: String,
},
AlreadyRegistered {
agent_uri: String,
},
Expired {
agent_uri: String,
},
InvalidAttestation {
agent_uri: String,
reason: String,
},
CapabilityMismatch {
claimed: String,
attested: String,
},
IdentityCapabilityConflict {
identity: String,
existing_path: String,
requested_path: String,
},
KeyCapacityExceeded {
key: String,
max: usize,
},
NoEndpoints,
Internal {
message: String,
},
}
impl fmt::Display for DhtError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotFound { agent_uri } => {
write!(
f,
"agent '{agent_uri}' not found in DHT; verify the URI is correct and the agent is registered"
)
}
Self::AlreadyRegistered { agent_uri } => {
write!(
f,
"agent '{agent_uri}' is already registered; use update_endpoint to modify the registration"
)
}
Self::Expired { agent_uri } => {
write!(
f,
"registration for agent '{agent_uri}' has expired; re-register to restore"
)
}
Self::InvalidAttestation { agent_uri, reason } => {
write!(f, "invalid attestation for agent '{agent_uri}': {reason}")
}
Self::CapabilityMismatch { claimed, attested } => {
write!(
f,
"capability mismatch: claimed path '{claimed}' is not covered by attested path '{attested}'"
)
}
Self::IdentityCapabilityConflict {
identity,
existing_path,
requested_path,
} => {
write!(
f,
"agent identity '{identity}' is already bound to capability path \
'{existing_path}' and cannot be reused at '{requested_path}'; mint a new agent ID"
)
}
Self::KeyCapacityExceeded { key, max } => {
write!(
f,
"DHT key '{key}' has reached maximum capacity of {max} registrations"
)
}
Self::NoEndpoints => {
write!(f, "registration must have at least one endpoint")
}
Self::Internal { message } => {
write!(f, "internal DHT error: {message}")
}
}
}
}
impl std::error::Error for DhtError {}
impl DhtError {
#[must_use]
pub fn not_found(agent_uri: impl Into<String>) -> Self {
Self::NotFound {
agent_uri: agent_uri.into(),
}
}
#[must_use]
pub fn already_registered(agent_uri: impl Into<String>) -> Self {
Self::AlreadyRegistered {
agent_uri: agent_uri.into(),
}
}
#[must_use]
pub fn expired(agent_uri: impl Into<String>) -> Self {
Self::Expired {
agent_uri: agent_uri.into(),
}
}
#[must_use]
pub fn invalid_attestation(agent_uri: impl Into<String>, reason: impl Into<String>) -> Self {
Self::InvalidAttestation {
agent_uri: agent_uri.into(),
reason: reason.into(),
}
}
#[must_use]
pub fn capability_mismatch(claimed: impl Into<String>, attested: impl Into<String>) -> Self {
Self::CapabilityMismatch {
claimed: claimed.into(),
attested: attested.into(),
}
}
#[must_use]
pub fn key_capacity_exceeded(key: impl Into<String>, max: usize) -> Self {
Self::KeyCapacityExceeded {
key: key.into(),
max,
}
}
#[must_use]
pub fn internal(message: impl Into<String>) -> Self {
Self::Internal {
message: message.into(),
}
}
#[must_use]
pub const fn is_not_found(&self) -> bool {
matches!(self, Self::NotFound { .. })
}
#[must_use]
pub const fn is_expired(&self) -> bool {
matches!(self, Self::Expired { .. })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn not_found_error_display() {
let err = DhtError::not_found("agent://example.com/test/agent_123");
assert!(err.to_string().contains("not found"));
assert!(err.is_not_found());
}
#[test]
fn already_registered_error_display() {
let err = DhtError::already_registered("agent://example.com/test/agent_123");
assert!(err.to_string().contains("already registered"));
}
#[test]
fn expired_error_display() {
let err = DhtError::expired("agent://example.com/test/agent_123");
assert!(err.to_string().contains("expired"));
assert!(err.is_expired());
}
#[test]
fn no_endpoints_error_display() {
let err = DhtError::NoEndpoints;
assert!(err.to_string().contains("at least one endpoint"));
}
#[test]
fn key_capacity_exceeded_error_display() {
let err = DhtError::key_capacity_exceeded("abc123...", 20);
assert!(err.to_string().contains("maximum capacity"));
assert!(err.to_string().contains("20"));
}
}