assemblyline_models/datastore/
error.rs

1use chrono::{DateTime, Utc};
2use rand::seq::IteratorRandom;
3use serde::{Serialize, Deserialize};
4use serde_with::{SerializeDisplay, DeserializeFromStr};
5use struct_metadata::Described;
6use strum::IntoEnumIterator;
7
8use crate::messages::task::{generate_conf_key, Task};
9use crate::{random_word, random_words, ElasticMeta, Readable};
10use crate::types::{ServiceName, Sha256, Text};
11
12
13#[derive(SerializeDisplay, DeserializeFromStr, strum::Display, strum::EnumString, Described, Clone, Copy)]
14#[metadata_type(ElasticMeta)]
15pub enum Status {
16    #[strum(serialize = "FAIL_NONRECOVERABLE")]
17    FailNonrecoverable,
18    #[strum(serialize = "FAIL_RECOVERABLE")]
19    FailRecoverable,
20}
21
22#[cfg(feature = "rand")]
23impl rand::distr::Distribution<Status> for rand::distr::StandardUniform {
24    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Status {
25        if rng.random() {
26            Status::FailNonrecoverable
27        } else {
28            Status::FailRecoverable
29        }
30    }
31}
32
33impl Status {
34    pub fn is_recoverable(&self) -> bool {
35        matches!(self, Status::FailRecoverable)
36    }
37    pub fn is_nonrecoverable(&self) -> bool {
38        matches!(self, Status::FailNonrecoverable)
39    }
40}
41
42
43#[derive(SerializeDisplay, DeserializeFromStr, strum::Display, strum::EnumString, strum::EnumIter, Described, Clone, Copy)]
44#[metadata_type(ElasticMeta)]
45pub enum ErrorTypes {
46    #[strum(serialize = "UNKNOWN")]
47    Unknown = 0,
48    #[strum(serialize = "EXCEPTION")]
49    Exception = 1,
50    #[strum(serialize = "MAX DEPTH REACHED")]
51    MaxDepthReached = 10,
52    #[strum(serialize = "MAX FILES REACHED")]
53    MaxFilesReached = 11,
54    #[strum(serialize = "MAX RETRY REACHED")]
55    MaxRetryReached = 12,
56    #[strum(serialize = "SERVICE BUSY")]
57    ServiceBusy = 20,
58    #[strum(serialize = "SERVICE DOWN")]
59    ServiceDown = 21,
60    #[strum(serialize = "TASK PRE-EMPTED")]
61    TaskPreempted = 30
62}
63
64#[cfg(feature = "rand")]
65impl rand::distr::Distribution<ErrorTypes> for rand::distr::StandardUniform {
66    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> ErrorTypes {
67        match ErrorTypes::iter().choose(rng) {
68            Some(value) => value,
69            None => ErrorTypes::Unknown,
70        }
71    }
72}
73
74/// Error Response from a Service
75#[derive(Serialize, Deserialize, Described)]
76#[metadata_type(ElasticMeta)]
77#[metadata(index=true, store=true)]
78pub struct Response {
79    /// Error message
80    #[metadata(copyto="__text__")]
81    pub message: Text,
82    /// Information about where the service was processed
83    pub service_debug_info: Option<String>,
84    /// Service Name
85    #[metadata(copyto="__text__")]
86    pub service_name: ServiceName,
87    /// Service Tool Version
88    #[metadata(copyto="__text__")]
89    pub service_tool_version: Option<String>,
90    /// Service Version
91    pub service_version: String,
92    /// Status of error produced by service
93    pub status: Status,
94}
95
96#[cfg(feature = "rand")]
97impl rand::distr::Distribution<Response> for rand::distr::StandardUniform {
98    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Response {
99        let word_count = rng.random_range(5..25);
100        Response {
101            message: Text(random_words(rng, word_count).join(" ")),
102            service_debug_info: None,
103            service_name: ServiceName::from_string(random_word(rng)),
104            service_tool_version: None,
105            service_version: "0.0".to_string(),
106            status: rng.random(),
107        }
108    }
109}
110
111
112/// Error Model used by Error Viewer
113#[derive(Serialize, Deserialize, Described)]
114#[metadata_type(ElasticMeta)]
115#[metadata(index=true, store=true)]
116pub struct Error {
117    /// Time at which the error was archived
118    #[serde(default)]
119    pub archive_ts: Option<DateTime<Utc>>,
120    /// Error creation timestamp
121    #[serde(default="chrono::Utc::now")]
122    pub created: DateTime<Utc>,
123    /// Expiry timestamp
124    #[metadata(store=false)]
125    pub expiry_ts: Option<DateTime<Utc>>,
126    /// Response from the service
127    pub response: Response,
128    /// SHA256 of file related to service error
129    #[metadata(copyto="__text__")]
130    pub sha256: Sha256,
131    /// Type of error
132    #[serde(rename="type", default="default_error_type")]
133    pub error_type: ErrorTypes,
134}
135
136#[cfg(feature = "rand")]
137impl rand::distr::Distribution<Error> for rand::distr::StandardUniform {
138    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Error {
139        Error {
140            archive_ts: None,
141            created: chrono::Utc::now(),
142            expiry_ts: None,
143            response: rng.random(),
144            sha256: rng.random(),
145            error_type: rng.random(),
146        }
147    }
148}
149
150impl Error {
151    pub fn build_key(&self, service_tool_version: Option<&str>, task: Option<&Task>) -> Result<String, serde_json::Error> {
152        let key_list = [
153            self.sha256.to_string(),
154            self.response.service_name.replace('.', "_"),
155            format!("v{}", self.response.service_version.replace('.', "_")),
156            format!("c{}", generate_conf_key(service_tool_version, task, None)?),
157            format!("e{}", self.error_type as u64),
158        ];
159
160        Ok(key_list.join("."))
161    }
162
163    pub fn build_unique_key(&self, service_tool_version: Option<&str>, task: Option<&Task>) -> Result<String, serde_json::Error> {
164        Ok(self.build_key(service_tool_version, task)? + "." + &rand::random::<u64>().to_string())
165    }
166}
167
168fn default_error_type() -> ErrorTypes { ErrorTypes::Exception }
169
170impl Readable for Error {
171    fn set_from_archive(&mut self, _from_archive: bool) {}
172}