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