1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
//! # Error wrappers and boilerplate
//!
//! This module provides some basic boilerplate for errors. As a consumer of this
//! library, you should expect that all public functions return a `Result` type
//! using this local `Error`, which implements the standard Error trait.
//! As a general rule, errors that come from dependent crates are wrapped by
//! this crate's error type.
#![allow(unused_macros)]

use core::fmt;
use nats_types::DeliveredMessage;
use nats_types::ProtocolMessage;

use std::{
    error::Error as StdError,
    string::{String, ToString},
};

/// Provides an error type specific to the natsclient library
#[derive(Debug)]
pub struct Error {
    kind: ErrorKind,

    description: Option<String>,
}

/// Provides context as to how a particular natsclient error might have occurred
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum ErrorKind {
    /// Indicates a bad URL scheme was supplied for server address(es)
    InvalidUriScheme,
    /// URI Parse failure
    UriParseFailure,
    /// Invalid client configuration
    InvalidClientConfig,
    /// I/O failure
    IOError,
    /// Concurrency Failure
    ConcurrencyFailure,
    /// Subscription Failure
    SubscriptionFailure,
    // Timeout expired
    Timeout,
}

/// A handy macro borrowed from the `signatory` crate that lets library-internal code generate
/// more readable exception handling flows
#[macro_export]
macro_rules! err {
    ($variant:ident, $msg:expr) => {
        $crate::error::Error::new(
            $crate::error::ErrorKind::$variant,
            Some($msg)
        )
    };
    ($variant:ident, $fmt:expr, $($arg:tt)+) => {
        err!($variant, &format!($fmt, $($arg)+))
    };
}

impl ErrorKind {
    pub fn as_str(&self) -> &'static str {
        match self {
            ErrorKind::InvalidUriScheme => "Invalid URI scheme",
            ErrorKind::UriParseFailure => "URI parse failure",
            ErrorKind::InvalidClientConfig => "Invalid client configuration",
            ErrorKind::IOError => "I/O failure",
            ErrorKind::ConcurrencyFailure => "Concurrency Failure",
            ErrorKind::SubscriptionFailure => "Subscription Failure",
            ErrorKind::Timeout => "Timeout expired",
        }
    }
}

impl fmt::Display for ErrorKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.as_str())
    }
}

impl Error {
    /// Creates a new natsclient error wrapper
    pub fn new(kind: ErrorKind, description: Option<&str>) -> Self {
        Error {
            kind,
            description: description.map(|desc| desc.to_string()),
        }
    }

    /// An accessor exposing the error kind enum. Crate consumers should have little to no
    /// need to access this directly and it's mostly used to assert that internal functions
    /// are creating appropriate error wrappers.
    pub fn kind(&self) -> ErrorKind {
        self.kind
    }
}

impl From<url::ParseError> for Error {
    fn from(source: url::ParseError) -> Error {
        err!(UriParseFailure, "URI parse failure: {}", source)
    }
}

impl From<std::io::Error> for Error {
    fn from(source: std::io::Error) -> Error {
        err!(IOError, "I/O error: {}", source)
    }
}

impl From<(ErrorKind, &'static str)> for Error {
    fn from((kind, description): (ErrorKind, &'static str)) -> Error {
        Error {
            kind,
            description: Some(description.to_string()),
        }
    }
}

impl From<crossbeam_channel::SendError<ProtocolMessage>> for Error {
    fn from(source: crossbeam_channel::SendError<ProtocolMessage>) -> Error {
        err!(ConcurrencyFailure, "Concurrency error: {}", source)
    }
}

impl From<crossbeam_channel::SendError<DeliveredMessage>> for Error {
    fn from(source: crossbeam_channel::SendError<DeliveredMessage>) -> Error {
        err!(ConcurrencyFailure, "Concurrency error: {}", source)
    }
}

impl From<crossbeam_channel::RecvTimeoutError> for Error {
    fn from(source: crossbeam_channel::RecvTimeoutError) -> Error {
        err!(Timeout, "Timeout expired: {}", source)
    }
}

impl StdError for Error {
    fn description(&self) -> &str {
        if let Some(ref desc) = self.description {
            desc
        } else {
            self.kind.as_str()
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self.description {
            Some(ref desc) => write!(f, "{}: {}", self.description(), desc),
            None => write!(f, "{}", self.description()),
        }
    }
}