tracexec_core/event/
message.rs

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// we need to implement custom Display so Result and Either do not fit.
80#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
81pub enum OutputMsg {
82  Ok(ArcStr),
83  // Part of the message contains error
84  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  /// Escape the content for bash shell if it is not error
135  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  /// Join two paths with a '/', preserving the semantics of [`OutputMsg`]
162  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  /// Escape the content for bash shell if it is not error
172  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  /// Escape the content for bash shell if it is not error
193  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    // We (ab)use Rust's Debug feature to escape our string.
220    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}