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
//! # 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 signatory::signature;

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

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

    description: Option<String>,
}

/// Provides context as to how a particular nkeys error might have occurred
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum ErrorKind {
    /// Indicates an inappropriate byte prefix was used for an encoded key string
    InvalidPrefix,
    /// Indicates a key string was used with the wrong length
    InvalidKeyLength,
    /// Indicates a signature verification mismatch. Use this to check for invalid signatures or messages
    VerifyError,
    /// Indicates an unexpected underlying error occurred while trying to perform routine signature tasks.
    SignatureError,
    /// Indicates a checksum mismatch occurred while validating a crc-encoded string
    ChecksumFailure,
    /// Indicates a miscellaneous error occurred during encoding or decoding the nkey-specific formats
    CodecFailure,
    /// Indicates a key type mismatch, e.g. attempting to sign with only a public key
    IncorrectKeyType,
    /// Payload not valid (or failed to be decrypted)
    InvalidPayload,
    /// Signature did not match the expected length (64 bytes)
    InvalidSignatureLength,
}

/// 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::InvalidPrefix => "Invalid byte prefix",
            ErrorKind::InvalidKeyLength => "Invalid key length",
            ErrorKind::InvalidSignatureLength => "Invalid signature length",
            ErrorKind::VerifyError => "Signature verification failure",
            ErrorKind::ChecksumFailure => "Checksum match failure",
            ErrorKind::CodecFailure => "Codec failure",
            ErrorKind::SignatureError => "Signature failure",
            ErrorKind::IncorrectKeyType => "Incorrect key type",
            ErrorKind::InvalidPayload => "Invalid payload",
        }
    }
}

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

impl Error {
    /// Creates a new nkeys 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
    }
}

/// Creates an nkeys error derived from an error that came from the `signatory` crate
impl From<signature::Error> for Error {
    fn from(source: signature::Error) -> Error {
        err!(SignatureError, &format!("Signature error: {}", source))
    }
}

/// Creates an nkeys error derived from a decoding failure in the `data_encoding` crate
impl From<data_encoding::DecodeError> for Error {
    fn from(source: data_encoding::DecodeError) -> Error {
        err!(CodecFailure, "Data encoding failure: {}", 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.kind.as_str(), desc),
            None => write!(f, "{}", self.kind.as_str()),
        }
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_error_to_string() {
        assert_eq!(
            err!(InvalidKeyLength, "Testing").to_string(),
            "Invalid key length: Testing"
        );
        assert_eq!(
            err!(InvalidKeyLength, "Testing {}", 1).to_string(),
            "Invalid key length: Testing 1"
        );
    }
}