rsketch_error/
lib.rs

1// Copyright 2025 Crrow
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{any::Any, error::Error as StdError, sync::Arc};
16
17use http::StatusCode as HttpStatusCode;
18use serde::Serialize;
19use snafu::Snafu;
20use strum::EnumProperty;
21use tonic::Code as TonicCode;
22
23#[derive(
24    Clone,
25    Copy,
26    Debug,
27    Eq,
28    PartialEq,
29    Serialize,
30    strum_macros::EnumProperty,
31    strum_macros::EnumString,
32)]
33#[serde(rename_all = "snake_case")]
34#[strum(serialize_all = "snake_case")]
35pub enum StatusCode {
36    #[strum(props(http_status = "400", tonic_code = "3"))]
37    InvalidArgument,
38    #[strum(props(http_status = "404", tonic_code = "5"))]
39    NotFound,
40    #[strum(props(http_status = "401", tonic_code = "16"))]
41    Unauthorized,
42    #[strum(props(http_status = "403", tonic_code = "7"))]
43    Forbidden,
44    #[strum(props(http_status = "409", tonic_code = "6"))]
45    Conflict,
46    #[strum(props(http_status = "500", tonic_code = "13"))]
47    Internal,
48    #[strum(props(http_status = "500", tonic_code = "13"))]
49    Unknown,
50}
51
52impl StatusCode {
53    #[must_use]
54    pub fn http_status(self) -> HttpStatusCode {
55        self.get_str("http_status")
56            .and_then(|value| value.parse::<u16>().ok())
57            .and_then(|value| HttpStatusCode::from_u16(value).ok())
58            .unwrap_or(HttpStatusCode::INTERNAL_SERVER_ERROR)
59    }
60
61    #[must_use]
62    pub fn tonic_code(self) -> TonicCode {
63        let value = self
64            .get_str("tonic_code")
65            .and_then(|value| value.parse::<i32>().ok())
66            .unwrap_or(TonicCode::Internal as i32);
67        TonicCode::from_i32(value)
68    }
69}
70
71pub trait StackError: StdError {
72    fn debug_fmt(&self, layer: usize, buf: &mut Vec<String>);
73
74    fn next(&self) -> Option<&dyn StackError>;
75
76    fn last(&self) -> &dyn StackError
77    where
78        Self: Sized,
79    {
80        let Some(mut result) = self.next() else {
81            return self;
82        };
83        while let Some(err) = result.next() {
84            result = err;
85        }
86        result
87    }
88
89    fn transparent(&self) -> bool { false }
90}
91
92pub trait ErrorExt: StackError {
93    fn status_code(&self) -> StatusCode { StatusCode::Unknown }
94
95    fn as_any(&self) -> &dyn Any;
96
97    fn output_msg(&self) -> String
98    where
99        Self: Sized,
100    {
101        match self.status_code() {
102            StatusCode::Unknown | StatusCode::Internal => {
103                format!("Internal error: {}", self.status_code() as u32)
104            }
105            _ => {
106                let error = self.last();
107                error.source().map_or_else(
108                    || format!("{error}"),
109                    |external_error| {
110                        let mut root = external_error;
111                        while let Some(source) = root.source() {
112                            root = source;
113                        }
114                        if error.transparent() {
115                            format!("{root}")
116                        } else {
117                            format!("{error}: {root}")
118                        }
119                    },
120                )
121            }
122        }
123    }
124
125    fn root_cause(&self) -> Option<&dyn StdError>
126    where
127        Self: Sized,
128    {
129        let error = self.last();
130        let mut source = error.source()?;
131        while let Some(next) = source.source() {
132            source = next;
133        }
134        Some(source)
135    }
136}
137
138impl<T: ?Sized + StackError> StackError for Arc<T> {
139    fn debug_fmt(&self, layer: usize, buf: &mut Vec<String>) {
140        self.as_ref().debug_fmt(layer, buf);
141    }
142
143    fn next(&self) -> Option<&dyn StackError> { self.as_ref().next() }
144}
145
146impl<T: StackError> StackError for Box<T> {
147    fn debug_fmt(&self, layer: usize, buf: &mut Vec<String>) {
148        self.as_ref().debug_fmt(layer, buf);
149    }
150
151    fn next(&self) -> Option<&dyn StackError> { self.as_ref().next() }
152}
153
154pub type Result<T> = std::result::Result<T, Error>;
155
156#[derive(Snafu, Debug)]
157#[snafu(visibility(pub))]
158pub enum Error {
159    #[snafu(transparent)]
160    Network {
161        source: NetworkError,
162        #[snafu(implicit)]
163        loc:    snafu::Location,
164    },
165}
166
167#[derive(Snafu, Debug)]
168#[snafu(visibility(pub))]
169pub enum NetworkError {
170    #[snafu(display("Failed to connect to {addr}"))]
171    ConnectionError {
172        addr:   String,
173        #[snafu(source)]
174        source: std::io::Error,
175        #[snafu(implicit)]
176        loc:    snafu::Location,
177    },
178
179    #[snafu(display("Failed to parse address {addr}"))]
180    ParseAddressError {
181        addr:   String,
182        #[snafu(source)]
183        source: std::net::AddrParseError,
184        #[snafu(implicit)]
185        loc:    snafu::Location,
186    },
187}