pub(crate) mod internal;
use std::net::SocketAddr;
use std::time::Duration;
use crate::oid::Oid;
use crate::v3::ReportStatus;
pub type Result<T> = std::result::Result<T, Box<Error>>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DecodeErrorOrigin {
Packet,
DecryptedScopedPdu,
}
impl std::fmt::Display for DecodeErrorOrigin {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Packet => f.write_str("packet"),
Self::DecryptedScopedPdu => f.write_str("decrypted scoped-PDU plaintext"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DecodeErrorKind {
UnexpectedTag {
expected: u8,
actual: u8,
},
TruncatedData,
InvalidLength,
IndefiniteLength,
IntegerOverflow,
IntegerOutOfRange {
value: i64,
minimum: i32,
maximum: i32,
},
UnsignedIntegerOutOfRange {
value: u64,
minimum: u32,
maximum: u32,
},
ZeroLengthInteger,
UnknownVersion(i32),
UnknownPduType(u8),
ConstructedOctetString,
MissingPdu,
InvalidMsgFlags,
InvalidMsgFlagsLength {
length: usize,
},
UnknownSecurityModel(i32),
InvalidUserNameLength {
length: usize,
},
InvalidNull,
InvalidIpAddressLength {
length: usize,
},
LengthTooLong {
octets: usize,
},
Integer64TooLong {
length: usize,
},
TlvOverflow,
InsufficientData {
needed: usize,
available: usize,
},
InvalidOid,
OidTooLong {
count: usize,
max: usize,
},
IntegerTooLong {
length: usize,
},
Unsigned32TooLong {
length: usize,
},
Integer64MissingLeadingZero,
Unsigned32MissingLeadingZero,
UnsupportedMultiOctetTag {
first_octet: u8,
},
TrailingData {
remaining: usize,
},
MessageTooLarge {
size: usize,
maximum: usize,
},
InvalidValue,
UnsupportedEncoding,
}
impl std::fmt::Display for DecodeErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnexpectedTag { expected, actual } => {
write!(f, "expected tag 0x{expected:02X}, got 0x{actual:02X}")
}
Self::TruncatedData => f.write_str("unexpected end of data"),
Self::InvalidLength => f.write_str("invalid length encoding"),
Self::IndefiniteLength => f.write_str("indefinite length encoding not supported"),
Self::IntegerOverflow => f.write_str("integer overflow"),
Self::IntegerOutOfRange {
value,
minimum,
maximum,
} => write!(
f,
"integer {value} outside constrained range {minimum}..{maximum}"
),
Self::UnsignedIntegerOutOfRange {
value,
minimum,
maximum,
} => write!(
f,
"unsigned integer {value} outside constrained range {minimum}..{maximum}"
),
Self::ZeroLengthInteger => f.write_str("zero-length integer"),
Self::UnknownVersion(value) => write!(f, "unknown SNMP version: {value}"),
Self::UnknownPduType(tag) => write!(f, "unknown PDU type: 0x{tag:02X}"),
Self::ConstructedOctetString => {
f.write_str("constructed OCTET STRING is not supported")
}
Self::MissingPdu => f.write_str("missing PDU in message"),
Self::InvalidMsgFlags => f.write_str("invalid msgFlags"),
Self::InvalidMsgFlagsLength { length } => {
write!(f, "msgFlags must contain exactly one octet, got {length}")
}
Self::UnknownSecurityModel(model) => write!(f, "unknown security model: {model}"),
Self::InvalidUserNameLength { length } => {
write!(f, "msgUserName length {length} exceeds maximum 32")
}
Self::InvalidNull => f.write_str("NULL with non-zero length"),
Self::InvalidIpAddressLength { length } => {
write!(f, "IP address must be 4 bytes, got {length}")
}
Self::LengthTooLong { octets } => {
write!(f, "length encoding too long ({octets} octets)")
}
Self::Integer64TooLong { length } => write!(f, "integer64 too long: {length} bytes"),
Self::TlvOverflow => f.write_str("TLV extends past end of data"),
Self::InsufficientData { needed, available } => {
write!(f, "need {needed} bytes but only {available} remaining")
}
Self::InvalidOid => f.write_str("invalid object identifier"),
Self::OidTooLong { count, max } => {
write!(f, "OID has {count} arcs, exceeds maximum {max}")
}
Self::IntegerTooLong { length } => {
write!(f, "integer encoding too long: {length} bytes (max 8)")
}
Self::Unsigned32TooLong { length } => {
write!(f, "unsigned32 encoding too long: {length} bytes (max 9)")
}
Self::Integer64MissingLeadingZero => {
f.write_str("9-octet integer64 missing required leading zero byte")
}
Self::Unsigned32MissingLeadingZero => {
f.write_str("9-octet unsigned32 input missing required leading zero byte")
}
Self::UnsupportedMultiOctetTag { first_octet } => {
write!(
f,
"unsupported multi-octet tag starting with 0x{first_octet:02X}"
)
}
Self::TrailingData { remaining } => write!(f, "{remaining} unconsumed bytes"),
Self::MessageTooLarge { size, maximum } => {
write!(f, "message size {size} exceeds receive limit {maximum}")
}
Self::InvalidValue => f.write_str("invalid encoded value"),
Self::UnsupportedEncoding => f.write_str("unsupported encoding"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("decode error at {origin} offset {offset}: {kind}{peer_suffix}", peer_suffix = peer.map(|value| format!(" from {value}")).unwrap_or_default())]
pub struct DecodeError {
pub origin: DecodeErrorOrigin,
pub offset: usize,
pub kind: DecodeErrorKind,
pub peer: Option<SocketAddr>,
}
impl DecodeError {
#[must_use]
pub const fn new(offset: usize, kind: DecodeErrorKind) -> Self {
Self {
origin: DecodeErrorOrigin::Packet,
offset,
kind,
peer: None,
}
}
#[must_use]
pub const fn with_origin(
origin: DecodeErrorOrigin,
offset: usize,
kind: DecodeErrorKind,
) -> Self {
Self {
origin,
offset,
kind,
peer: None,
}
}
#[must_use]
pub const fn with_peer(mut self, peer: SocketAddr) -> Self {
self.peer = Some(peer);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum WalkAbortReason {
NonIncreasing,
Cycle,
ResultLimitExceeded {
limit: usize,
},
}
impl std::fmt::Display for WalkAbortReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NonIncreasing => write!(f, "non-increasing OID"),
Self::Cycle => write!(f, "cycle detected"),
Self::ResultLimitExceeded { limit } => {
write!(f, "result limit of {limit} exceeded")
}
}
}
}
impl std::error::Error for WalkAbortReason {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ConstructionStage {
Resolve,
Bind,
Connect,
Prepare,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum ErrorKind {
Network,
Timeout,
ConstructionTimeout,
Closed,
RequestIdInUse,
OutboundMessageTooLarge,
Snmp,
Auth,
Report,
Decode,
MalformedResponse,
ResponseShape,
WalkAborted,
Config,
AuthoritativeEnginePersistence,
Privacy,
RandomSource,
AgentAlreadyRunning,
InvalidMessage,
InvalidOid,
}
impl ErrorKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Network => "network",
Self::Timeout => "timeout",
Self::ConstructionTimeout => "construction_timeout",
Self::Closed => "transport_closed",
Self::RequestIdInUse => "request_id_in_use",
Self::OutboundMessageTooLarge => "outbound_message_too_large",
Self::Snmp => "snmp",
Self::Auth => "authentication",
Self::Report => "v3_report",
Self::Decode => "decode",
Self::MalformedResponse => "malformed_response",
Self::ResponseShape => "response_shape",
Self::WalkAborted => "walk_aborted",
Self::Config => "configuration",
Self::AuthoritativeEnginePersistence => "authoritative_engine_persistence",
Self::Privacy => "privacy",
Self::RandomSource => "random_source",
Self::AgentAlreadyRunning => "agent_already_running",
Self::InvalidMessage => "invalid_message",
Self::InvalidOid => "invalid_oid",
}
}
}
impl std::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("{source}")]
SharedOperation {
#[source]
source: std::sync::Arc<Error>,
},
#[error("{source}")]
Exchange {
#[source]
source: Box<Error>,
metadata: Box<crate::client::ResponseMetadata>,
},
#[error("network error communicating with {target}: {source}")]
Network {
target: SocketAddr,
#[source]
source: std::io::Error,
},
#[error("timeout after {elapsed:?} waiting for {target} ({retries} retries)")]
Timeout {
target: SocketAddr,
elapsed: Duration,
retries: u32,
},
#[error("construction timed out during {stage:?} after {elapsed:?} for {target}")]
ConstructionTimeout {
target: crate::client::Target,
stage: ConstructionStage,
elapsed: Duration,
},
#[cfg(feature = "agent")]
#[error(
"Agent construction timed out during {stage:?} after {elapsed:?} for bind {bind_addr} (sink index {sink_index:?}, destination {sink_destination:?})"
)]
AgentConstructionTimeout {
bind_addr: SocketAddr,
stage: ConstructionStage,
sink_index: Option<usize>,
sink_destination: Option<String>,
elapsed: Duration,
},
#[error("transport closed while waiting for {target}")]
Closed { target: SocketAddr },
#[error("request ID {request_id} is already in use")]
RequestIdInUse { request_id: i32 },
#[error("encoded outbound message size {size} exceeds limit {limit}")]
OutboundMessageTooLarge {
size: usize,
limit: usize,
},
#[error("SNMP error from {target}: {status} at index {index}")]
Snmp {
target: SocketAddr,
status: ErrorStatus,
index: u32,
oid: Option<Box<Oid>>,
metadata: Box<crate::client::ResponseMetadata>,
},
#[error("authentication failed for {target}")]
Auth { target: SocketAddr },
#[error("SNMPv3 Report from {target}: {status}")]
Report {
target: SocketAddr,
status: Box<ReportStatus>,
metadata: Box<crate::client::ResponseMetadata>,
},
#[error("{0}")]
Decode(
#[from]
#[source]
DecodeError,
),
#[error("malformed response from {target}")]
MalformedResponse { target: SocketAddr },
#[error("response shape anomaly from {target}: {response:?}")]
ResponseShape {
target: SocketAddr,
response: crate::client::FixedCardinalityResponse,
},
#[error("walk aborted for {target}: {reason}")]
WalkAborted {
target: SocketAddr,
reason: WalkAbortReason,
},
#[error("configuration error: {0}")]
Config(Box<str>),
#[error(transparent)]
AuthoritativeEnginePersistence(#[from] crate::v3::AuthoritativeEnginePersistenceError),
#[error(transparent)]
Privacy(#[from] crate::v3::PrivacyError),
#[error("OS random source unavailable: {source}")]
RandomSource {
#[source]
source: getrandom::Error,
},
#[cfg(feature = "agent")]
#[error("agent is already running")]
AgentAlreadyRunning,
#[error("invalid SNMP message: {0}")]
InvalidMessage(Box<str>),
#[error("invalid OID: {0}")]
InvalidOid(Box<str>),
}
impl Error {
#[must_use]
pub fn kind(&self) -> ErrorKind {
match self {
Self::SharedOperation { source } => source.kind(),
Self::Exchange { source, .. } => source.kind(),
Self::Network { .. } => ErrorKind::Network,
Self::Timeout { .. } => ErrorKind::Timeout,
Self::ConstructionTimeout { .. } => ErrorKind::ConstructionTimeout,
#[cfg(feature = "agent")]
Self::AgentConstructionTimeout { .. } => ErrorKind::ConstructionTimeout,
Self::Closed { .. } => ErrorKind::Closed,
Self::RequestIdInUse { .. } => ErrorKind::RequestIdInUse,
Self::OutboundMessageTooLarge { .. } => ErrorKind::OutboundMessageTooLarge,
Self::Snmp { .. } => ErrorKind::Snmp,
Self::Auth { .. } => ErrorKind::Auth,
Self::Report { .. } => ErrorKind::Report,
Self::Decode(_) => ErrorKind::Decode,
Self::MalformedResponse { .. } => ErrorKind::MalformedResponse,
Self::ResponseShape { .. } => ErrorKind::ResponseShape,
Self::WalkAborted { .. } => ErrorKind::WalkAborted,
Self::Config(_) => ErrorKind::Config,
Self::AuthoritativeEnginePersistence(_) => ErrorKind::AuthoritativeEnginePersistence,
Self::Privacy(_) => ErrorKind::Privacy,
Self::RandomSource { .. } => ErrorKind::RandomSource,
#[cfg(feature = "agent")]
Self::AgentAlreadyRunning => ErrorKind::AgentAlreadyRunning,
Self::InvalidMessage(_) => ErrorKind::InvalidMessage,
Self::InvalidOid(_) => ErrorKind::InvalidOid,
}
}
#[must_use]
pub fn response_metadata(&self) -> Option<&crate::client::ResponseMetadata> {
match self {
Self::SharedOperation { source } => source.response_metadata(),
Self::Exchange { metadata, .. }
| Self::Snmp { metadata, .. }
| Self::Report { metadata, .. } => Some(metadata.as_ref()),
Self::ResponseShape { response, .. } => Some(&response.metadata),
_ => None,
}
}
#[must_use]
pub fn exchange_source(&self) -> &Error {
match self {
Self::SharedOperation { source } => source.exchange_source(),
Self::Exchange { source, .. } => source.exchange_source(),
_ => self,
}
}
#[must_use]
pub fn authoritative_engine_persistence(
&self,
) -> Option<&crate::v3::AuthoritativeEnginePersistenceError> {
match self.exchange_source() {
Self::AuthoritativeEnginePersistence(error) => Some(error),
_ => None,
}
}
pub(crate) fn with_prior_response_metadata(
mut self: Box<Self>,
prior: &crate::client::ResponseMetadata,
) -> Box<Self> {
if prior.decode_anomalies.is_empty() {
return self;
}
match self.as_mut() {
Self::Exchange { metadata, .. }
| Self::Snmp { metadata, .. }
| Self::Report { metadata, .. } => {
let mut combined = prior.clone();
combined.append(std::mem::take(metadata.as_mut()));
**metadata = combined;
self
}
Self::ResponseShape { response, .. } => {
let mut combined = prior.clone();
combined.append(std::mem::take(&mut response.metadata));
response.metadata = combined;
self
}
_ => Box::new(Self::Exchange {
source: self,
metadata: Box::new(prior.clone()),
}),
}
}
#[must_use]
pub fn boxed(self) -> Box<Self> {
Box::new(self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ErrorStatus {
NoError,
TooBig,
NoSuchName,
BadValue,
ReadOnly,
GenErr,
NoAccess,
WrongType,
WrongLength,
WrongEncoding,
WrongValue,
NoCreation,
InconsistentValue,
ResourceUnavailable,
CommitFailed,
UndoFailed,
AuthorizationError,
NotWritable,
InconsistentName,
Unknown(i32),
}
impl ErrorStatus {
#[must_use]
pub const fn from_i32(value: i32) -> Self {
match value {
0 => Self::NoError,
1 => Self::TooBig,
2 => Self::NoSuchName,
3 => Self::BadValue,
4 => Self::ReadOnly,
5 => Self::GenErr,
6 => Self::NoAccess,
7 => Self::WrongType,
8 => Self::WrongLength,
9 => Self::WrongEncoding,
10 => Self::WrongValue,
11 => Self::NoCreation,
12 => Self::InconsistentValue,
13 => Self::ResourceUnavailable,
14 => Self::CommitFailed,
15 => Self::UndoFailed,
16 => Self::AuthorizationError,
17 => Self::NotWritable,
18 => Self::InconsistentName,
other => Self::Unknown(other),
}
}
#[must_use]
pub fn as_i32(&self) -> i32 {
match self {
Self::NoError => 0,
Self::TooBig => 1,
Self::NoSuchName => 2,
Self::BadValue => 3,
Self::ReadOnly => 4,
Self::GenErr => 5,
Self::NoAccess => 6,
Self::WrongType => 7,
Self::WrongLength => 8,
Self::WrongEncoding => 9,
Self::WrongValue => 10,
Self::NoCreation => 11,
Self::InconsistentValue => 12,
Self::ResourceUnavailable => 13,
Self::CommitFailed => 14,
Self::UndoFailed => 15,
Self::AuthorizationError => 16,
Self::NotWritable => 17,
Self::InconsistentName => 18,
Self::Unknown(code) => *code,
}
}
#[must_use]
pub fn to_v1(&self) -> Self {
match self {
Self::NoError
| Self::TooBig
| Self::NoSuchName
| Self::BadValue
| Self::ReadOnly
| Self::GenErr => *self,
Self::WrongType
| Self::WrongLength
| Self::WrongEncoding
| Self::WrongValue
| Self::InconsistentValue => Self::BadValue,
Self::NoAccess
| Self::NotWritable
| Self::NoCreation
| Self::InconsistentName
| Self::AuthorizationError => Self::NoSuchName,
Self::ResourceUnavailable | Self::CommitFailed | Self::UndoFailed => Self::GenErr,
Self::Unknown(_) => Self::GenErr,
}
}
#[must_use]
pub fn as_str(&self) -> Option<&'static str> {
match self {
Self::NoError => Some("noError"),
Self::TooBig => Some("tooBig"),
Self::NoSuchName => Some("noSuchName"),
Self::BadValue => Some("badValue"),
Self::ReadOnly => Some("readOnly"),
Self::GenErr => Some("genErr"),
Self::NoAccess => Some("noAccess"),
Self::WrongType => Some("wrongType"),
Self::WrongLength => Some("wrongLength"),
Self::WrongEncoding => Some("wrongEncoding"),
Self::WrongValue => Some("wrongValue"),
Self::NoCreation => Some("noCreation"),
Self::InconsistentValue => Some("inconsistentValue"),
Self::ResourceUnavailable => Some("resourceUnavailable"),
Self::CommitFailed => Some("commitFailed"),
Self::UndoFailed => Some("undoFailed"),
Self::AuthorizationError => Some("authorizationError"),
Self::NotWritable => Some("notWritable"),
Self::InconsistentName => Some("inconsistentName"),
Self::Unknown(_) => None,
}
}
}
impl std::fmt::Display for ErrorStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.as_str() {
Some(name) => f.write_str(name),
None => write!(f, "unknown({})", self.as_i32()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exchange_metadata_wrapper_preserves_kind_source_and_metadata() {
let target = "127.0.0.1:161".parse().unwrap();
let metadata = crate::client::ResponseMetadata::from_decode_anomalies(vec![
crate::DecodeAnomaly::TrailingBytes {
original_length: 3,
canonical_length: 0,
},
]);
let error = Error::Closed { target }
.boxed()
.with_prior_response_metadata(&metadata);
assert_eq!(error.kind(), ErrorKind::Closed);
assert!(matches!(error.exchange_source(), Error::Closed { .. }));
assert_eq!(error.response_metadata(), Some(&metadata));
let source = std::error::Error::source(error.as_ref())
.expect("wrapper must preserve the standard error source chain");
assert_eq!(source.to_string(), Error::Closed { target }.to_string());
}
#[test]
fn error_kind_exhaustively_maps_non_agent_variants() {
let target = "127.0.0.1:161".parse().unwrap();
let cases = vec![
(
Error::Network {
target,
source: std::io::Error::other("network"),
},
ErrorKind::Network,
),
(
Error::Timeout {
target,
elapsed: Duration::from_secs(1),
retries: 2,
},
ErrorKind::Timeout,
),
(
Error::ConstructionTimeout {
target: crate::client::Target::from("unresolved.example"),
stage: ConstructionStage::Connect,
elapsed: Duration::from_secs(2),
},
ErrorKind::ConstructionTimeout,
),
(Error::Closed { target }, ErrorKind::Closed),
(
Error::RequestIdInUse { request_id: 7 },
ErrorKind::RequestIdInUse,
),
(
Error::OutboundMessageTooLarge {
size: 1500,
limit: 1472,
},
ErrorKind::OutboundMessageTooLarge,
),
(
Error::Snmp {
target,
status: ErrorStatus::GenErr,
index: 1,
oid: Some(Box::new(Oid::from_slice(&[1, 3, 6, 1]))),
metadata: Box::new(crate::client::ResponseMetadata::default()),
},
ErrorKind::Snmp,
),
(Error::Auth { target }, ErrorKind::Auth),
(
Error::Report {
target,
status: Box::new(crate::v3::ReportStatus::UnknownEngineId { counter: 1 }),
metadata: Box::new(crate::client::ResponseMetadata::default()),
},
ErrorKind::Report,
),
(
Error::MalformedResponse { target },
ErrorKind::MalformedResponse,
),
(
Error::Decode(DecodeError::new(3, DecodeErrorKind::TruncatedData)),
ErrorKind::Decode,
),
(
Error::ResponseShape {
target,
response: crate::client::FixedCardinalityResponse {
operation: crate::client::FixedCardinalityOperation::Get,
varbinds: Vec::new(),
anomalies: Vec::new(),
metadata: crate::client::ResponseMetadata::default(),
},
},
ErrorKind::ResponseShape,
),
(
Error::WalkAborted {
target,
reason: WalkAbortReason::Cycle,
},
ErrorKind::WalkAborted,
),
(Error::Config("config".into()), ErrorKind::Config),
(
*crate::v3::AuthoritativeEngine::install(b"error-kind-engine".to_vec(), |_| {
Err(std::io::Error::other("persistence"))
})
.unwrap_err(),
ErrorKind::AuthoritativeEnginePersistence,
),
(
Error::RandomSource {
source: getrandom::Error::UNEXPECTED,
},
ErrorKind::RandomSource,
),
(
Error::InvalidMessage("message".into()),
ErrorKind::InvalidMessage,
),
(Error::InvalidOid("oid".into()), ErrorKind::InvalidOid),
];
for (error, expected) in cases {
assert_eq!(error.kind(), expected);
}
}
#[test]
fn error_kind_canonical_names_and_display_are_total() {
let cases = [
(ErrorKind::Network, "network"),
(ErrorKind::Timeout, "timeout"),
(ErrorKind::ConstructionTimeout, "construction_timeout"),
(ErrorKind::Closed, "transport_closed"),
(ErrorKind::RequestIdInUse, "request_id_in_use"),
(
ErrorKind::OutboundMessageTooLarge,
"outbound_message_too_large",
),
(ErrorKind::Snmp, "snmp"),
(ErrorKind::Auth, "authentication"),
(ErrorKind::Report, "v3_report"),
(ErrorKind::Decode, "decode"),
(ErrorKind::MalformedResponse, "malformed_response"),
(ErrorKind::ResponseShape, "response_shape"),
(ErrorKind::WalkAborted, "walk_aborted"),
(ErrorKind::Config, "configuration"),
(
ErrorKind::AuthoritativeEnginePersistence,
"authoritative_engine_persistence",
),
(ErrorKind::RandomSource, "random_source"),
(ErrorKind::AgentAlreadyRunning, "agent_already_running"),
(ErrorKind::InvalidMessage, "invalid_message"),
(ErrorKind::InvalidOid, "invalid_oid"),
];
for (kind, expected) in cases {
assert_eq!(kind.as_str(), expected);
assert_eq!(kind.to_string(), expected);
}
}
#[test]
fn error_kind_names_are_const() {
const NAME: &str = ErrorKind::ConstructionTimeout.as_str();
assert_eq!(NAME, "construction_timeout");
}
#[cfg(feature = "agent")]
#[test]
fn agent_error_maps_to_always_nameable_kind() {
assert_eq!(
Error::AgentAlreadyRunning.kind(),
ErrorKind::AgentAlreadyRunning
);
assert_eq!(
ErrorKind::AgentAlreadyRunning.as_str(),
"agent_already_running"
);
}
#[test]
fn walk_abort_reason_is_error() {
let reason = WalkAbortReason::NonIncreasing;
let err: &dyn std::error::Error = &reason;
assert_eq!(err.to_string(), "non-increasing OID");
}
#[test]
fn error_status_conversion_is_const_and_preserves_unknown_values() {
const KNOWN: ErrorStatus = ErrorStatus::from_i32(2);
const FUTURE: ErrorStatus = ErrorStatus::from_i32(99);
const NEGATIVE: ErrorStatus = ErrorStatus::from_i32(-1);
assert_eq!(KNOWN, ErrorStatus::NoSuchName);
assert_eq!(FUTURE, ErrorStatus::Unknown(99));
assert_eq!(NEGATIVE, ErrorStatus::Unknown(-1));
}
#[test]
fn error_status_to_v1_mapping() {
assert_eq!(ErrorStatus::NoError.to_v1(), ErrorStatus::NoError);
assert_eq!(ErrorStatus::TooBig.to_v1(), ErrorStatus::TooBig);
assert_eq!(ErrorStatus::NoSuchName.to_v1(), ErrorStatus::NoSuchName);
assert_eq!(ErrorStatus::BadValue.to_v1(), ErrorStatus::BadValue);
assert_eq!(ErrorStatus::ReadOnly.to_v1(), ErrorStatus::ReadOnly);
assert_eq!(ErrorStatus::GenErr.to_v1(), ErrorStatus::GenErr);
assert_eq!(ErrorStatus::WrongValue.to_v1(), ErrorStatus::BadValue);
assert_eq!(ErrorStatus::WrongType.to_v1(), ErrorStatus::BadValue);
assert_eq!(ErrorStatus::WrongLength.to_v1(), ErrorStatus::BadValue);
assert_eq!(ErrorStatus::WrongEncoding.to_v1(), ErrorStatus::BadValue);
assert_eq!(
ErrorStatus::InconsistentValue.to_v1(),
ErrorStatus::BadValue
);
assert_eq!(ErrorStatus::NoAccess.to_v1(), ErrorStatus::NoSuchName);
assert_eq!(ErrorStatus::NotWritable.to_v1(), ErrorStatus::NoSuchName);
assert_eq!(ErrorStatus::NoCreation.to_v1(), ErrorStatus::NoSuchName);
assert_eq!(
ErrorStatus::InconsistentName.to_v1(),
ErrorStatus::NoSuchName
);
assert_eq!(
ErrorStatus::AuthorizationError.to_v1(),
ErrorStatus::NoSuchName
);
assert_eq!(
ErrorStatus::ResourceUnavailable.to_v1(),
ErrorStatus::GenErr
);
assert_eq!(ErrorStatus::CommitFailed.to_v1(), ErrorStatus::GenErr);
assert_eq!(ErrorStatus::UndoFailed.to_v1(), ErrorStatus::GenErr);
}
#[test]
fn error_size_budget() {
assert!(
std::mem::size_of::<Error>() <= 128,
"Error size {} exceeds 128-byte budget",
std::mem::size_of::<Error>()
);
assert_eq!(
std::mem::size_of::<Result<()>>(),
std::mem::size_of::<*const ()>(),
"Result<()> should be pointer-sized"
);
}
}