use std::borrow::Cow;
use crate::error::ErrorStatus;
use crate::value::Value;
use crate::varbind::VarBind;
pub struct HandlerError {
message: Cow<'static, str>,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
}
impl HandlerError {
pub fn new(message: impl Into<Cow<'static, str>>) -> Self {
Self {
message: message.into(),
source: None,
}
}
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
#[must_use]
pub fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source.as_deref().map(|e| e as _)
}
}
impl std::fmt::Display for HandlerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::fmt::Debug for HandlerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HandlerError")
.field("message", &self.message)
.field("source", &self.source)
.finish()
}
}
impl<E: std::error::Error + Send + Sync + 'static> From<E> for HandlerError {
fn from(err: E) -> Self {
Self {
message: err.to_string().into(),
source: Some(Box::new(err)),
}
}
}
pub type HandlerResult<T> = Result<T, HandlerError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SetResult {
Ok,
NoAccess,
NotWritable,
WrongType,
WrongLength,
WrongEncoding,
WrongValue,
NoCreation,
InconsistentValue,
ResourceUnavailable,
CommitFailed,
UndoFailed,
InconsistentName,
}
impl SetResult {
#[must_use]
pub fn is_ok(&self) -> bool {
matches!(self, SetResult::Ok)
}
#[must_use]
pub fn to_error_status(&self) -> ErrorStatus {
match self {
SetResult::Ok => ErrorStatus::NoError,
SetResult::NoAccess => ErrorStatus::NoAccess,
SetResult::NotWritable => ErrorStatus::NotWritable,
SetResult::WrongType => ErrorStatus::WrongType,
SetResult::WrongLength => ErrorStatus::WrongLength,
SetResult::WrongEncoding => ErrorStatus::WrongEncoding,
SetResult::WrongValue => ErrorStatus::WrongValue,
SetResult::NoCreation => ErrorStatus::NoCreation,
SetResult::InconsistentValue => ErrorStatus::InconsistentValue,
SetResult::ResourceUnavailable => ErrorStatus::ResourceUnavailable,
SetResult::CommitFailed => ErrorStatus::CommitFailed,
SetResult::UndoFailed => ErrorStatus::UndoFailed,
SetResult::InconsistentName => ErrorStatus::InconsistentName,
}
}
}
#[derive(Debug, Clone)]
pub struct Response {
pub varbinds: Vec<VarBind>,
pub error_status: ErrorStatus,
pub error_index: i32,
}
impl Response {
#[must_use]
pub fn success(varbinds: Vec<VarBind>) -> Self {
Self {
varbinds,
error_status: ErrorStatus::NoError,
error_index: 0,
}
}
#[must_use]
pub fn error(error_status: ErrorStatus, error_index: i32, varbinds: Vec<VarBind>) -> Self {
Self {
varbinds,
error_status,
error_index,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum GetResult {
Value(Value),
NoSuchObject,
NoSuchInstance,
}
impl GetResult {
pub fn from_option(value: Option<Value>) -> Self {
match value {
Some(v) => GetResult::Value(v),
None => GetResult::NoSuchObject,
}
}
}
impl From<Value> for GetResult {
fn from(value: Value) -> Self {
GetResult::Value(value)
}
}
impl From<Option<Value>> for GetResult {
fn from(value: Option<Value>) -> Self {
GetResult::from_option(value)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum GetNextResult {
Value(VarBind),
EndOfMibView,
}
impl GetNextResult {
pub fn from_option(value: Option<VarBind>) -> Self {
match value {
Some(vb) => GetNextResult::Value(vb),
None => GetNextResult::EndOfMibView,
}
}
pub fn is_value(&self) -> bool {
matches!(self, GetNextResult::Value(_))
}
pub fn is_end_of_mib_view(&self) -> bool {
matches!(self, GetNextResult::EndOfMibView)
}
pub fn into_option(self) -> Option<VarBind> {
match self {
GetNextResult::Value(vb) => Some(vb),
GetNextResult::EndOfMibView => None,
}
}
}
impl From<VarBind> for GetNextResult {
fn from(vb: VarBind) -> Self {
GetNextResult::Value(vb)
}
}
impl From<Option<VarBind>> for GetNextResult {
fn from(value: Option<VarBind>) -> Self {
GetNextResult::from_option(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::oid;
#[test]
fn test_response_success() {
let response = Response::success(vec![VarBind::new(oid!(1, 3, 6, 1), Value::Integer(1))]);
assert_eq!(response.error_status, ErrorStatus::NoError);
assert_eq!(response.error_index, 0);
assert_eq!(response.varbinds.len(), 1);
}
#[test]
fn test_response_error() {
let response = Response::error(
ErrorStatus::NoSuchName,
1,
vec![VarBind::new(oid!(1, 3, 6, 1), Value::Null)],
);
assert_eq!(response.error_status, ErrorStatus::NoSuchName);
assert_eq!(response.error_index, 1);
}
#[test]
fn test_get_result_from_option() {
let result = GetResult::from_option(Some(Value::Integer(42)));
assert!(matches!(result, GetResult::Value(Value::Integer(42))));
let result = GetResult::from_option(None);
assert!(matches!(result, GetResult::NoSuchObject));
}
#[test]
fn test_get_result_from_value() {
let result: GetResult = Value::Integer(42).into();
assert!(matches!(result, GetResult::Value(Value::Integer(42))));
}
#[test]
fn test_get_next_result_from_option() {
let vb = VarBind::new(oid!(1, 3, 6, 1), Value::Integer(42));
let result = GetNextResult::from_option(Some(vb.clone()));
assert!(result.is_value());
assert_eq!(result.into_option(), Some(vb));
let result = GetNextResult::from_option(None);
assert!(result.is_end_of_mib_view());
assert_eq!(result.into_option(), None);
}
#[test]
fn test_get_next_result_from_varbind() {
let vb = VarBind::new(oid!(1, 3, 6, 1), Value::Integer(42));
let result: GetNextResult = vb.clone().into();
assert!(result.is_value());
if let GetNextResult::Value(inner) = result {
assert_eq!(inner.oid, oid!(1, 3, 6, 1));
}
}
#[test]
fn test_set_result_to_error_status() {
assert_eq!(SetResult::Ok.to_error_status(), ErrorStatus::NoError);
assert_eq!(SetResult::NoAccess.to_error_status(), ErrorStatus::NoAccess);
assert_eq!(
SetResult::NotWritable.to_error_status(),
ErrorStatus::NotWritable
);
assert_eq!(
SetResult::WrongType.to_error_status(),
ErrorStatus::WrongType
);
assert_eq!(
SetResult::CommitFailed.to_error_status(),
ErrorStatus::CommitFailed
);
}
#[test]
fn test_handler_error_message_only() {
let err = HandlerError::new("backend down");
assert_eq!(err.message(), "backend down");
assert!(err.source().is_none());
assert_eq!(err.to_string(), "backend down");
}
#[test]
fn test_handler_error_from_std_error() {
let io_err = std::io::Error::other("bus timeout");
let err: HandlerError = io_err.into();
assert_eq!(err.message(), "bus timeout");
assert!(err.source().is_some());
assert_eq!(err.to_string(), "bus timeout");
}
#[test]
fn test_handler_error_question_mark_conversion() {
fn inner() -> HandlerResult<GetResult> {
Err(std::io::Error::other("no route"))?;
unreachable!()
}
let err = inner().unwrap_err();
assert_eq!(err.message(), "no route");
}
#[test]
fn test_set_result_is_ok() {
assert!(SetResult::Ok.is_ok());
assert!(!SetResult::NoAccess.is_ok());
assert!(!SetResult::NotWritable.is_ok());
}
}