assemblyline_models/datastore/
error.rs

1use chrono::{DateTime, TimeDelta, Utc};
2use rand::seq::IteratorRandom;
3use serde::{Serialize, Deserialize};
4use serde_with::{SerializeDisplay, DeserializeFromStr};
5use struct_metadata::Described;
6use strum::IntoEnumIterator;
7
8use crate::datastore::Submission;
9use crate::messages::task::{generate_conf_key, Task};
10use crate::{random_word, random_words, ElasticMeta, Readable};
11use crate::types::{ServiceName, Sha256, Text};
12
13
14#[derive(SerializeDisplay, DeserializeFromStr, strum::Display, strum::EnumString, Described, Clone, Copy)]
15#[metadata_type(ElasticMeta)]
16pub enum Status {
17    #[strum(serialize = "FAIL_NONRECOVERABLE")]
18    FailNonrecoverable,
19    #[strum(serialize = "FAIL_RECOVERABLE")]
20    FailRecoverable,
21}
22
23#[cfg(feature = "rand")]
24impl rand::distr::Distribution<Status> for rand::distr::StandardUniform {
25    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Status {
26        if rng.random() {
27            Status::FailNonrecoverable
28        } else {
29            Status::FailRecoverable
30        }
31    }
32}
33
34impl Status {
35    pub fn is_recoverable(&self) -> bool {
36        matches!(self, Status::FailRecoverable)
37    }
38    pub fn is_nonrecoverable(&self) -> bool {
39        matches!(self, Status::FailNonrecoverable)
40    }
41}
42
43#[derive(SerializeDisplay, DeserializeFromStr, strum::Display, strum::EnumString, strum::EnumIter, Described, Clone, Copy)]
44#[strum(serialize_all="lowercase")]
45#[metadata_type(ElasticMeta)]
46pub enum ErrorSeverity {
47    Error,
48    Warning,
49}
50
51impl Default for ErrorSeverity {
52    fn default() -> Self {
53        Self::Error
54    }
55}
56
57#[cfg(feature = "rand")]
58impl rand::distr::Distribution<ErrorSeverity> for rand::distr::StandardUniform {
59    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> ErrorSeverity {
60        match ErrorSeverity::iter().choose(rng) {
61            Some(value) => value,
62            None => ErrorSeverity::Error,
63        }
64    }
65}
66
67#[derive(SerializeDisplay, DeserializeFromStr, strum::Display, strum::EnumString, strum::EnumIter, Described, Clone, Copy)]
68#[metadata_type(ElasticMeta)]
69pub enum ErrorTypes {
70    #[strum(serialize = "UNKNOWN")]
71    Unknown = 0,
72    #[strum(serialize = "EXCEPTION")]
73    Exception = 1,
74    #[strum(serialize = "MAX DEPTH REACHED")]
75    MaxDepthReached = 10,
76    #[strum(serialize = "MAX FILES REACHED")]
77    MaxFilesReached = 11,
78    #[strum(serialize = "MAX RETRY REACHED")]
79    MaxRetryReached = 12,
80    #[strum(serialize = "SERVICE BUSY")]
81    ServiceBusy = 20,
82    #[strum(serialize = "SERVICE DOWN")]
83    ServiceDown = 21,
84    #[strum(serialize = "TASK PRE-EMPTED")]
85    TaskPreempted = 30
86}
87
88#[cfg(feature = "rand")]
89impl rand::distr::Distribution<ErrorTypes> for rand::distr::StandardUniform {
90    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> ErrorTypes {
91        match ErrorTypes::iter().choose(rng) {
92            Some(value) => value,
93            None => ErrorTypes::Unknown,
94        }
95    }
96}
97
98/// Error Response from a Service
99#[derive(Serialize, Deserialize, Described)]
100#[metadata_type(ElasticMeta)]
101#[metadata(index=true, store=true)]
102pub struct Response {
103    /// Error message
104    #[metadata(copyto="__text__")]
105    pub message: Text,
106    /// Information about where the service was processed
107    pub service_debug_info: Option<String>,
108    /// Service Name
109    #[metadata(copyto="__text__")]
110    pub service_name: ServiceName,
111    /// Service Tool Version
112    #[metadata(copyto="__text__")]
113    pub service_tool_version: Option<String>,
114    /// Service Version
115    pub service_version: String,
116    /// Status of error produced by service
117    pub status: Status,
118}
119
120#[cfg(feature = "rand")]
121impl rand::distr::Distribution<Response> for rand::distr::StandardUniform {
122    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Response {
123        let word_count = rng.random_range(5..25);
124        Response {
125            message: Text(random_words(rng, word_count).join(" ")),
126            service_debug_info: None,
127            service_name: ServiceName::from_string(random_word(rng)),
128            service_tool_version: None,
129            service_version: "0.0".to_string(),
130            status: rng.random(),
131        }
132    }
133}
134
135
136/// Error Model used by Error Viewer
137#[derive(Serialize, Deserialize, Described)]
138#[metadata_type(ElasticMeta)]
139#[metadata(index=true, store=true)]
140pub struct Error {
141    /// Time at which the error was archived
142    #[serde(default)]
143    pub archive_ts: Option<DateTime<Utc>>,
144    /// Error creation timestamp
145    #[serde(default="chrono::Utc::now")]
146    pub created: DateTime<Utc>,
147    /// Expiry timestamp
148    #[metadata(store=false)]
149    pub expiry_ts: Option<DateTime<Utc>>,
150    /// Response from the service
151    pub response: Response,
152    /// SHA256 of file related to service error
153    #[metadata(copyto="__text__")]
154    pub sha256: Sha256,
155    /// Type of error
156    #[serde(rename="type", default="default_error_type")]
157    pub error_type: ErrorTypes,
158    /// The severity of an error
159    #[serde(default)]
160    pub severity: ErrorSeverity
161}
162
163#[cfg(feature = "rand")]
164impl rand::distr::Distribution<Error> for rand::distr::StandardUniform {
165    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Error {
166        Error {
167            archive_ts: None,
168            created: chrono::Utc::now(),
169            expiry_ts: None,
170            response: rng.random(),
171            sha256: rng.random(),
172            error_type: rng.random(),
173            severity: rng.random(),
174        }
175    }
176}
177
178impl Error {
179    pub fn build_key(&self, service_tool_version: Option<&str>, task: Option<&Task>) -> Result<String, serde_json::Error> {
180        let key_list = [
181            self.sha256.to_string(),
182            self.response.service_name.replace('.', "_"),
183            format!("v{}", self.response.service_version.replace('.', "_")),
184            format!("c{}", generate_conf_key(service_tool_version, task, None)?),
185            format!("e{}", self.error_type as u64),
186        ];
187
188        Ok(key_list.join("."))
189    }
190
191    pub fn build_unique_key(&self, service_tool_version: Option<&str>, task: Option<&Task>) -> Result<String, serde_json::Error> {
192        Ok(self.build_key(service_tool_version, task)? + "." + &rand::random::<u64>().to_string())
193    }
194
195    pub fn from_submission(context: &Submission) -> builder::NeedsService {
196        builder::NeedsService { error: Self {
197            archive_ts: None,
198            created: chrono::Utc::now(),
199            expiry_ts: context.expiry_ts,
200            response: Response { 
201                message: Text("".to_owned()), 
202                service_debug_info: None, 
203                service_name: "".into(), 
204                service_tool_version: None, 
205                service_version: "".to_owned(), 
206                status: Status::FailNonrecoverable 
207            },
208            sha256: context.files[0].sha256.clone(),
209            error_type: ErrorTypes::Exception,
210            severity: ErrorSeverity::Error,
211        } }
212    }
213
214    pub fn from_task(context: &crate::messages::task::Task) -> builder::NeedsMessage {
215        builder::NeedsMessage { error: Self {
216            archive_ts: None,
217            created: chrono::Utc::now(),
218            expiry_ts: if context.ttl > 0 { Some(Utc::now() + TimeDelta::days(context.ttl as i64)) } else { None },
219            response: Response { 
220                message: Text("".to_owned()), 
221                service_debug_info: None, 
222                service_name: context.service_name, 
223                service_tool_version: None, 
224                service_version: "0".to_owned(), 
225                status: Status::FailNonrecoverable 
226            },
227            sha256: context.fileinfo.sha256.clone(),
228            error_type: ErrorTypes::Exception,
229            severity: ErrorSeverity::Error,
230        } }
231    }
232
233    pub fn from_result(context: &crate::datastore::result::Result) -> builder::NeedsMessage {
234        builder::NeedsMessage { error: Self {
235            archive_ts: None,
236            created: chrono::Utc::now(),
237            expiry_ts: context.expiry_ts,
238            response: Response { 
239                message: Text("".to_owned()), 
240                service_debug_info: context.response.service_debug_info.clone(), 
241                service_name: context.response.service_name, 
242                service_tool_version: context.response.service_tool_version.clone(), 
243                service_version: context.response.service_version.clone(), 
244                status: Status::FailNonrecoverable 
245            },
246            sha256: context.sha256.clone(),
247            error_type: ErrorTypes::Exception,
248            severity: ErrorSeverity::Error,
249        } }
250    }
251}
252
253fn default_error_type() -> ErrorTypes { ErrorTypes::Exception }
254
255impl Readable for Error {
256    fn set_from_archive(&mut self, _from_archive: bool) {}
257}
258
259pub mod builder {
260    use super::ErrorTypes;
261    use crate::datastore::error::{ErrorSeverity, Status};
262    use crate::types::{ServiceName, Sha256, Text};
263
264
265    pub struct NeedsService {
266        pub (crate) error: super::Error,
267    }
268
269    impl NeedsService {
270        pub fn service(mut self, name: ServiceName, version: String) -> NeedsMessage {
271            self.error.response.service_name = name;
272            self.error.response.service_version = version;
273            NeedsMessage { error: self.error }
274        }
275    }
276
277    pub struct NeedsMessage {
278        pub (crate) error: super::Error,
279    }
280
281    impl NeedsMessage {
282        pub fn sha256(mut self, hash: Sha256) -> Self {
283            self.error.sha256 = hash; self
284        }
285
286        pub fn error_type(mut self, error_type: ErrorTypes) -> Self {
287            self.error.error_type = error_type; self
288        }
289
290        pub fn status(mut self, status: Status) -> Self {
291            self.error.response.status = status; self
292        }
293
294        pub fn severity(mut self, severity: ErrorSeverity) -> Self {
295            self.error.severity = severity; self
296        }
297
298        pub fn maybe_tool_version(mut self, version: Option<String>) -> Self {
299            self.error.response.service_tool_version = version; self
300        }
301
302        pub fn tool_version(mut self, version: String) -> Self {
303            self.error.response.service_tool_version = Some(version); self
304        }
305
306        pub fn message(mut self, message: String) -> super::Error {
307            self.error.response.message = Text(message); self.error
308        }
309    }
310
311}