assemblyline_models/types/
classification.rs

1use std::ops::Deref;
2use std::sync::{Arc, Mutex};
3
4use serde::{Serialize, Deserialize};
5use assemblyline_markings::classification::{ClassificationParser, NormalizeOptions};
6use struct_metadata::Described;
7
8use crate::{ElasticMeta, JsonMap, ModelError};
9
10enum ClassificationMode {
11    Uninitialized,
12    Disabled,
13    Configured(Arc<ClassificationParser>)
14}
15
16static GLOBAL_CLASSIFICATION: Mutex<ClassificationMode> = Mutex::new(ClassificationMode::Uninitialized);
17
18// #[cfg(feature = "local_classification")]
19// tokio::task_local! {
20//     static TASK_CLASSIFICATION: Option<Arc<ClassificationParser>>;
21// }
22
23pub fn disable_global_classification() {
24    *GLOBAL_CLASSIFICATION.lock().unwrap() = ClassificationMode::Disabled;
25}
26
27pub fn set_global_classification(config: Arc<ClassificationParser>) {
28    *GLOBAL_CLASSIFICATION.lock().unwrap() = ClassificationMode::Configured(config);
29}
30
31pub fn unrestricted_classification() -> String {
32    match &*GLOBAL_CLASSIFICATION.lock().unwrap() {
33        ClassificationMode::Uninitialized => panic!("classification handling without defining parser"),
34        ClassificationMode::Disabled => "".to_string(),
35        ClassificationMode::Configured(parser) => parser.unrestricted().to_owned(),
36    }
37}
38
39pub fn unrestricted_classification_string() -> ClassificationString {
40    match &*GLOBAL_CLASSIFICATION.lock().unwrap() {
41        ClassificationMode::Uninitialized => panic!("classification handling without defining parser"),
42        ClassificationMode::Disabled => ClassificationString(Default::default()),
43        ClassificationMode::Configured(parser) => ClassificationString::unrestricted(parser),
44    }
45}
46
47pub fn unrestricted_expanding_classification() -> ExpandingClassification {
48    match &*GLOBAL_CLASSIFICATION.lock().unwrap() {
49        ClassificationMode::Uninitialized => panic!("classification handling without defining parser"),
50        ClassificationMode::Disabled => ExpandingClassification {
51            classification: Default::default(),
52            __access_lvl__: Default::default(),
53            __access_req__: Default::default(),
54            __access_grp1__: Default::default(),
55            __access_grp2__: Default::default(),
56        },
57        ClassificationMode::Configured(parser) => ExpandingClassification::unrestricted(parser),
58    }
59}
60
61
62// #[cfg(feature = "local_classification")]
63// pub async fn with_local_classification<F: std::future::Future>(config: Arc<ClassificationParser>, f: F)  {
64//     TASK_CLASSIFICATION.scope(Some(config), f).await
65// }
66
67// MARK: ExpandingClassification
68
69/// Expanding classification type
70#[derive(Debug, Clone, Eq)]
71pub struct ExpandingClassification<const USER: bool=false> { 
72    pub classification: String,
73    pub __access_lvl__: i32,
74    pub __access_req__: Vec<String>,
75    pub __access_grp1__: Vec<String>,
76    pub __access_grp2__: Vec<String>,
77}
78
79impl<const USER: bool> ExpandingClassification<USER> {
80    pub fn new(classification: String, parser: &ClassificationParser) -> Result<Self, ModelError> {
81        if parser.original_definition.enforce {
82
83            let parts = parser.get_classification_parts(&classification, false, true, !USER)?;
84            let classification = parser.get_normalized_classification_text(parts.clone(), false, false)?;
85
86            Ok(Self {
87                classification,
88                __access_lvl__: parts.level,
89                __access_req__: parts.required,
90                __access_grp1__: if parts.groups.is_empty() { vec!["__EMPTY__".to_owned()] } else { parts.groups },
91                __access_grp2__: if parts.subgroups.is_empty() { vec!["__EMPTY__".to_owned()] } else { parts.subgroups },
92            })
93        } else {
94            Ok(Self {
95                classification,
96                __access_lvl__: Default::default(),
97                __access_req__: Default::default(),
98                __access_grp1__: vec!["__EMPTY__".to_owned()],
99                __access_grp2__: vec!["__EMPTY__".to_owned()],
100            })
101        }
102    }
103
104    pub fn try_unrestricted() -> Option<Self> {
105        if let ClassificationMode::Configured(parser) = &*GLOBAL_CLASSIFICATION.lock().unwrap() {
106            Some(Self::unrestricted(parser))
107        } else {
108            None
109        }   
110    }
111
112    pub fn unrestricted(parser: &ClassificationParser) -> Self {
113        Self::new(parser.unrestricted().to_string(), parser).unwrap()
114    }
115
116    pub fn as_str(&self) -> &str {
117        &self.classification
118    }
119
120    pub fn insert(parser: &ClassificationParser, output: &mut JsonMap, classification: &str) -> Result<(), ModelError> {
121        use serde_json::json;
122        if parser.original_definition.enforce {
123            let parts = parser.get_classification_parts(classification, true, true, !USER)?;
124            let classification = parser.get_normalized_classification_text(parts.clone(), true, false)?;
125
126            output.insert("classification".to_string(), json!(classification));
127            output.insert("__access_lvl__".to_string(), json!(parts.level));
128            output.insert("__access_req__".to_string(), json!(parts.required));
129            output.insert("__access_grp1__".to_string(), json!(if parts.groups.is_empty() { vec!["__EMPTY__".to_string()] } else { parts.groups }));
130            output.insert("__access_grp2__".to_string(), json!(if parts.subgroups.is_empty() { vec!["__EMPTY__".to_string()] } else { parts.subgroups }));
131            Ok(())
132        } else {
133            output.insert("classification".to_string(), json!(classification));
134            output.insert("__access_lvl__".to_string(), json!(0));
135            output.insert("__access_req__".to_string(), serde_json::Value::Array(Default::default()));
136            output.insert("__access_grp1__".to_string(), json!(&["__EMPTY__"]));
137            output.insert("__access_grp2__".to_string(), json!(&["__EMPTY__"]));
138            Ok(())
139        }
140    }
141}
142
143#[derive(Serialize, Deserialize)]
144struct RawClassification { 
145    pub classification: String,
146    #[serde(default)]
147    pub __access_lvl__: i32,
148    #[serde(default)]
149    pub __access_req__: Vec<String>,
150    #[serde(default)]
151    pub __access_grp1__: Vec<String>,
152    #[serde(default)]
153    pub __access_grp2__: Vec<String>,
154}
155
156impl<const U: bool> From<&ExpandingClassification<U>> for RawClassification {
157    fn from(value: &ExpandingClassification<U>) -> Self {
158        Self {
159            classification: value.classification.clone(),
160            __access_lvl__: value.__access_lvl__,
161            __access_req__: value.__access_req__.clone(),
162            __access_grp1__: value.__access_grp1__.clone(),
163            __access_grp2__: value.__access_grp2__.clone(),
164        }
165    }
166}
167
168impl<const U: bool> From<RawClassification> for ExpandingClassification<U> {
169    fn from(value: RawClassification) -> Self {
170        Self {
171            classification: value.classification.clone(),
172            __access_lvl__: value.__access_lvl__,
173            __access_req__: value.__access_req__.clone(),
174            __access_grp1__: value.__access_grp1__.clone(),
175            __access_grp2__: value.__access_grp2__.clone(),
176        }
177    }
178}
179
180
181impl<const U: bool> Serialize for ExpandingClassification<U> {
182    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
183    where S: serde::Serializer 
184    {
185        match &*GLOBAL_CLASSIFICATION.lock().unwrap() {
186            ClassificationMode::Uninitialized => return Err(serde::ser::Error::custom("classification engine not initalized")),
187            // if it is disabled just serialize as raw
188            ClassificationMode::Disabled |
189            // assume the content of the struct is already normalized on load or construction
190            ClassificationMode::Configured(_) => {},
191        }
192        RawClassification::from(self).serialize(serializer)
193    }
194}
195
196impl<'de, const U: bool> Deserialize<'de> for ExpandingClassification<U> {
197    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
198    where D: serde::Deserializer<'de> 
199    {
200        let parser = match &*GLOBAL_CLASSIFICATION.lock().unwrap() {
201            ClassificationMode::Uninitialized => return Err(serde::de::Error::custom("classification engine not initalized")),
202            // if it is disabled just handle as raw
203            ClassificationMode::Disabled => None,
204            // normalize on load
205            ClassificationMode::Configured(parser) => Some(parser.clone()),
206        };
207
208        if let Some(parser) = parser {
209            let raw = RawClassification::deserialize(deserializer)?;
210            Self::new(raw.classification, &parser).map_err(serde::de::Error::custom)
211        } else {
212            Ok(RawClassification::deserialize(deserializer)?.into())
213        }        
214    }
215}
216
217impl<const USER: bool> Described<ElasticMeta> for ExpandingClassification<USER> {
218    fn metadata() -> struct_metadata::Descriptor<ElasticMeta> {
219
220        // let group_meta = ElasticMeta {
221        //     index: Some(true),
222        //     store: None,
223        //     ..Default::default()
224        // };
225
226        struct_metadata::Descriptor { 
227            docs: None, 
228            metadata: ElasticMeta{mapping: Some("classification"), ..Default::default()}, 
229            kind: struct_metadata::Kind::new_struct("ExpandingClassification", vec![
230                struct_metadata::Entry { label: "classification", docs: None, metadata: ElasticMeta{mapping: Some("classification"), ..Default::default()}, type_info: String::metadata(), has_default: false, aliases: &["classification"] },
231                // struct_metadata::Entry { label: "__access_lvl__", docs: None, metadata: Default::default(), type_info: i32::metadata(), has_default: false, aliases: &["__access_lvl__"] },
232                // struct_metadata::Entry { label: "__access_req__", docs: None, metadata: Default::default(), type_info: Vec::<String>::metadata(), has_default: false, aliases: &["__access_req__"] },
233                // struct_metadata::Entry { label: "__access_grp1__", docs: None, metadata: Default::default(), type_info: Vec::<String>::metadata(), has_default: false, aliases: &["__access_grp1__"] },
234                // struct_metadata::Entry { label: "__access_grp2__", docs: None, metadata: Default::default(), type_info: Vec::<String>::metadata(), has_default: false, aliases: &["__access_grp2__"] },
235            ], &mut [], &mut[]),
236            // kind: struct_metadata::Kind::Aliased { 
237            //     name: "ExpandingClassification", 
238            //     kind: Box::new(String::metadata())
239            // },
240        }
241    }
242}
243
244impl<const U: bool> PartialEq<&str> for ExpandingClassification<U> {
245    fn eq(&self, other: &&str) -> bool {
246        let parser = match &*GLOBAL_CLASSIFICATION.lock().unwrap() {
247            ClassificationMode::Uninitialized | ClassificationMode::Disabled => None,
248            ClassificationMode::Configured(parser) => Some(parser.clone()),
249        };
250
251        if let Some(parser) = parser {
252            match (parser.normalize_classification(&self.classification), parser.normalize_classification(other)) {
253                (Ok(a), Ok(b)) => a == b,
254                _ => false
255            }
256        } else {
257            self.classification.as_str() == *other
258        }
259    }
260}
261
262impl<const U: bool> PartialEq for ExpandingClassification<U> {
263    fn eq(&self, other: &Self) -> bool {
264        self.eq(&other.classification.as_str())
265    }
266}
267
268
269// MARK: ClassificationString
270
271/// A classification value stored as a string
272#[derive(Described, Debug, Clone, Eq)]
273#[metadata_type(ElasticMeta)]
274#[metadata(mapping="classification_string")]
275pub struct ClassificationString(pub (crate) String);
276
277impl From<ClassificationString> for String {
278    fn from(value: ClassificationString) -> Self {
279        value.0
280    }
281}
282
283impl Deref for ClassificationString {
284    type Target = str;
285
286    fn deref(&self) -> &Self::Target {
287        &self.0
288    }
289}
290
291impl ClassificationString {
292    pub fn new(classification: String, parser: &Arc<ClassificationParser>) -> Result<Self, ModelError> {
293        Ok(Self(parser.normalize_classification_options(&classification, NormalizeOptions::short())?))
294    }
295
296    pub fn new_unchecked(classification: String) -> Self {
297        Self(classification)
298    }
299
300    pub fn try_unrestricted() -> Option<Self> {
301        if let ClassificationMode::Configured(parser) = &*GLOBAL_CLASSIFICATION.lock().unwrap() {
302            Some(Self::unrestricted(parser))
303        } else {
304            None
305        }   
306    }
307    
308    pub fn unrestricted(parser: &ClassificationParser) -> Self {
309        Self(parser.unrestricted().to_owned())
310    }
311
312    pub fn as_str(&self) -> &str {
313        &self.0
314    }
315
316    pub fn default_unrestricted() -> ClassificationString {
317        match &*GLOBAL_CLASSIFICATION.lock().unwrap() {
318            ClassificationMode::Uninitialized => panic!("classification handling without defining parser"),
319            ClassificationMode::Disabled => ClassificationString(Default::default()),
320            ClassificationMode::Configured(parser) => ClassificationString::unrestricted(parser),
321        }
322    }
323}
324
325#[derive(Serialize, Deserialize)]
326struct RawClassificationString(String);
327
328
329impl From<&ClassificationString> for RawClassificationString {
330    fn from(value: &ClassificationString) -> Self {
331        Self(value.0.clone())
332    }
333}
334
335impl From<RawClassificationString> for ClassificationString {
336    fn from(value: RawClassificationString) -> Self {
337        Self(value.0.clone())
338    }
339}
340
341
342impl Serialize for ClassificationString {
343    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
344    where S: serde::Serializer 
345    {
346        match &*GLOBAL_CLASSIFICATION.lock().unwrap() {
347            ClassificationMode::Uninitialized => return Err(serde::ser::Error::custom("classification engine not initalized")),
348            // if it is disabled just serialize as raw
349            ClassificationMode::Disabled |
350            // assume the content of the struct is already normalized on load or construction
351            ClassificationMode::Configured(_) => {},
352        }
353        RawClassificationString::from(self).serialize(serializer)
354    }
355}
356
357// fn response_or_empty_string<'de, D>(d: D) -> Result<Option<Response>, D::Error>
358// where
359//     D: Deserializer<'de>,
360// {
361//     #[derive(Deserialize)]
362//     #[serde(untagged)]
363//     enum MyHelper<'a> {
364//         S(&'a str),
365//         R(Response),
366//     }
367
368//     match MyHelper::deserialize(d) {
369//         Ok(MyHelper::R(r)) => Ok(Some(r)),
370//         Ok(MyHelper::S(s)) if s.is_empty() => Ok(None),
371//         Ok(MyHelper::S(_)) => Err(D::Error::custom("only empty strings may be provided")),
372//         Err(err) => Err(err),
373//     }
374// }
375
376impl<'de> Deserialize<'de> for ClassificationString {
377    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
378    where D: serde::Deserializer<'de> 
379    {
380        let parser = match &*GLOBAL_CLASSIFICATION.lock().unwrap() {
381            ClassificationMode::Uninitialized => return Err(serde::de::Error::custom("classification engine not initalized")),
382            // if it is disabled just handle as raw
383            ClassificationMode::Disabled => None,
384            // normalize on load
385            ClassificationMode::Configured(parser) => Some(parser.clone()),
386        };
387
388        if let Some(parser) = parser {
389            let raw = RawClassificationString::deserialize(deserializer)?;
390            Self::new(raw.0, &parser).map_err(serde::de::Error::custom)
391        } else {
392            Ok(RawClassificationString::deserialize(deserializer)?.into())
393        }
394    }
395}
396
397impl PartialEq for ClassificationString {
398    fn eq(&self, other: &Self) -> bool {
399        let parser = match &*GLOBAL_CLASSIFICATION.lock().unwrap() {
400            ClassificationMode::Uninitialized | ClassificationMode::Disabled => None,
401            ClassificationMode::Configured(parser) => Some(parser.clone()),
402        };
403
404        if let Some(parser) = parser {
405            match (parser.normalize_classification(&self.0), parser.normalize_classification(&other.0)) {
406                (Ok(a), Ok(b)) => a == b,
407                _ => false
408            }
409        } else {
410            self.0 == other.0
411        }
412    }
413}