Skip to main content

clawdstrike_ocsf/objects/
metadata.rs

1//! OCSF Metadata and Product objects.
2//!
3//! Every OCSF event **must** contain a `metadata` object with `version` and `product`.
4
5use serde::{Deserialize, Serialize};
6
7use crate::OCSF_VERSION;
8
9/// OCSF Metadata object — required on every event.
10#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(deny_unknown_fields)]
12pub struct Metadata {
13    /// OCSF schema version (e.g., "1.4.0").
14    pub version: String,
15    /// Product that generated the event.
16    pub product: Product,
17    /// Original event UID from the source system.
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub original_uid: Option<String>,
20}
21
22impl Metadata {
23    /// Create metadata for a ClawdStrike event.
24    #[must_use]
25    pub fn clawdstrike(product_version: &str) -> Self {
26        Self {
27            version: OCSF_VERSION.to_string(),
28            product: Product::clawdstrike(product_version),
29            original_uid: None,
30        }
31    }
32
33    /// Create metadata with an original UID.
34    #[must_use]
35    pub fn with_original_uid(mut self, uid: impl Into<String>) -> Self {
36        self.original_uid = Some(uid.into());
37        self
38    }
39}
40
41/// OCSF Product object identifying the source product.
42#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
43#[serde(deny_unknown_fields)]
44pub struct Product {
45    /// Product display name.
46    pub name: String,
47    /// Product unique identifier.
48    pub uid: String,
49    /// Vendor / company name.
50    pub vendor_name: String,
51    /// Product version.
52    pub version: String,
53}
54
55impl Product {
56    /// Create the canonical ClawdStrike product descriptor.
57    #[must_use]
58    pub fn clawdstrike(version: &str) -> Self {
59        Self {
60            name: "ClawdStrike".to_string(),
61            uid: "clawdstrike".to_string(),
62            vendor_name: "Backbay Labs".to_string(),
63            version: version.to_string(),
64        }
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn metadata_clawdstrike() {
74        let m = Metadata::clawdstrike("0.1.3");
75        assert_eq!(m.version, "1.4.0");
76        assert_eq!(m.product.name, "ClawdStrike");
77        assert_eq!(m.product.uid, "clawdstrike");
78        assert_eq!(m.product.vendor_name, "Backbay Labs");
79        assert_eq!(m.product.version, "0.1.3");
80        assert!(m.original_uid.is_none());
81    }
82
83    #[test]
84    fn metadata_with_original_uid() {
85        let m = Metadata::clawdstrike("0.1.3").with_original_uid("evt-123");
86        assert_eq!(m.original_uid.as_deref(), Some("evt-123"));
87    }
88
89    #[test]
90    fn metadata_roundtrip() {
91        let m = Metadata::clawdstrike("0.1.3").with_original_uid("uid-1");
92        let json = serde_json::to_string(&m).unwrap();
93        let m2: Metadata = serde_json::from_str(&json).unwrap();
94        assert_eq!(m, m2);
95    }
96}