assemblyline_models/datastore/
error.rs1use 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#[derive(Serialize, Deserialize, Described)]
103#[metadata_type(ElasticMeta)]
104#[metadata(index=true, store=true)]
105pub struct Response {
106 #[metadata(copyto="__text__")]
108 pub message: Text,
109 pub service_debug_info: Option<String>,
111 #[metadata(copyto="__text__")]
113 pub service_name: ServiceName,
114 #[metadata(copyto="__text__")]
116 pub service_tool_version: Option<String>,
117 pub service_version: String,
119 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#[derive(Serialize, Deserialize, Described)]
141#[metadata_type(ElasticMeta)]
142#[metadata(index=true, store=true)]
143pub struct Error {
144 #[serde(default)]
146 pub archive_ts: Option<DateTime<Utc>>,
147 #[serde(default="chrono::Utc::now")]
149 pub created: DateTime<Utc>,
150 #[metadata(store=false)]
152 pub expiry_ts: Option<DateTime<Utc>>,
153 pub response: Response,
155 #[metadata(copyto="__text__")]
157 pub sha256: Sha256,
158 #[serde(rename="type", default="default_error_type")]
160 pub error_type: ErrorTypes,
161 #[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}