Skip to main content

assemblyline_models/datastore/
error.rs

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