assemblyline_models/
meta.rs

1use std::collections::{HashMap, BTreeMap};
2
3use serde::{Serialize, Deserialize};
4use serde_json::json;
5use struct_metadata::{Described, Descriptor, Kind, MetadataKind};
6use crate::serialize::{deserialize_bool, deserialize_string_or_list};
7
8
9/// Retrieve all subfields recursively flattened into a single listing.    
10/// 
11/// This method strips direct information about whether a field is optional or part of a sequence and includes it as boolean flags.
12/// response type is mapping from path to (type info, bool flag for multivalued, bool flag for optional)
13pub fn flatten_fields(target: &Descriptor<ElasticMeta>) -> HashMap<Vec<String>, (&Descriptor<ElasticMeta>, bool, bool)> {    
14    // if target.metadata.mapping.is_some() {
15    //     return [(vec![], (target, false, false))].into_iter().collect()
16    // }
17    match &target.kind {
18        Kind::Struct { children , ..} => {
19            let mut fields = HashMap::<_, _>::new();
20            for child in children {
21                for (mut path, (subfield, multivalued, optional)) in flatten_fields(&child.type_info) {
22                    let mut label = vec![child.label.to_owned()];
23                    if !path.is_empty() {
24                        label.append(&mut path);
25                    }
26                    fields.insert(label, (subfield, multivalued, optional));
27                }
28            }
29            fields
30        },
31        Kind::Sequence(kind) => {
32            let mut fields = flatten_fields(kind);
33            for row in fields.iter_mut() {
34                row.1.1 = true
35            }
36            fields
37        }
38        Kind::Option(kind) => {
39            let mut fields = flatten_fields(kind);
40            for (_, _, optional) in fields.values_mut() {
41                *optional = true;
42            }
43            fields
44        },
45        Kind::Aliased { kind, .. } => flatten_fields(kind),
46        _ => [(vec![], (target, false, false))].into_iter().collect(),
47    }
48}
49
50/// Metadata fields required for converting the structs to elasticsearch mappings
51#[derive(Default, PartialEq, Eq, Debug, Clone)]
52pub struct ElasticMeta {
53    pub index: Option<bool>,
54    pub store: Option<bool>,
55    pub copyto: Option<&'static str>,
56    pub mapping: Option<&'static str>,
57    pub analyzer: Option<&'static str>,
58    pub normalizer: Option<&'static str>,
59}
60
61impl MetadataKind for ElasticMeta {
62    fn forward_propagate_context(&mut self, context: &Self) {
63        self.index = self.index.or(context.index);
64        self.store = self.store.or(context.store);
65        self.copyto = self.copyto.or(context.copyto);
66        self.mapping = self.mapping.or(context.mapping);
67        self.analyzer = self.analyzer.or(context.analyzer);
68        self.normalizer = self.normalizer.or(context.normalizer);        
69    }
70
71    fn forward_propagate_child_defaults(&mut self, kind: &ElasticMeta) {
72        self.index = self.index.or(kind.index);
73        self.store = self.store.or(kind.store);
74        self.copyto = self.copyto.or(kind.copyto);
75        self.mapping = self.mapping.or(kind.mapping);
76        self.analyzer = self.analyzer.or(kind.analyzer);
77        self.normalizer = self.normalizer.or(kind.normalizer);
78    }
79
80    fn forward_propagate_entry_defaults(&mut self, context: &ElasticMeta, kind: &ElasticMeta) {
81        self.index = self.index.or(kind.index).or(context.index);
82        self.store = self.store.or(kind.store).or(context.store);
83        self.copyto = self.copyto.or(kind.copyto).or(context.copyto);
84        self.mapping = self.mapping.or(kind.mapping).or(context.mapping);
85        self.analyzer = self.analyzer.or(kind.analyzer).or(context.analyzer);
86        self.normalizer = self.normalizer.or(kind.normalizer).or(context.normalizer);
87    }
88}
89
90#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Default)]
91#[serde(default)]
92pub struct FieldMapping {
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub enabled: Option<bool>,
95    #[serde(rename="type", skip_serializing_if = "Option::is_none")]
96    pub type_: Option<String>,
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub index: Option<bool>,
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub store: Option<bool>,
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub ignore_malformed: Option<bool>,
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub doc_values: Option<bool>,
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub ignore_above: Option<u32>,
107    #[serde(skip_serializing_if = "Vec::is_empty", deserialize_with="deserialize_string_or_list")]
108    pub copy_to: Vec<String>,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub analyzer: Option<String>,
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub normalizer: Option<String>,
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub format: Option<String>,
115    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
116    pub properties: BTreeMap<String, FieldMapping>,
117}
118
119
120#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Debug)]
121#[serde(default)]
122pub struct DynamicTemplate {
123    #[serde(rename="match", skip_serializing_if = "Option::is_none")]
124    pub match_: Option<String>,
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub match_mapping_type: Option<String>,
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub path_match: Option<String>,
129    pub mapping: FieldMapping,
130}
131
132
133fn dynamic_default() -> bool { true }
134
135#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
136#[serde(default)]
137pub struct Mappings {
138    #[serde(default="dynamic_default", deserialize_with="deserialize_bool")]
139    pub dynamic: bool,
140    pub properties: BTreeMap<String, FieldMapping>,
141    pub dynamic_templates: Vec<HashMap<String, DynamicTemplate>>,
142}
143
144impl Default for Mappings {
145    fn default() -> Self {
146        Self { dynamic: dynamic_default(), properties: Default::default(), dynamic_templates: Default::default() }
147    }
148}
149
150impl Mappings {
151    fn insert(&mut self, name: &str, meta: &ElasticMeta, mut field: FieldMapping) {
152        field.index = field.index.or(meta.index);
153        field.store = field.store.or(meta.store);
154
155        if field.type_.clone().is_some_and(|x| x != "text") {
156            field.doc_values = meta.index;
157        }
158
159        if let Some(value) = meta.copyto {
160            field.copy_to.push(value.to_owned());
161            field.copy_to.sort_unstable();
162            field.copy_to.dedup();
163            field.copy_to.retain(|s|!s.is_empty());
164        }
165        // field.copy_to = field.copy_to.or(meta.copyto.map(ToOwned::to_owned));
166        // if field.copy_to.as_ref().is_some_and(|copyto| copyto.is_empty()) {
167        //     field.copy_to = None;
168        // }
169        field.analyzer = field.analyzer.or(meta.analyzer.map(ToOwned::to_owned));
170        field.normalizer = field.normalizer.or(meta.normalizer.map(ToOwned::to_owned));
171            
172        self.properties.insert(name.trim_matches('.').to_owned(), field);
173    }
174
175    fn build_field(&mut self, label: Option<&str>, kind: &Kind<ElasticMeta>, meta: &ElasticMeta, prefix: &[&str], _allow_refuse_implicit: bool) -> Result<(), MappingError> {
176        // resolve the absolute name for this field
177        let mut path = Vec::from(prefix);
178        if let Some(label) = label {
179            path.push(label);
180        }
181        let full_name = path.join(".");
182
183        // if a mapping is simple or has been explicity set, use it
184        let simple_mapping = meta.mapping.or(simple_mapping(kind));
185        // println!("{full_name} | {simple_mapping:?}");
186
187        if let Some(mapping) = simple_mapping {
188            if mapping.eq_ignore_ascii_case("classification") {
189                self.insert(&full_name, meta, FieldMapping{ type_: "keyword".to_owned().into(), ..Default::default() });
190                if !full_name.contains('.') {
191                    self.properties.insert("__access_lvl__".to_owned(), FieldMapping{type_: "integer".to_owned().into(), index: true.into(), ..Default::default()});
192                    self.properties.insert("__access_req__".to_owned(), FieldMapping{type_: "keyword".to_owned().into(), index: true.into(), ..Default::default()});
193                    self.properties.insert("__access_grp1__".to_owned(), FieldMapping{type_: "keyword".to_owned().into(), index: true.into(), ..Default::default()});
194                    self.properties.insert("__access_grp2__".to_owned(), FieldMapping{type_: "keyword".to_owned().into(), index: true.into(), ..Default::default()});
195                }
196            } else if mapping.eq_ignore_ascii_case("classification_string") {
197                self.insert(&full_name, meta, FieldMapping{ type_: "keyword".to_owned().into(), ..Default::default() });
198            } else if mapping.eq_ignore_ascii_case("flattenedobject") {
199                if let Kind::Mapping(key_type, child_type) = kind {
200                    if key_type.kind != Kind::String {
201                        return Err(MappingError::OnlyStringKeys)
202                    }
203    
204                    let index = meta.index.or(child_type.metadata.index).unwrap_or_default();
205                    // todo!("{:?}", child_type.kind);
206                    if !index || matches!(child_type.kind, Kind::Any) {
207                        self.insert(&full_name, meta, FieldMapping{
208                            type_: Some("object".to_owned()),
209                            enabled: Some(false),
210                            ..Default::default()
211                        })
212                    } else {
213                        self.build_dynamic(&(full_name + ".*"), &child_type.kind, meta, false, index, true)?;
214                    }
215                } else {
216                    return Err(MappingError::UnsupportedType(full_name, format!("{kind:?}")))
217                }
218            } else if mapping.eq_ignore_ascii_case("date") {
219                self.insert(&full_name, meta, FieldMapping{ 
220                    type_: Some(mapping.to_owned()), 
221                    // The maximum always safe value in elasticsearch
222                    format: Some("date_optional_time||epoch_millis".to_owned()),
223                    ..Default::default()
224                });                
225            } else if mapping.eq_ignore_ascii_case("keyword") {
226                self.insert(&full_name, meta, FieldMapping{ 
227                    type_: Some(mapping.to_owned()), 
228                    // The maximum always safe value in elasticsearch
229                    ignore_above: Some(8191),
230                    ..Default::default()
231                });
232            } else if mapping.eq_ignore_ascii_case("wildcard") {
233                self.properties.insert(full_name, FieldMapping{ 
234                    type_: Some(mapping.to_owned()), 
235                    copy_to: meta.copyto.map(|item| vec![item.to_owned()]).unwrap_or_default(),
236                    ..Default::default()
237                });
238            } else {
239                self.insert(&full_name, meta, FieldMapping{ 
240                    type_: Some(mapping.to_owned()), 
241                    ..Default::default()
242                });
243            }
244            return Ok(());
245        };
246        // handle complex mappings
247        match kind {
248            Kind::Struct { children, .. } => {
249                for child in children {
250                    self.build_field(Some(child.label), &child.type_info.kind, &child.metadata,  &path, _allow_refuse_implicit)?;
251                }
252            },
253            Kind::Aliased { kind, .. } => {
254                self.build_field(None, &kind.kind, meta, &path, _allow_refuse_implicit)?;
255            },
256            Kind::Enum { .. } => {
257                self.insert(&full_name, meta, FieldMapping{ 
258                    type_: Some("keyword".to_owned()), 
259                    // The maximum always safe value in elasticsearch
260                    ignore_above: Some(8191),
261                    ..Default::default()
262                });
263            },
264            Kind::Sequence(kind) => {
265                self.build_field(None, &kind.kind, meta, &path, _allow_refuse_implicit)?;
266            },
267            Kind::Option(kind) => {
268                self.build_field(None, &kind.kind, meta, &path, _allow_refuse_implicit)?;
269            },
270            Kind::Mapping(key, value) => {
271                if key.kind != Kind::String {
272                    return Err(MappingError::OnlyStringKeys)
273                }
274                // elif isinstance(field, FlattenedObject):
275                //     if not field.index or isinstance(field.child_type, Any):
276                //         mappings[name.strip(".")] = {"type": "object", "enabled": False}
277                //     else:
278                //         dynamic.extend(build_templates(f'{name}.*', field.child_type, nested_template=True, index=field.index))
279
280                // elif isinstance(field, Mapping):
281                //     if not field.index or isinstance(field.child_type, Any):
282                //         mappings[name.strip(".")] = {"type": "object", "enabled": False}
283                //     else:
284                //         dynamic.extend(build_templates(f'{name}.*', field.child_type, index=field.index))
285                let index = meta.index.unwrap_or(false);
286                if !index || value.kind == struct_metadata::Kind::Any {
287                    let mut meta = meta.clone();
288                    meta.index = None;
289                    meta.store = None;
290                    self.insert(&full_name, &meta, FieldMapping{
291                        type_: "object".to_owned().into(),
292                        enabled: false.into(),
293                        ..Default::default()
294                    });
295                } else {
296                    self.build_dynamic(&(full_name + ".*"), &value.kind, &value.metadata, false, index, false)?;
297                }
298            },
299            Kind::Any | Kind::JSON => {
300                let index = meta.index.unwrap_or(false);
301                if index {
302                    return Err(MappingError::NoIndexedAny(full_name));
303                }
304
305                self.insert(&full_name, meta, FieldMapping{
306                    type_: Some("keyword".to_string()),
307                    index: Some(false),
308                    ignore_above: Some(8191),
309                    doc_values: Some(false),
310                    ..Default::default()
311                });
312            },
313            _ => return Err(MappingError::UnsupportedType(full_name, format!("{kind:?}")))
314        };
315        Ok(())
316    }
317
318    // // nested_template = false
319    // // index = true
320    fn build_dynamic(&mut self, name: &str, kind: &Kind<ElasticMeta>, meta: &ElasticMeta, nested_template: bool, index: bool, flatten: bool) -> Result<(), MappingError> {
321        // println!("build_dynamic -> {name} | {kind:?} | {nested_template}");
322
323        match kind {
324            Kind::JSON | Kind::String | Kind::Enum { .. } |
325            Kind::U64 | Kind::I64 | Kind::U32 | Kind::I32 | Kind::U16 | Kind::I16 | Kind::U8 | Kind::I8 |
326            Kind::F64 | Kind::F32 |
327            Kind::Bool => {
328                if nested_template {
329                    self.insert_dynamic("nested_".to_owned() + name, DynamicTemplate {
330                        match_: Some(name.to_string()),
331                        mapping: FieldMapping {
332                            type_: "nested".to_string().into(),
333                            ..Default::default()
334                        },
335                        ..Default::default()
336                    });
337                } else if let Some(mapping) = meta.mapping.or_else(|| simple_mapping(kind)) {
338                    if flatten {
339
340                        self.insert_dynamic(format!("{name}_object_tpl"), DynamicTemplate {
341                            path_match: Some(name.to_owned()),
342                            match_mapping_type: Some("object".to_owned()),
343                            mapping: FieldMapping{
344                                type_: Some("object".to_owned()),
345                                ..Default::default()
346                            },
347                            ..Default::default()
348                        });
349
350                        self.insert_dynamic(format!("{name}_wildcard_tpl"), DynamicTemplate {
351                            path_match: Some(name.to_owned()),
352                            mapping: FieldMapping{
353                                type_: mapping.to_owned().into(),
354                                index: if meta.index == Some(false) { Some(false) } else { None },
355                                store: if meta.store == Some(true) { Some(true) } else { None },
356                                copy_to: meta.copyto.map(|item| vec![item.to_owned()]).unwrap_or_default(),
357                                ..Default::default()
358                            },
359                            ..Default::default()
360                        })
361
362                    } else {
363                        self.insert_dynamic(format!("{name}_tpl"), DynamicTemplate {
364                            path_match: Some(name.to_owned()),
365                            mapping: FieldMapping{
366                                type_: mapping.to_owned().into(),
367                                index: if meta.index == Some(false) { Some(false) } else { None },
368                                store: if meta.store == Some(true) { Some(true) } else { None },
369                                copy_to: meta.copyto.map(|item| vec![item.to_owned()]).unwrap_or_default(),
370                                ..Default::default()
371                            },
372                            ..Default::default()
373                        })
374                    }
375                } else {
376                    return Err(MappingError::UnsupportedType(name.to_owned(), format!("{kind:?}")))
377                }
378                return Ok(())
379            },
380
381            // have aliased types apply as the underlying type
382            Kind::Aliased {name: _, kind: k } => {
383                return self.build_dynamic(name, &k.kind, &k.metadata, nested_template, index, flatten);
384            }
385
386            Kind::Mapping(_, child) => {
387                return self.build_dynamic(name, &child.kind, &child.metadata, true, index, flatten)
388            },
389
390            Kind::Sequence(child) => {
391                return self.build_dynamic(name, &child.kind, &child.metadata, nested_template, index, flatten)
392            },
393
394            Kind::Struct { children, .. } => {
395
396                for child in children {
397                    let sub_name = format!("{name}.{}", child.label);
398                    let index = child.metadata.index.or(meta.index).unwrap_or(index);
399                    self.build_dynamic(&sub_name, &child.type_info.kind, &child.metadata, nested_template, index, flatten)?;
400                }
401
402                return Ok(())
403            }
404
405            // elif isinstance(field, Optional):
406            Kind::Option(kind) => { 
407                return self.build_dynamic(name, &kind.kind, meta, nested_template, index, flatten);
408                // return build_templates(name, field.child_type, nested_template=nested_template)
409            }
410
411            _ => {}
412        }
413
414        // elif isinstance(field, Any) or not index:
415        if matches!(kind, Kind::Any) || !index {
416            if index {
417                return Err(MappingError::NoIndexedAny(name.to_owned()))
418            }
419
420            self.insert_dynamic(format!("{name}_tpl"), DynamicTemplate {
421                path_match: Some(name.to_owned()),
422                mapping: FieldMapping {
423                    type_: "keyword".to_owned().into(),
424                    index: false.into(),
425                    ..Default::default()
426                },
427                ..Default::default()
428            });
429
430            return Ok(())
431        }
432
433        todo!("Unknown type for elasticsearch dynamic mapping: {kind:?}");
434    }
435
436    pub fn insert_dynamic(&mut self, name: String, template: DynamicTemplate) {
437        self.dynamic_templates.push([(name, template)].into_iter().collect());
438    }
439
440    pub fn apply_defaults(&mut self) {
441        self.dynamic_templates.push([("strings_as_keywords".to_owned(), DynamicTemplate {
442            match_: None,
443            path_match: None,
444            match_mapping_type: Some("string".to_owned()),
445            mapping: FieldMapping {
446                type_: "keyword".to_owned().into(),
447                ignore_above: Some(8191),
448                ..Default::default()
449            }
450        })].into_iter().collect());
451
452        if !self.properties.contains_key("id") {
453            self.properties.insert("id".to_owned(), FieldMapping {
454                store: true.into(),
455                doc_values: Some(true),
456                type_: "keyword".to_owned().into(),
457                copy_to: vec!["__text__".to_string()],
458                ..Default::default()
459            });
460        }
461    
462        self.properties.insert("__text__".to_owned(), FieldMapping {
463            store: false.into(),
464            type_: "text".to_owned().into(),
465            ..Default::default()
466        });
467    
468    }
469
470}
471
472pub fn default_settings(index: serde_json::Value) -> serde_json::Value {
473     json!({
474        "analysis": {
475            "filter": {
476                "text_ws_dsplit": {
477                    "type": "pattern_replace",
478                    "pattern": r"(\.)",
479                    "replacement": " "
480                }
481            },
482            "analyzer": {
483                "string_ci": {
484                    "type": "custom",
485                    "tokenizer": "keyword",
486                    "filter": ["lowercase"]
487                },
488                "text_fuzzy": {
489                    "type": "pattern",
490                    "pattern": r"\s*:\s*",
491                    "lowercase": false
492                },
493                "text_whitespace": {
494                    "type": "whitespace"
495                },
496                "text_ws_dsplit": {
497                    "type": "custom",
498                    "tokenizer": "whitespace",
499                    "filters": ["text_ws_dsplit"]
500                }
501            },
502            "normalizer": {
503                "lowercase_normalizer": {
504                    "type": "custom",
505                    "char_filter": [],
506                    "filter": ["lowercase"]
507                }
508            }
509        },
510        "index": index,
511    })
512}
513
514pub fn build_mapping<T: Described<ElasticMeta>>() -> Result<Mappings, MappingError> {
515    let metadata = T::metadata();
516    if let struct_metadata::Kind::Struct { children, .. } = metadata.kind {
517        let children: Vec<_> = children.iter().map(|entry|(Some(entry.label), &entry.type_info.kind, &entry.metadata)).collect();
518        build_mapping_inner(&children, vec![], true)
519    } else {
520        Err(MappingError::OnlyStructs)
521    }
522}
523
524/// The mapping for Elasticsearch based on a model object.
525pub fn build_mapping_inner(children: &[(Option<&'static str>, &Kind<ElasticMeta>, &ElasticMeta)], prefix: Vec<&str>, allow_refuse_implicit: bool) -> Result<Mappings, MappingError> {
526    let mut mappings = Mappings::default();
527
528    // Fill in the sections
529    for (label, kind, meta) in children {
530        mappings.build_field(*label, kind, meta, &prefix, allow_refuse_implicit)?;
531    }
532
533    // The final template must match everything and disable indexing
534    // this effectively disables dynamic indexing EXCEPT for the templates
535    // we have defined
536    if mappings.dynamic_templates.is_empty() && allow_refuse_implicit {
537        // We cannot use the dynamic type matching if others are in play because they conflict with each other
538        // TODO: Find a way to make them work together.
539        mappings.insert_dynamic("refuse_all_implicit_mappings".to_owned(), DynamicTemplate {
540            match_: Some("*".to_owned()),
541            mapping: FieldMapping {
542                index: false.into(),
543                ignore_malformed: true.into(),
544                ..Default::default()
545            },
546            ..Default::default()
547        });
548    }
549
550    Ok(mappings)
551}
552
553// fn build_field_mapping(mapping: &mut Mappings) -> Result<(), MappingError> {
554//     todo!()
555// }
556
557// Simple types can be resolved by a direct mapping
558fn simple_mapping(kind: &Kind<ElasticMeta>) -> Option<&'static str> {
559    match kind {
560        Kind::Struct { .. } => None,
561        Kind::Aliased { kind, .. } => match kind.metadata.mapping {
562            Some(mapping) => Some(mapping),
563            None => simple_mapping(&kind.kind),
564        },
565        Kind::Enum { .. } => Some("keyword"),
566        Kind::Sequence(kind) => match kind.metadata.mapping {
567            Some(mapping) => Some(mapping),
568            None => simple_mapping(&kind.kind),
569        },
570        Kind::Option(kind) => match kind.metadata.mapping {
571            Some(mapping) => Some(mapping),
572            None => simple_mapping(&kind.kind),
573        },
574        Kind::Mapping(..) => None,
575        Kind::DateTime => Some("date"),
576        Kind::String => Some("keyword"),
577        Kind::U128 | Kind::I128 => None,
578        Kind::U64 => Some("unsigned_long"),
579        Kind::I64 | Kind::U32 => Some("long"),
580        Kind::I32 | Kind::U16 | Kind::I16 | Kind::U8 | Kind::I8 => Some("integer"),
581        Kind::F64 => Some("double"),
582        Kind::F32 => Some("float"),
583        Kind::Bool => Some("boolean"),
584        Kind::Any => Some("keyword"),
585        Kind::JSON => None,
586        _ => todo!("{kind:?}"),
587    }
588    // Text: 'text',
589    // Classification: 'keyword',
590    // ClassificationString: 'keyword',
591    // UUID: 'keyword',
592    // IP: 'ip',
593    // Domain: 'keyword',
594    // Email: 'keyword',
595    // URI: 'keyword',
596    // UNCPath: 'keyword',
597    // URIPath: 'keyword',
598    // MAC: 'keyword',
599    // PhoneNumber: 'keyword',
600    // SSDeepHash: 'text',
601    // SHA1: 'keyword',
602    // SHA256: 'keyword',
603    // MD5: 'keyword',
604    // Platform: 'keyword',
605    // Processor: 'keyword',
606    // FlattenedObject: 'nested',
607    // UpperKeyword: 'keyword',
608    // Json: 'keyword',
609    // ValidatedKeyword: 'keyword'
610}
611// // __analyzer_mapping = {
612// //     SSDeepHash: 'text_fuzzy',
613// // }
614// // __normalizer_mapping = {
615// //     SHA1: 'lowercase_normalizer',
616// //     SHA256: 'lowercase_normalizer',
617// //     MD5: 'lowercase_normalizer',
618// // }
619// // # TODO: We might want to use custom analyzers for Classification and Enum and not create special backmapping cases
620// // back_mapping = {v: k for k, v in __type_mapping.items() if k not in [Enum, Classification, UUID, IP, Domain, URI,
621// //                                                                      URIPath, MAC, PhoneNumber, SSDeepHash, Email,
622// //                                                                      SHA1, SHA256, MD5, Platform, Processor,
623// //                                                                      ClassificationString, Any, UpperKeyword, Json,
624// //                                                                      ValidatedKeyword, UNCPath]}
625// // back_mapping.update({x: Keyword for x in set(__analyzer_mapping.values())})
626
627#[derive(Debug)]
628pub enum MappingError {
629    OnlyStructs,
630    UnsupportedType(String, String),
631    NoIndexedAny(String),
632    OnlyStringKeys
633}
634
635impl std::fmt::Display for MappingError {
636    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
637        match self {
638            MappingError::OnlyStructs => f.write_str("Mappings can only be created for structs"),
639            MappingError::UnsupportedType(name, kind) => f.write_fmt(format_args!("The field {name} is assigned an unsupported type {kind}")),
640            MappingError::NoIndexedAny(name) => f.write_fmt(format_args!("The field {name} can't be Any type while being indexed.")),
641            MappingError::OnlyStringKeys => f.write_str("Mapping keys must be strings"),
642        }
643    }
644}
645
646impl std::error::Error for MappingError {
647
648}