1use super::cmd::{Format, ResetStyle, SetForeground8};
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum ErrorKind {
12    NoData,
14    InFlight,
16    MalformedUtf8,
18    MalformedSequence,
20    PathologicalSequence,
22    BadControl,
24    BadSequence,
26    NotASequence,
28    OutOfMemory,
30    TooFewCoordinates,
32    TooManyCoordinates,
34    EmptyCoordinate,
36    OversizedCoordinate,
38    MalformedCoordinate,
40    Unreadable,
42}
43
44impl ErrorKind {
45    pub fn as_str(&self) -> &'static str {
47        match *self {
48            Self::NoData => "reading terminal input timed out without returning data",
49            Self::InFlight => "token is in-flight, hence access to raw bytes is not safe",
50            Self::MalformedUtf8 => "malformed UTF-8",
51            Self::MalformedSequence => "malformed ANSI escape sequence",
52            Self::PathologicalSequence => "pathologically long ANSI escape sequence",
53            Self::BadControl => "unexpected control for ANSI escape sequence",
54            Self::BadSequence => "unexpected ANSI escape sequence",
55            Self::NotASequence => "token not an ANSI escape sequence",
56            Self::OutOfMemory => "ANSI escape sequence too long for internal buffer",
57            Self::Unreadable => "error reading terminal",
58            Self::TooFewCoordinates => "too few color coordinates",
59            Self::TooManyCoordinates => "too many color coordinates",
60            Self::EmptyCoordinate => "empty color coordinate",
61            Self::OversizedCoordinate => "oversized color coordinate",
62            Self::MalformedCoordinate => "malformed color coordinate",
63        }
64    }
65}
66
67impl From<ErrorKind> for std::io::Error {
68    fn from(value: ErrorKind) -> Self {
69        Error::from(value).into()
70    }
71}
72
73impl From<ErrorKind> for Error {
74    fn from(kind: ErrorKind) -> Self {
75        Self { kind, source: None }
76    }
77}
78
79#[derive(Debug)]
81pub struct Error {
82    kind: ErrorKind,
83    source: Option<std::io::Error>,
84}
85
86impl Error {
87    #[must_use = "the only reason to invoke method is to access the returned value"]
89    pub fn unreadable(source: std::io::Error) -> Self {
90        Self {
91            kind: ErrorKind::Unreadable,
92            source: Some(source),
93        }
94    }
95
96    pub fn kind(&self) -> ErrorKind {
98        self.kind
99    }
100}
101
102impl core::fmt::Display for Error {
103    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
104        f.write_str(self.kind.as_str())
105    }
106}
107
108impl core::error::Error for Error {
109    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
110        if let &Self {
111            kind: ErrorKind::Unreadable,
112            source: Some(ref error),
113        } = self
114        {
115            Some(error)
116        } else {
117            None
118        }
119    }
120}
121
122impl From<std::io::Error> for Error {
123    fn from(value: std::io::Error) -> Self {
124        Self::unreadable(value)
125    }
126}
127
128impl From<Error> for std::io::Error {
129    fn from(value: Error) -> Self {
130        use self::ErrorKind::*;
131
132        match value.kind {
133            MalformedUtf8 | MalformedSequence | PathologicalSequence | BadControl | BadSequence
134            | NotASequence | TooFewCoordinates | TooManyCoordinates | EmptyCoordinate
135            | OversizedCoordinate | MalformedCoordinate => {
136                Self::new(std::io::ErrorKind::InvalidData, value)
137            }
138            NoData => std::io::ErrorKind::TimedOut.into(),
139            InFlight => std::io::ErrorKind::ResourceBusy.into(),
140            OutOfMemory => std::io::ErrorKind::OutOfMemory.into(),
141            Unreadable =>
142            {
143                #[allow(clippy::option_if_let_else)]
144                if let Some(error) = value.source {
145                    error
146                } else {
147                    Self::other(value)
148                }
149            }
150        }
151    }
152}
153
154pub fn should_retry<T, E>(result: core::result::Result<T, E>) -> bool
158where
159    E: Into<std::io::Error>,
160{
161    if let Err(err) = result {
162        let kind = err.into().kind();
163        kind == std::io::ErrorKind::Interrupted || kind == std::io::ErrorKind::TimedOut
164    } else {
165        false
166    }
167}
168
169#[allow(clippy::print_stdout)]
171pub fn report<E: core::error::Error>(error: &E) {
172    println!(
173        "{}{}ERROR: {}{}",
174        Format::Bold,
175        SetForeground8::<1>,
176        error,
177        ResetStyle
178    );
179
180    let mut error: &dyn core::error::Error = error;
181    while let Some(inner) = error.source() {
182        println!("    {}", inner);
183        error = inner;
184    }
185}