Skip to main content

gossan_graph/
edge.rs

1//! Typed graph edge.
2
3use serde::{Deserialize, Serialize};
4
5use crate::schema::EdgeType;
6
7/// An edge (relationship) in the attack-surface graph.
8#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub struct Edge {
10    /// Source node id.
11    pub source_id: String,
12    /// Target node id.
13    pub target_id: String,
14    /// Semantic relationship type.
15    pub kind: EdgeType,
16    /// Optional JSON payload with relationship-specific metadata.
17    #[serde(default, skip_serializing_if = "Option::is_none")]
18    pub payload: Option<serde_json::Value>,
19    /// Unix timestamp (ms) when the edge was first observed.
20    #[serde(default)]
21    pub first_seen_ms: u64,
22    /// Unix timestamp (ms) when the edge was last observed.
23    #[serde(default)]
24    pub last_seen_ms: u64,
25}
26
27impl Edge {
28    /// Create a new edge with the current time as `first_seen`.
29    #[must_use]
30    pub fn new(
31        source_id: impl Into<String>,
32        target_id: impl Into<String>,
33        kind: EdgeType,
34    ) -> Self {
35        let now = std::time::SystemTime::now()
36            .duration_since(std::time::UNIX_EPOCH)
37            .unwrap_or_default()
38            .as_millis() as u64;
39        Self {
40            source_id: source_id.into(),
41            target_id: target_id.into(),
42            kind,
43            payload: None,
44            first_seen_ms: now,
45            last_seen_ms: now,
46        }
47    }
48
49    /// Attach a JSON payload.
50    #[must_use]
51    pub fn with_payload(mut self, payload: impl Serialize) -> Self {
52        self.payload = serde_json::to_value(payload).ok();
53        self
54    }
55}