xand_api_client/
errors.rs

1use serde::Serialize;
2use snafu::Snafu;
3use std::sync::Arc;
4use tonic::{transport::Error, Code, Status};
5use xand_api_proto::error::XandApiProtoErrs;
6use xand_api_proto::proto_models::TransactionIdError;
7// TODO consider changing snafu to thiserror
8#[derive(Debug, Snafu, Clone, Serialize)]
9#[snafu(visibility(pub))]
10pub enum XandApiClientError {
11    TransportError {
12        #[snafu(source(from(tonic::transport::Error, Arc::new)))]
13        #[serde(serialize_with = "xand_utils::snafu_extensions::debug_serialize")]
14        source: Arc<tonic::transport::Error>,
15    },
16    #[snafu(display("Requested entity not found: {}", message))]
17    NotFound {
18        message: String,
19    },
20    #[snafu(display("Bad request: {}", message))]
21    BadRequest {
22        message: String,
23    },
24    /// Other grpc errors which are not represented as first-class items in this error enum
25    OtherGrpcError {
26        #[snafu(source(from(tonic::Status, Arc::new)))]
27        #[serde(serialize_with = "xand_utils::snafu_extensions::debug_serialize")]
28        source: Arc<tonic::Status>,
29    },
30    ProtoError {
31        source: XandApiProtoErrs,
32    },
33    #[snafu(display("Transaction ID returned by xand-api is invalid: {}", source))]
34    #[snafu(context(false))]
35    InvalidIdInTransaction {
36        source: TransactionIdError,
37    },
38    #[snafu(display("xand-api reply was missing required data: {}", message))]
39    BadReplyError {
40        message: String,
41    },
42    #[snafu(display("Transaction state is unknown"))]
43    UnknownTransactionStatus,
44    #[snafu(display("Invalid address {}", address))]
45    InvalidAddress {
46        address: String,
47    },
48    #[snafu(display("Invalid CIDR block {}", cidr_block))]
49    InvalidCidrBlock {
50        cidr_block: String,
51    },
52    #[snafu(display("The operation timed out"))]
53    Timeout,
54
55    #[snafu(display(
56        "Your request was not authorized. Please check your credentials and try again: {}",
57        message
58    ))]
59    Unauthorized {
60        message: String,
61    },
62}
63
64impl From<tonic::transport::Error> for XandApiClientError {
65    fn from(e: Error) -> Self {
66        XandApiClientError::TransportError {
67            source: Arc::new(e),
68        }
69    }
70}
71
72impl From<tonic::Status> for XandApiClientError {
73    fn from(e: Status) -> Self {
74        match e.code() {
75            Code::NotFound => XandApiClientError::NotFound {
76                message: e.message().to_owned(),
77            },
78            Code::InvalidArgument => XandApiClientError::BadRequest {
79                message: e.message().to_owned(),
80            },
81            Code::Unauthenticated => XandApiClientError::Unauthorized {
82                message: e.message().to_owned(),
83            },
84            _ => XandApiClientError::OtherGrpcError { source: e.into() },
85        }
86    }
87}
88
89impl From<XandApiProtoErrs> for XandApiClientError {
90    fn from(e: XandApiProtoErrs) -> Self {
91        XandApiClientError::ProtoError { source: e }
92    }
93}
94
95#[cfg(test)]
96mod test {
97    use super::*;
98
99    // XandApiClientError should be Send
100    // Never is executed. Ignore clippy. Compile-only test.
101    #[allow(unconditional_recursion, dead_code)]
102    fn send_test<T: Send>(_: T) {
103        send_test(XandApiClientError::BadRequest {
104            message: "hi".to_string(),
105        });
106    }
107
108    #[test]
109    fn unauthorized() {
110        let status = Status::new(Code::Unauthenticated, "some arbitrary message".to_string());
111        let error: XandApiClientError = status.into();
112
113        assert!(matches!(
114            error,
115            XandApiClientError::Unauthorized {
116                message
117            } if message == "some arbitrary message"
118        ));
119    }
120}