Skip to main content

stix_ffi/
handles.rs

1//! Opaque handles (`Pattern`, `Bundle`) and the plain `MatchOutcome` value.
2
3/// Opaque handle around a parsed pattern AST.
4#[derive(Debug, Clone)]
5pub struct Pattern {
6    inner: stix::pattern::Pattern,
7}
8
9impl Pattern {
10    pub(crate) fn new(inner: stix::pattern::Pattern) -> Self {
11        Pattern { inner }
12    }
13
14    pub(crate) fn inner(&self) -> &stix::pattern::Pattern {
15        &self.inner
16    }
17
18    /// The pattern's AST serialized as compact JSON.
19    pub fn to_json(&self) -> String {
20        // Serialization of the AST is infallible in practice; fall back to "null".
21        serde_json::to_string(&self.inner).unwrap_or_else(|_| "null".to_string())
22    }
23}
24
25/// Opaque handle around an imported bundle.
26#[derive(Debug, Clone)]
27pub struct Bundle {
28    inner: stix::model::Bundle,
29}
30
31impl Bundle {
32    pub(crate) fn new(inner: stix::model::Bundle) -> Self {
33        Bundle { inner }
34    }
35
36    pub(crate) fn inner(&self) -> &stix::model::Bundle {
37        &self.inner
38    }
39
40    /// Number of objects in the bundle.
41    pub fn object_count(&self) -> usize {
42        self.inner.objects.len()
43    }
44
45    /// The object at `index` serialized as JSON, or `None` if out of range.
46    pub fn object_json(&self, index: usize) -> Option<String> {
47        let obj = self.inner.objects.get(index)?;
48        Some(serde_json::to_string(obj).unwrap_or_else(|_| "null".to_string()))
49    }
50}
51
52/// The outcome of a match: whether it matched and which observation indices bound.
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct MatchOutcome {
55    /// Whether the pattern matched.
56    pub matched: bool,
57    /// The indices of the observations that participated in the match.
58    pub observations: Vec<u64>,
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn pattern_to_json_round_trips() {
67        let inner = stix::parse("[ipv4-addr:value = '1.2.3.4']").unwrap();
68        let handle = Pattern::new(inner.clone());
69        let json = handle.to_json();
70        let back: stix::pattern::Pattern = serde_json::from_str(&json).unwrap();
71        assert_eq!(back, inner);
72    }
73
74    #[test]
75    fn bundle_object_access() {
76        let raw = r#"{"type":"bundle","id":"bundle--1","objects":[
77            {"type":"ipv4-addr","id":"ipv4-addr--1","value":"1.2.3.4"}
78        ]}"#;
79        let inner = stix::model::Bundle::from_json_str(raw).unwrap();
80        let handle = Bundle::new(inner);
81        assert_eq!(handle.object_count(), 1);
82        let obj_json = handle.object_json(0).unwrap();
83        assert!(obj_json.contains("ipv4-addr--1"));
84        assert!(handle.object_json(5).is_none());
85    }
86
87    #[test]
88    fn match_outcome_fields() {
89        let o = MatchOutcome {
90            matched: true,
91            observations: vec![0, 2],
92        };
93        assert!(o.matched);
94        assert_eq!(o.observations, vec![0, 2]);
95    }
96}