1use std::path::PathBuf;
2
3use soma_fleet::{FleetError, HostId};
4
5const PUBLIC_DIAGNOSTIC_LIMIT: usize = 2048;
6
7pub type InfraResult<T> = Result<T, InfraError>;
9
10#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
12#[non_exhaustive]
13pub enum InfraError {
14 #[error(transparent)]
16 Fleet(#[from] FleetError),
17 #[error("invalid {domain} request: {message}")]
19 InvalidRequest {
20 domain: &'static str,
22 message: String,
24 },
25 #[error("{domain} operation is unsupported for host {host}")]
27 UnsupportedTarget {
28 domain: &'static str,
30 host: HostId,
32 },
33 #[error("{domain} command failed on {host} with exit {exit_code:?}: {stderr}")]
35 CommandFailed {
36 domain: &'static str,
38 host: HostId,
40 exit_code: Option<i32>,
42 stderr: String,
44 },
45 #[error("failed to parse {domain} output: {message}")]
47 Parse {
48 domain: &'static str,
50 message: String,
52 },
53 #[error("filesystem {operation} failed for {path}: {message}")]
55 Filesystem {
56 operation: &'static str,
58 path: PathBuf,
60 message: String,
62 },
63 #[error("path is outside admitted read roots: {0}")]
65 PathOutsideRoots(PathBuf),
66 #[error("Docker access failed: {0}")]
68 Docker(String),
69}
70
71pub(crate) fn public_diagnostic(bytes: &[u8]) -> String {
72 #[derive(Clone, Copy)]
73 enum EscapeState {
74 None,
75 Escape,
76 ControlSequence,
77 }
78
79 let text = String::from_utf8_lossy(bytes);
80 let mut sanitized = String::with_capacity(text.len().min(PUBLIC_DIAGNOSTIC_LIMIT));
81 let mut escape = EscapeState::None;
82 for character in text.chars() {
83 match escape {
84 EscapeState::Escape => {
85 escape = if character == '[' {
86 EscapeState::ControlSequence
87 } else {
88 EscapeState::None
89 };
90 continue;
91 }
92 EscapeState::ControlSequence => {
93 if ('@'..='~').contains(&character) {
94 escape = EscapeState::None;
95 }
96 continue;
97 }
98 EscapeState::None => {}
99 }
100 if character == '\u{1b}' {
101 escape = EscapeState::Escape;
102 continue;
103 }
104 if character.is_control() {
105 if !sanitized.ends_with(' ') && sanitized.len() < PUBLIC_DIAGNOSTIC_LIMIT {
106 sanitized.push(' ');
107 }
108 } else if sanitized.len() + character.len_utf8() <= PUBLIC_DIAGNOSTIC_LIMIT {
109 sanitized.push(character);
110 } else {
111 break;
112 }
113 }
114 sanitized.trim().to_owned()
115}
116
117#[cfg(test)]
118#[path = "error_tests.rs"]
119mod tests;