1use serde::{Deserialize, Serialize};
4use serde_json::{Value, json};
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub struct ResourceTraceEnvelope {
13 pub version: u32,
15 pub resource: String,
17 pub operation: String,
19 pub trace_kind: String,
21 pub input_summary: Value,
23 pub output_summary: Value,
25 #[serde(skip_serializing_if = "Option::is_none")]
27 pub reason: Option<String>,
28 #[serde(default, skip_serializing_if = "Value::is_null")]
30 pub metadata: Value,
31}
32
33impl ResourceTraceEnvelope {
34 pub const VERSION: u32 = 1;
36
37 #[must_use]
39 pub fn new(
40 resource: impl Into<String>,
41 operation: impl Into<String>,
42 trace_kind: impl Into<String>,
43 ) -> Self {
44 Self {
45 version: Self::VERSION,
46 resource: resource.into(),
47 operation: operation.into(),
48 trace_kind: trace_kind.into(),
49 input_summary: Value::Null,
50 output_summary: Value::Null,
51 reason: None,
52 metadata: Value::Null,
53 }
54 }
55
56 #[must_use]
58 pub fn with_input_summary(mut self, input_summary: Value) -> Self {
59 self.input_summary = input_summary;
60 self
61 }
62
63 #[must_use]
65 pub fn with_output_summary(mut self, output_summary: Value) -> Self {
66 self.output_summary = output_summary;
67 self
68 }
69
70 #[must_use]
72 pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
73 self.reason = Some(reason.into());
74 self
75 }
76
77 #[must_use]
79 pub fn with_metadata(mut self, metadata: Value) -> Self {
80 self.metadata = metadata;
81 self
82 }
83
84 #[must_use]
86 pub fn to_value(&self) -> Value {
87 json!(self)
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn trace_envelope_round_trips_as_json() {
97 let trace = ResourceTraceEnvelope::new("graph", "expand", "graph_expansion")
98 .with_input_summary(json!({"entity": "host-1"}))
99 .with_output_summary(json!({"distinct_neighbours": 4}))
100 .with_reason("threshold_exceeded");
101
102 let value = trace.to_value();
103 let decoded: ResourceTraceEnvelope = serde_json::from_value(value).unwrap();
104
105 assert_eq!(decoded.version, ResourceTraceEnvelope::VERSION);
106 assert_eq!(decoded.resource, "graph");
107 assert_eq!(decoded.reason.as_deref(), Some("threshold_exceeded"));
108 }
109}