1use std::{
2 borrow::Cow,
3 fmt::{Debug, Display},
4 hash::Hash,
5};
6
7use crate::cache::ArcStr;
8use either::Either;
9use nix::errno::Errno;
10use owo_colors::OwoColorize;
11use serde::Serialize;
12
13use crate::{cli, proc::cached_string};
14
15#[cfg(feature = "ebpf")]
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
17#[repr(u8)]
18pub enum BpfError {
19 Dropped,
20 Flags,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24#[repr(u64)]
25pub enum FriendlyError {
26 InspectError(Errno),
27 #[cfg(feature = "ebpf")]
28 Bpf(BpfError),
29}
30
31impl PartialOrd for FriendlyError {
32 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
33 Some(Ord::cmp(self, other))
34 }
35}
36
37impl Ord for FriendlyError {
38 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
39 match (self, other) {
40 (Self::InspectError(a), Self::InspectError(b)) => (*a as i32).cmp(&(*b as i32)),
41 #[cfg(feature = "ebpf")]
42 (Self::Bpf(a), Self::Bpf(b)) => a.cmp(b),
43 #[cfg(feature = "ebpf")]
44 (Self::InspectError(_), Self::Bpf(_)) => std::cmp::Ordering::Less,
45 #[cfg(feature = "ebpf")]
46 (Self::Bpf(_), Self::InspectError(_)) => std::cmp::Ordering::Greater,
47 }
48 }
49}
50
51impl Hash for FriendlyError {
52 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
53 core::mem::discriminant(self).hash(state);
54 match self {
55 Self::InspectError(e) => (*e as i32).hash(state),
56 #[cfg(feature = "ebpf")]
57 Self::Bpf(e) => e.hash(state),
58 }
59 }
60}
61
62#[cfg(feature = "ebpf")]
63impl From<BpfError> for FriendlyError {
64 fn from(value: BpfError) -> Self {
65 Self::Bpf(value)
66 }
67}
68
69impl From<&FriendlyError> for &'static str {
70 fn from(value: &FriendlyError) -> Self {
71 match value {
72 FriendlyError::InspectError(_) => "[err: failed to inspect]",
73 #[cfg(feature = "ebpf")]
74 FriendlyError::Bpf(_) => "[err: bpf error]",
75 }
76 }
77}
78
79#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
81pub enum OutputMsg {
82 Ok(ArcStr),
83 PartialOk(ArcStr),
85 Err(FriendlyError),
86}
87
88impl AsRef<str> for OutputMsg {
89 fn as_ref(&self) -> &str {
90 match self {
91 Self::Ok(s) => s.as_ref(),
92 Self::PartialOk(s) => s.as_ref(),
93 Self::Err(e) => <&'static str>::from(e),
94 }
95 }
96}
97
98impl Serialize for OutputMsg {
99 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
100 where
101 S: serde::Serializer,
102 {
103 match self {
104 Self::Ok(s) => s.serialize(serializer),
105 Self::PartialOk(s) => s.serialize(serializer),
106 Self::Err(e) => <&'static str>::from(e).serialize(serializer),
107 }
108 }
109}
110
111impl Display for OutputMsg {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 match self {
114 Self::Ok(msg) => write!(f, "{msg:?}"),
115 Self::PartialOk(msg) => write!(f, "{:?}", cli::theme::THEME.inline_error.style(msg)),
116 Self::Err(e) => Display::fmt(&cli::theme::THEME.inline_error.style(&e), f),
117 }
118 }
119}
120
121impl Display for FriendlyError {
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 write!(f, "{}", <&'static str>::from(self))
124 }
125}
126
127impl From<ArcStr> for OutputMsg {
128 fn from(value: ArcStr) -> Self {
129 Self::Ok(value)
130 }
131}
132
133pub trait StyleOutputMsg<O> {
134 fn bash_escaped_with_style(&self) -> O;
136
137 fn styled(&self) -> O;
138}
139
140impl OutputMsg {
141 pub fn not_ok(&self) -> bool {
142 !matches!(self, Self::Ok(_))
143 }
144
145 pub fn is_ok_and(&self, predicate: impl FnOnce(&str) -> bool) -> bool {
146 match self {
147 Self::Ok(s) => predicate(s),
148 Self::PartialOk(_) => false,
149 Self::Err(_) => false,
150 }
151 }
152
153 pub fn is_err_or(&self, predicate: impl FnOnce(&str) -> bool) -> bool {
154 match self {
155 Self::Ok(s) => predicate(s),
156 Self::PartialOk(_) => true,
157 Self::Err(_) => true,
158 }
159 }
160
161 pub fn join(&self, path: impl AsRef<str>) -> Self {
163 let path = path.as_ref();
164 match self {
165 Self::Ok(s) => Self::Ok(cached_string(format!("{s}/{path}"))),
166 Self::PartialOk(s) => Self::PartialOk(cached_string(format!("{s}/{path}"))),
167 Self::Err(s) => Self::PartialOk(cached_string(format!("{}/{path}", <&'static str>::from(s)))),
168 }
169 }
170
171 pub fn cli_bash_escaped_with_style(
173 &self,
174 style: owo_colors::Style,
175 ) -> Either<impl Display, impl Display> {
176 match self {
177 Self::Ok(s) => Either::Left(style.style(shell_quote::QuoteRefExt::<String>::quoted(
178 s.as_str(),
179 shell_quote::Bash,
180 ))),
181 Self::PartialOk(s) => Either::Left(cli::theme::THEME.inline_error.style(
182 shell_quote::QuoteRefExt::<String>::quoted(s.as_str(), shell_quote::Bash),
183 )),
184 Self::Err(e) => Either::Right(
185 cli::theme::THEME
186 .inline_error
187 .style(<&'static str>::from(e)),
188 ),
189 }
190 }
191
192 pub fn bash_escaped(&self) -> Cow<'static, str> {
194 match self {
195 Self::Ok(s) | Self::PartialOk(s) => Cow::Owned(shell_quote::QuoteRefExt::quoted(
196 s.as_str(),
197 shell_quote::Bash,
198 )),
199 Self::Err(e) => Cow::Borrowed(<&'static str>::from(e)),
200 }
201 }
202
203 pub fn cli_styled(&self, style: owo_colors::Style) -> Either<impl Display + '_, impl Display> {
204 match self {
205 Self::Ok(s) => Either::Left(s.style(style)),
206 Self::PartialOk(s) => Either::Left(s.style(cli::theme::THEME.inline_error)),
207 Self::Err(e) => Either::Right(
208 cli::theme::THEME
209 .inline_error
210 .style(<&'static str>::from(e)),
211 ),
212 }
213 }
214
215 pub fn cli_escaped_styled(
216 &self,
217 style: owo_colors::Style,
218 ) -> Either<impl Display + '_, impl Display> {
219 struct DebugAsDisplay<T: Debug>(T);
221 impl<T: Debug> Display for DebugAsDisplay<T> {
222 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223 self.0.fmt(f)
224 }
225 }
226 match self {
227 Self::Ok(s) => Either::Left(style.style(DebugAsDisplay(s))),
228 Self::PartialOk(s) => Either::Left(cli::theme::THEME.inline_error.style(DebugAsDisplay(s))),
229 Self::Err(e) => Either::Right(
230 cli::theme::THEME
231 .inline_error
232 .style(<&'static str>::from(e)),
233 ),
234 }
235 }
236}