Skip to main content

stix_model/
store.rs

1//! `ObjectStore`: an id-indexed collection for resolving references.
2
3use std::collections::HashMap;
4
5use crate::bundle::Bundle;
6use crate::object::StixObject;
7use crate::view::ObjectView;
8
9/// An id → object index built from a bundle or a list of objects. Used by the
10/// matcher to resolve `object_refs` and reference properties (e.g. `src_ref`).
11#[derive(Debug, Clone, Default)]
12pub struct ObjectStore {
13    by_id: HashMap<String, StixObject>,
14}
15
16impl ObjectStore {
17    /// Build a store from a slice of objects. Objects without an `id` are skipped.
18    pub fn from_objects(objects: &[StixObject]) -> Self {
19        let mut by_id = HashMap::new();
20        for obj in objects {
21            if let Some(id) = obj.id() {
22                by_id.insert(id.to_string(), obj.clone());
23            }
24        }
25        ObjectStore { by_id }
26    }
27
28    /// Build a store from a bundle's objects.
29    pub fn from_bundle(bundle: &Bundle) -> Self {
30        ObjectStore::from_objects(&bundle.objects)
31    }
32
33    /// Resolve an object by id.
34    pub fn get(&self, id: &str) -> Option<&StixObject> {
35        self.by_id.get(id)
36    }
37
38    /// The number of objects in the store.
39    pub fn len(&self) -> usize {
40        self.by_id.len()
41    }
42
43    /// Returns true if the store holds no objects.
44    pub fn is_empty(&self) -> bool {
45        self.by_id.is_empty()
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52    use crate::bundle::Bundle;
53    use crate::view::ObjectView;
54
55    fn store() -> ObjectStore {
56        let b = Bundle::from_json_str(
57            r#"{
58                "type": "bundle",
59                "id": "bundle--1",
60                "objects": [
61                    {"type": "ipv4-addr", "id": "ipv4-addr--1", "value": "1.2.3.4"},
62                    {"type": "domain-name", "id": "domain-name--1", "value": "evil.example"}
63                ]
64            }"#,
65        )
66        .unwrap();
67        ObjectStore::from_bundle(&b)
68    }
69
70    #[test]
71    fn resolves_by_id() {
72        let s = store();
73        let o = s.get("ipv4-addr--1").expect("should be present");
74        assert_eq!(o.property("value").unwrap().as_str(), Some("1.2.3.4"));
75    }
76
77    #[test]
78    fn missing_id_returns_none() {
79        let s = store();
80        assert!(s.get("nope--1").is_none());
81    }
82
83    #[test]
84    fn len_counts_objects() {
85        let s = store();
86        assert_eq!(s.len(), 2);
87        assert!(!s.is_empty());
88    }
89}