use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
pub enum ErrorCode {
UnsupportedEnvelopeVersion = 1000,
InvalidEnvelopeStructure = 1001,
SignatureInvalid = 1100,
SignatureAlgorithmMismatch = 1101,
UnsupportedAlgorithm = 1102,
InvalidKeyLength = 1103,
InvalidSignatureLength = 1104,
UnsupportedPayloadVersion = 1200,
InvalidPayloadStructure = 1201,
MalformedCBOR = 1202,
UnknownPayloadField = 1203,
MissingRequiredField = 1204,
WarrantExpired = 1300,
WarrantNotYetValid = 1301,
IssuedInFuture = 1302,
TTLExceeded = 1303,
InvalidIssuer = 1400,
ParentHashMismatch = 1401,
DepthExceeded = 1402,
DepthViolation = 1403,
ChainTooLong = 1404,
ChainBroken = 1405,
UntrustedRoot = 1406,
ToolNotAuthorized = 1500,
ConstraintViolation = 1501,
InvalidAttenuation = 1502,
CapabilityExpansion = 1503,
UnknownConstraintType = 1504,
PopSignatureInvalid = 1600,
PopExpired = 1601,
PopChallengeInvalid = 1602,
InsufficientApprovals = 1700,
ApprovalInvalid = 1701,
ApproverNotAuthorized = 1702,
ApprovalExpired = 1703,
UnsupportedApprovalVersion = 1704,
ApprovalPayloadInvalid = 1705,
ApprovalRequestHashMismatch = 1706,
ApprovalRequired = 1707,
WarrantRevoked = 1800,
SRLInvalid = 1801,
SRLVersionRollback = 1802,
WarrantTooLarge = 1900,
ChainTooLarge = 1901,
TooManyTools = 1902,
TooManyConstraints = 1903,
ExtensionTooLarge = 1904,
ValueTooLarge = 1905,
ReservedExtensionKey = 2000,
InvalidExtensionValue = 2001,
ReservedToolName = 2100,
}
impl ErrorCode {
pub fn code(self) -> u16 {
self as u16
}
pub fn name(self) -> &'static str {
match self {
Self::UnsupportedEnvelopeVersion => "unsupported-envelope-version",
Self::InvalidEnvelopeStructure => "invalid-envelope-structure",
Self::SignatureInvalid => "signature-invalid",
Self::SignatureAlgorithmMismatch => "signature-algorithm-mismatch",
Self::UnsupportedAlgorithm => "unsupported-algorithm",
Self::InvalidKeyLength => "invalid-key-length",
Self::InvalidSignatureLength => "invalid-signature-length",
Self::UnsupportedPayloadVersion => "unsupported-payload-version",
Self::InvalidPayloadStructure => "invalid-payload-structure",
Self::MalformedCBOR => "malformed-cbor",
Self::UnknownPayloadField => "unknown-payload-field",
Self::MissingRequiredField => "missing-required-field",
Self::WarrantExpired => "warrant-expired",
Self::WarrantNotYetValid => "warrant-not-yet-valid",
Self::IssuedInFuture => "issued-in-future",
Self::TTLExceeded => "ttl-exceeded",
Self::InvalidIssuer => "invalid-issuer",
Self::ParentHashMismatch => "parent-hash-mismatch",
Self::DepthExceeded => "depth-exceeded",
Self::DepthViolation => "depth-violation",
Self::ChainTooLong => "chain-too-long",
Self::ChainBroken => "chain-broken",
Self::UntrustedRoot => "untrusted-root",
Self::ToolNotAuthorized => "tool-not-authorized",
Self::ConstraintViolation => "constraint-violation",
Self::InvalidAttenuation => "invalid-attenuation",
Self::CapabilityExpansion => "capability-expansion",
Self::UnknownConstraintType => "unknown-constraint-type",
Self::PopSignatureInvalid => "pop-signature-invalid",
Self::PopExpired => "pop-expired",
Self::PopChallengeInvalid => "pop-challenge-invalid",
Self::InsufficientApprovals => "insufficient-approvals",
Self::ApprovalInvalid => "approval-invalid",
Self::ApproverNotAuthorized => "approver-not-authorized",
Self::ApprovalExpired => "approval-expired",
Self::UnsupportedApprovalVersion => "unsupported-approval-version",
Self::ApprovalPayloadInvalid => "approval-payload-invalid",
Self::ApprovalRequestHashMismatch => "approval-request-hash-mismatch",
Self::ApprovalRequired => "approval-required",
Self::WarrantRevoked => "warrant-revoked",
Self::SRLInvalid => "srl-invalid",
Self::SRLVersionRollback => "srl-version-rollback",
Self::WarrantTooLarge => "warrant-too-large",
Self::ChainTooLarge => "chain-too-large",
Self::TooManyTools => "too-many-tools",
Self::TooManyConstraints => "too-many-constraints",
Self::ExtensionTooLarge => "extension-too-large",
Self::ValueTooLarge => "value-too-large",
Self::ReservedExtensionKey => "reserved-extension-key",
Self::InvalidExtensionValue => "invalid-extension-value",
Self::ReservedToolName => "reserved-tool-name",
}
}
pub fn http_status(self) -> u16 {
match self.code() / 100 {
10 => 400, 11 => 401, 12 => 400, 13 => 401, 14 => 403, 15 => 403, 16 => 401, 17 => 403, 18 => 401, 19 => 413, 20 => 400, 21 => 400, _ => 500, }
}
pub fn description(self) -> &'static str {
match self {
Self::UnsupportedEnvelopeVersion => "Envelope version not supported",
Self::InvalidEnvelopeStructure => "Envelope structure is invalid",
Self::SignatureInvalid => "Signature verification failed",
Self::SignatureAlgorithmMismatch => "Signature algorithm does not match key type",
Self::UnsupportedAlgorithm => "Cryptographic algorithm not supported",
Self::InvalidKeyLength => "Cryptographic key length is invalid",
Self::InvalidSignatureLength => "Signature length is invalid",
Self::UnsupportedPayloadVersion => "Payload version not supported",
Self::InvalidPayloadStructure => "Payload structure is invalid",
Self::MalformedCBOR => "CBOR serialization is malformed",
Self::UnknownPayloadField => "Unknown field in payload",
Self::MissingRequiredField => "Required field is missing",
Self::WarrantExpired => "Warrant has expired",
Self::WarrantNotYetValid => "Warrant is not yet valid",
Self::IssuedInFuture => "Warrant issued_at is in the future",
Self::TTLExceeded => "Warrant TTL exceeded",
Self::InvalidIssuer => "Issuer is invalid",
Self::ParentHashMismatch => "Parent hash does not match",
Self::DepthExceeded => "Delegation depth exceeded",
Self::DepthViolation => "Delegation depth constraint violated",
Self::ChainTooLong => "Warrant chain too long",
Self::ChainBroken => "Warrant chain verification failed",
Self::UntrustedRoot => "Root issuer not trusted",
Self::ToolNotAuthorized => "Tool not authorized by warrant",
Self::ConstraintViolation => "Constraint not satisfied",
Self::InvalidAttenuation => "Attenuation is invalid",
Self::CapabilityExpansion => "Attenuation would expand capabilities",
Self::UnknownConstraintType => "Constraint type not recognized",
Self::PopSignatureInvalid => "Proof-of-Possession signature invalid",
Self::PopExpired => "Proof-of-Possession outside replay window",
Self::PopChallengeInvalid => "Proof-of-Possession challenge invalid",
Self::InsufficientApprovals => "Insufficient approvals for multi-sig",
Self::ApprovalInvalid => "Approval is invalid",
Self::ApproverNotAuthorized => "Approver not authorized",
Self::ApprovalExpired => "Approval has expired",
Self::UnsupportedApprovalVersion => "Approval version not supported",
Self::ApprovalPayloadInvalid => "Approval payload is invalid",
Self::ApprovalRequestHashMismatch => "Approval request hash mismatch",
Self::ApprovalRequired => "Approval required for this action",
Self::WarrantRevoked => "Warrant has been revoked",
Self::SRLInvalid => "Signature Revocation List is invalid",
Self::SRLVersionRollback => "SRL version rollback detected",
Self::WarrantTooLarge => "Warrant size exceeds limit",
Self::ChainTooLarge => "Warrant chain size exceeds limit",
Self::TooManyTools => "Too many tools in warrant",
Self::TooManyConstraints => "Too many constraints",
Self::ExtensionTooLarge => "Extension size exceeds limit",
Self::ValueTooLarge => "Value size exceeds limit",
Self::ReservedExtensionKey => "Extension key is reserved",
Self::InvalidExtensionValue => "Extension value is invalid",
Self::ReservedToolName => "Tool name is reserved",
}
}
}
#[derive(Error, Debug, Clone)]
#[non_exhaustive]
pub enum Error {
#[error("signature verification failed: {0}")]
SignatureInvalid(String),
#[error("missing signature: {0}")]
MissingSignature(String),
#[error("cryptographic error: {0}")]
CryptoError(String),
#[error("warrant revoked: {0}")]
WarrantRevoked(String),
#[error("warrant expired at {0}")]
WarrantExpired(chrono::DateTime<chrono::Utc>),
#[error("warrant issued in the future (check system clock)")]
IssuedInFuture,
#[error("delegation depth {0} exceeds maximum {1}")]
DepthExceeded(u32, u32),
#[error("invalid warrant ID: {0}")]
InvalidWarrantId(String),
#[error("invalid TTL: {0}")]
InvalidTtl(String),
#[error("constraint depth {depth} exceeds maximum {max}")]
ConstraintDepthExceeded { depth: u32, max: u32 },
#[error("payload size {size} bytes exceeds maximum {max} bytes")]
PayloadTooLarge { size: usize, max: usize },
#[error("parent warrant required for attenuation")]
ParentRequired,
#[error("tool name mismatch: parent has '{parent}', child has '{child}'")]
ToolMismatch { parent: String, child: String },
#[error("attenuation would expand capabilities: {0}")]
MonotonicityViolation(String),
#[error("incompatible constraint types: cannot attenuate {parent_type} to {child_type}")]
IncompatibleConstraintTypes {
parent_type: String,
child_type: String,
},
#[error("cannot attenuate to Wildcard: this would expand permissions from {parent_type}")]
WildcardExpansion { parent_type: String },
#[error("empty result set: NotOneOf excludes all {count} values from parent {parent_type}")]
EmptyResultSet { parent_type: String, count: usize },
#[error("exclusion removed: child must still exclude '{value}'")]
ExclusionRemoved { value: String },
#[error("value not allowed: '{value}' is not in parent's allowed set")]
ValueNotInParentSet { value: String },
#[error(
"range expanded: child {bound} ({child_value}) exceeds parent {bound} ({parent_value})"
)]
RangeExpanded {
bound: String, parent_value: f64,
child_value: f64,
},
#[error(
"range inclusivity expanded: child made {bound} ({value}) inclusive when parent was exclusive"
)]
RangeInclusivityExpanded {
bound: String, value: f64,
parent_inclusive: bool,
child_inclusive: bool,
},
#[error("value {value} not in range [{min:?}, {max:?}]")]
ValueNotInRange {
value: f64,
min: Option<f64>,
max: Option<f64>,
},
#[error("pattern attenuation cannot be verified: parent pattern '{parent}' has multiple wildcards (child: '{child}'). Use UrlPattern for URL constraints, or use exact equality")]
PatternExpanded { parent: String, child: String },
#[error("invalid CIDR: '{cidr}' - {reason}")]
InvalidCidr { cidr: String, reason: String },
#[error("invalid IP address: '{ip}' - {reason}")]
InvalidIpAddress { ip: String, reason: String },
#[error("IP address '{ip}' not in CIDR range '{cidr}'")]
IpNotInCidr { ip: String, cidr: String },
#[error("CIDR not subnet: '{child}' is not a subnet of '{parent}'")]
CidrNotSubnet { parent: String, child: String },
#[error("invalid URL: '{url}' - {reason}")]
InvalidUrl { url: String, reason: String },
#[error("URL scheme expanded: child scheme '{child}' not allowed by parent scheme '{parent}'")]
UrlSchemeExpanded { parent: String, child: String },
#[error("URL host expanded: child host '{child}' not allowed by parent host '{parent}'")]
UrlHostExpanded { parent: String, child: String },
#[error("URL port expanded: child port '{child:?}' not allowed by parent port '{parent:?}'")]
UrlPortExpanded {
parent: Option<u16>,
child: Option<u16>,
},
#[error("URL path expanded: child path '{child}' not allowed by parent path '{parent}'")]
UrlPathExpanded { parent: String, child: String },
#[error("URL does not match: {reason}")]
UrlMismatch { reason: String },
#[error("path '{path}' is not contained within root '{root}'")]
PathNotContained { path: String, root: String },
#[error("invalid path '{path}': {reason}")]
InvalidPath { path: String, reason: String },
#[error("URL '{url}' blocked: {reason}")]
UrlNotSafe { url: String, reason: String },
#[error("required value removed: child must still require '{value}'")]
RequiredValueRemoved { value: String },
#[error("exact value mismatch: parent requires '{parent}', child has '{child}'")]
ExactValueMismatch { parent: String, child: String },
#[error("constraint not satisfied: {field} - {reason}")]
ConstraintNotSatisfied { field: String, reason: String },
#[error("insufficient clearance: tool '{tool}' requires {required}, warrant has {actual}")]
InsufficientClearance {
tool: String,
required: String,
actual: String,
},
#[error("invalid pattern: {0}")]
InvalidPattern(String),
#[error("invalid range: {0}")]
InvalidRange(String),
#[error("invalid regex: {0}")]
InvalidRegex(String),
#[error("CEL expression error: {0}")]
CelError(String),
#[error("serialization error: {0}")]
SerializationError(String),
#[error("deserialization error: {0}")]
DeserializationError(String),
#[error("unsupported wire format version: {0}")]
UnsupportedVersion(u8),
#[error("missing required field: {0}")]
MissingField(String),
#[error("chain verification failed: {0}")]
ChainVerificationFailed(String),
#[error("clearance level exceeded: requested {requested:?} exceeds limit {limit:?}")]
ClearanceLevelExceeded { requested: String, limit: String },
#[error("unauthorized tool issuance: '{tool}' not in issuable_tools {allowed:?}")]
UnauthorizedToolIssuance { tool: String, allowed: Vec<String> },
#[error("self-issuance prohibited: {reason}")]
SelfIssuanceProhibited { reason: String },
#[error("delegation authority error: expected signer {expected}, got {actual}")]
DelegationAuthorityError { expected: String, actual: String },
#[error("issue depth exceeded: depth {depth} exceeds max_issue_depth {max}")]
IssueDepthExceeded { depth: u32, max: u32 },
#[error("invalid warrant type: {message}")]
InvalidWarrantType { message: String },
#[error("issuer chain too long: length {length} would exceed maximum {max}")]
IssuerChainTooLong { length: usize, max: usize },
#[error("approval expired: approved at {approved_at}, expired at {expired_at}")]
ApprovalExpired {
approved_at: chrono::DateTime<chrono::Utc>,
expired_at: chrono::DateTime<chrono::Utc>,
},
#[error("insufficient approvals: required {required}, received {received}{}", .detail.as_deref().unwrap_or(""))]
InsufficientApprovals {
required: u32,
received: u32,
detail: Option<String>,
},
#[error("invalid approval: {0}")]
InvalidApproval(String),
#[error("approval required for tool '{tool}'")]
ApprovalRequired {
tool: String,
request: Box<crate::approval::ApprovalRequest>,
},
#[error("unknown approval provider: {0}")]
UnknownProvider(String),
#[error("unauthorized: {0}")]
Unauthorized(String),
#[error("validation error: {0}")]
Validation(String),
#[error("configuration error: {0}")]
ConfigurationError(String),
#[error("{feature} requires the '{feature}' feature. Enable with: tenuo = {{ features = [\"{feature}\"] }}")]
FeatureNotEnabled { feature: &'static str },
}
impl From<ciborium::ser::Error<std::io::Error>> for Error {
fn from(e: ciborium::ser::Error<std::io::Error>) -> Self {
Error::SerializationError(e.to_string())
}
}
impl From<ciborium::de::Error<std::io::Error>> for Error {
fn from(e: ciborium::de::Error<std::io::Error>) -> Self {
Error::DeserializationError(e.to_string())
}
}
impl From<ed25519_dalek::SignatureError> for Error {
fn from(e: ed25519_dalek::SignatureError) -> Self {
Error::CryptoError(e.to_string())
}
}
impl Error {
pub fn code(&self) -> ErrorCode {
match self {
Self::SignatureInvalid(_) => ErrorCode::SignatureInvalid,
Self::MissingSignature(_) => ErrorCode::SignatureInvalid,
Self::CryptoError(_) => ErrorCode::SignatureInvalid,
Self::WarrantRevoked(_) => ErrorCode::WarrantRevoked,
Self::WarrantExpired(_) => ErrorCode::WarrantExpired,
Self::IssuedInFuture => ErrorCode::IssuedInFuture,
Self::DepthExceeded(_, _) => ErrorCode::DepthExceeded,
Self::InvalidWarrantId(_) => ErrorCode::InvalidPayloadStructure,
Self::InvalidTtl(_) => ErrorCode::TTLExceeded,
Self::ConstraintDepthExceeded { .. } => ErrorCode::TooManyConstraints,
Self::PayloadTooLarge { .. } => ErrorCode::WarrantTooLarge,
Self::ParentRequired => ErrorCode::MissingRequiredField,
Self::ToolMismatch { .. } => ErrorCode::InvalidAttenuation,
Self::MonotonicityViolation(_) => ErrorCode::InvalidAttenuation,
Self::IncompatibleConstraintTypes { .. } => ErrorCode::InvalidAttenuation,
Self::WildcardExpansion { .. } => ErrorCode::CapabilityExpansion,
Self::EmptyResultSet { .. } => ErrorCode::InvalidAttenuation,
Self::ExclusionRemoved { .. } => ErrorCode::CapabilityExpansion,
Self::ValueNotInParentSet { .. } => ErrorCode::CapabilityExpansion,
Self::RangeExpanded { .. } => ErrorCode::CapabilityExpansion,
Self::RangeInclusivityExpanded { .. } => ErrorCode::CapabilityExpansion,
Self::ValueNotInRange { .. } => ErrorCode::ConstraintViolation,
Self::PatternExpanded { .. } => ErrorCode::CapabilityExpansion,
Self::InvalidCidr { .. } => ErrorCode::ConstraintViolation,
Self::InvalidIpAddress { .. } => ErrorCode::ConstraintViolation,
Self::IpNotInCidr { .. } => ErrorCode::ConstraintViolation,
Self::CidrNotSubnet { .. } => ErrorCode::CapabilityExpansion,
Self::InvalidUrl { .. } => ErrorCode::ConstraintViolation,
Self::UrlSchemeExpanded { .. } => ErrorCode::CapabilityExpansion,
Self::UrlHostExpanded { .. } => ErrorCode::CapabilityExpansion,
Self::UrlPortExpanded { .. } => ErrorCode::CapabilityExpansion,
Self::UrlPathExpanded { .. } => ErrorCode::CapabilityExpansion,
Self::UrlMismatch { .. } => ErrorCode::ConstraintViolation,
Self::PathNotContained { .. } => ErrorCode::ConstraintViolation,
Self::InvalidPath { .. } => ErrorCode::ConstraintViolation,
Self::UrlNotSafe { .. } => ErrorCode::ConstraintViolation,
Self::RequiredValueRemoved { .. } => ErrorCode::CapabilityExpansion,
Self::ExactValueMismatch { .. } => ErrorCode::InvalidAttenuation,
Self::ConstraintNotSatisfied { .. } => ErrorCode::ConstraintViolation,
Self::InsufficientClearance { .. } => ErrorCode::ToolNotAuthorized,
Self::InvalidPattern(_) => ErrorCode::ConstraintViolation,
Self::InvalidRange(_) => ErrorCode::ConstraintViolation,
Self::InvalidRegex(_) => ErrorCode::ConstraintViolation,
Self::CelError(_) => ErrorCode::ConstraintViolation,
Self::SerializationError(_) => ErrorCode::MalformedCBOR,
Self::DeserializationError(_) => ErrorCode::MalformedCBOR,
Self::UnsupportedVersion(_) => ErrorCode::UnsupportedPayloadVersion,
Self::MissingField(_) => ErrorCode::MissingRequiredField,
Self::ChainVerificationFailed(_) => ErrorCode::ChainBroken,
Self::ClearanceLevelExceeded { .. } => ErrorCode::ToolNotAuthorized,
Self::UnauthorizedToolIssuance { .. } => ErrorCode::ToolNotAuthorized,
Self::SelfIssuanceProhibited { .. } => ErrorCode::InvalidIssuer,
Self::DelegationAuthorityError { .. } => ErrorCode::InvalidIssuer,
Self::IssueDepthExceeded { .. } => ErrorCode::DepthExceeded,
Self::InvalidWarrantType { .. } => ErrorCode::InvalidPayloadStructure,
Self::IssuerChainTooLong { .. } => ErrorCode::ChainTooLong,
Self::ApprovalExpired { .. } => ErrorCode::ApprovalExpired,
Self::InsufficientApprovals { .. } => ErrorCode::InsufficientApprovals,
Self::InvalidApproval(_) => ErrorCode::ApprovalInvalid,
Self::ApprovalRequired { .. } => ErrorCode::ApprovalRequired,
Self::UnknownProvider(_) => ErrorCode::ApprovalInvalid,
Self::Unauthorized(_) => ErrorCode::ToolNotAuthorized,
Self::Validation(_) => ErrorCode::InvalidPayloadStructure,
Self::ConfigurationError(_) => ErrorCode::InvalidPayloadStructure,
Self::FeatureNotEnabled { .. } => ErrorCode::UnknownConstraintType,
}
}
pub fn name(&self) -> &'static str {
self.code().name()
}
pub fn http_status(&self) -> u16 {
self.code().http_status()
}
pub fn description(&self) -> &'static str {
self.code().description()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_code_values() {
assert_eq!(ErrorCode::UnsupportedEnvelopeVersion.code(), 1000);
assert_eq!(ErrorCode::InvalidEnvelopeStructure.code(), 1001);
assert_eq!(ErrorCode::SignatureInvalid.code(), 1100);
assert_eq!(ErrorCode::SignatureAlgorithmMismatch.code(), 1101);
assert_eq!(ErrorCode::UnsupportedPayloadVersion.code(), 1200);
assert_eq!(ErrorCode::MalformedCBOR.code(), 1202);
assert_eq!(ErrorCode::WarrantExpired.code(), 1300);
assert_eq!(ErrorCode::WarrantNotYetValid.code(), 1301);
assert_eq!(ErrorCode::InvalidIssuer.code(), 1400);
assert_eq!(ErrorCode::DepthExceeded.code(), 1402);
assert_eq!(ErrorCode::ChainBroken.code(), 1405);
assert_eq!(ErrorCode::UntrustedRoot.code(), 1406);
assert_eq!(ErrorCode::ToolNotAuthorized.code(), 1500);
assert_eq!(ErrorCode::ConstraintViolation.code(), 1501);
assert_eq!(ErrorCode::InvalidAttenuation.code(), 1502);
assert_eq!(ErrorCode::CapabilityExpansion.code(), 1503);
assert_eq!(ErrorCode::PopSignatureInvalid.code(), 1600);
assert_eq!(ErrorCode::PopExpired.code(), 1601);
assert_eq!(ErrorCode::InsufficientApprovals.code(), 1700);
assert_eq!(ErrorCode::ApprovalInvalid.code(), 1701);
assert_eq!(ErrorCode::ApprovalExpired.code(), 1703);
assert_eq!(ErrorCode::WarrantRevoked.code(), 1800);
assert_eq!(ErrorCode::SRLVersionRollback.code(), 1802);
assert_eq!(ErrorCode::WarrantTooLarge.code(), 1900);
assert_eq!(ErrorCode::TooManyConstraints.code(), 1903);
assert_eq!(ErrorCode::ExtensionTooLarge.code(), 1904);
assert_eq!(ErrorCode::ReservedToolName.code(), 2100);
}
#[test]
fn test_error_code_names() {
assert_eq!(ErrorCode::SignatureInvalid.name(), "signature-invalid");
assert_eq!(ErrorCode::WarrantExpired.name(), "warrant-expired");
assert_eq!(
ErrorCode::ConstraintViolation.name(),
"constraint-violation"
);
assert_eq!(ErrorCode::WarrantRevoked.name(), "warrant-revoked");
assert_eq!(ErrorCode::ChainBroken.name(), "chain-broken");
assert_eq!(ErrorCode::DepthExceeded.name(), "depth-exceeded");
assert_eq!(ErrorCode::ToolNotAuthorized.name(), "tool-not-authorized");
}
#[test]
fn test_error_code_descriptions() {
assert_eq!(
ErrorCode::SignatureInvalid.description(),
"Signature verification failed"
);
assert_eq!(
ErrorCode::WarrantExpired.description(),
"Warrant has expired"
);
assert_eq!(
ErrorCode::ConstraintViolation.description(),
"Constraint not satisfied"
);
}
#[test]
fn test_error_code_http_status() {
assert_eq!(ErrorCode::SignatureInvalid.http_status(), 401);
assert_eq!(ErrorCode::WarrantExpired.http_status(), 401);
assert_eq!(ErrorCode::ChainBroken.http_status(), 403);
assert_eq!(ErrorCode::ConstraintViolation.http_status(), 403);
assert_eq!(ErrorCode::ToolNotAuthorized.http_status(), 403);
assert_eq!(ErrorCode::PopSignatureInvalid.http_status(), 401);
assert_eq!(ErrorCode::WarrantRevoked.http_status(), 401);
assert_eq!(ErrorCode::WarrantTooLarge.http_status(), 413);
}
#[test]
fn test_error_to_code_mapping() {
let err = Error::SignatureInvalid("test".into());
assert_eq!(err.code(), ErrorCode::SignatureInvalid);
assert_eq!(err.name(), "signature-invalid");
assert_eq!(err.http_status(), 401);
let err = Error::WarrantExpired(chrono::Utc::now());
assert_eq!(err.code(), ErrorCode::WarrantExpired);
assert_eq!(err.name(), "warrant-expired");
let err = Error::DepthExceeded(10, 5);
assert_eq!(err.code(), ErrorCode::DepthExceeded);
assert_eq!(err.name(), "depth-exceeded");
let err = Error::ConstraintNotSatisfied {
field: "amount".into(),
reason: "too large".into(),
};
assert_eq!(err.code(), ErrorCode::ConstraintViolation);
assert_eq!(err.name(), "constraint-violation");
let err = Error::RangeExpanded {
bound: "max".into(),
parent_value: 100.0,
child_value: 200.0,
};
assert_eq!(err.code(), ErrorCode::CapabilityExpansion);
let err = Error::WarrantRevoked("test".into());
assert_eq!(err.code(), ErrorCode::WarrantRevoked);
assert_eq!(err.name(), "warrant-revoked");
}
#[test]
fn test_all_error_variants_map() {
let test_errors = vec![
Error::SignatureInvalid("test".into()),
Error::MissingSignature("test".into()),
Error::CryptoError("test".into()),
Error::WarrantRevoked("test".into()),
Error::WarrantExpired(chrono::Utc::now()),
Error::DepthExceeded(1, 0),
Error::InvalidWarrantId("test".into()),
Error::InvalidTtl("test".into()),
Error::ConstraintDepthExceeded { depth: 10, max: 5 },
Error::PayloadTooLarge { size: 100, max: 50 },
Error::ParentRequired,
Error::ToolMismatch {
parent: "a".into(),
child: "b".into(),
},
Error::MonotonicityViolation("test".into()),
Error::IncompatibleConstraintTypes {
parent_type: "a".into(),
child_type: "b".into(),
},
Error::WildcardExpansion {
parent_type: "a".into(),
},
Error::EmptyResultSet {
parent_type: "a".into(),
count: 1,
},
Error::ConstraintNotSatisfied {
field: "a".into(),
reason: "b".into(),
},
Error::InvalidPattern("test".into()),
Error::InvalidRange("test".into()),
Error::InvalidRegex("test".into()),
Error::CelError("test".into()),
Error::SerializationError("test".into()),
Error::DeserializationError("test".into()),
Error::UnsupportedVersion(1),
Error::MissingField("test".into()),
Error::ChainVerificationFailed("test".into()),
Error::ApprovalExpired {
approved_at: chrono::Utc::now(),
expired_at: chrono::Utc::now(),
},
Error::InsufficientApprovals {
required: 2,
received: 1,
detail: None,
},
Error::InvalidApproval("test".into()),
Error::Unauthorized("test".into()),
Error::Validation("test".into()),
Error::ConfigurationError("test".into()),
];
for error in test_errors {
let code = error.code();
assert!(code.code() >= 1000 && code.code() < 2200);
}
}
#[test]
fn test_error_name_format() {
let codes = vec![
ErrorCode::SignatureInvalid,
ErrorCode::WarrantExpired,
ErrorCode::ConstraintViolation,
ErrorCode::ToolNotAuthorized,
ErrorCode::PopSignatureInvalid,
ErrorCode::UnsupportedPayloadVersion,
];
for code in codes {
let name = code.name();
assert!(
name.chars()
.all(|c| c.is_lowercase() || c.is_numeric() || c == '-'),
"Error name '{}' is not kebab-case",
name
);
assert!(!name.starts_with('-') && !name.ends_with('-'));
}
}
}