Skip to main content

kyma_graph/
types.rs

1//! Wire types for the graph layer. These mirror the JSON contract the web
2//! Context Graph UI consumes, so field names are load-bearing.
3
4use serde::{Deserialize, Serialize};
5use std::collections::BTreeMap;
6
7/// Free-form node/edge property bag. `BTreeMap` keeps key order stable for
8/// deterministic JSON in tests.
9pub type Props = BTreeMap<String, serde_json::Value>;
10
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub struct NodeMetadata {
13    pub created_at: String,
14    pub updated_at: String,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub source_type: Option<String>,
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub source_id: Option<String>,
19    pub realm: String,
20}
21
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23pub struct GraphNode {
24    pub id: String,
25    pub labels: Vec<String>,
26    #[serde(default)]
27    pub properties: Props,
28    pub metadata: NodeMetadata,
29}
30
31#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
32pub struct GraphRelationship {
33    pub id: String,
34    pub source_id: String,
35    pub target_id: String,
36    pub relationship_type: String,
37    #[serde(default)]
38    pub properties: Props,
39}
40
41#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
42pub struct GraphStats {
43    pub total_nodes: usize,
44    pub total_relationships: usize,
45    pub label_counts: BTreeMap<String, usize>,
46    pub relationship_type_counts: BTreeMap<String, usize>,
47}
48
49#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
50pub struct GraphPayload {
51    pub stats: GraphStats,
52    pub nodes: Vec<GraphNode>,
53    pub edges: Vec<GraphRelationship>,
54}
55
56#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
57pub struct EdgeExpansion {
58    pub edges: Vec<GraphRelationship>,
59    pub new_node_ids: Vec<String>,
60}
61
62#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
63pub struct SearchHits {
64    pub hits: Vec<GraphNode>,
65    pub total: usize,
66    pub limit: usize,
67    pub offset: usize,
68}
69
70#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
71pub struct GraphSchema {
72    pub node_kinds: Vec<String>,
73    pub edge_types: Vec<String>,
74    pub property_keys: BTreeMap<String, Vec<String>>,
75}
76
77/// One entry in the `GET /v1/graph` listing.
78#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
79pub struct GraphRef {
80    pub name: String,
81    /// `"schema"` (synthetic) or `"stored"` (registered — later).
82    pub kind: String,
83    pub description: String,
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
87#[serde(rename_all = "lowercase")]
88pub enum Direction {
89    Forward,
90    Backward,
91    Both,
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn graph_node_serializes_to_wire_shape() {
100        let node = GraphNode {
101            id: "default::otel_logs".into(),
102            labels: vec!["Table".into()],
103            properties: [("database".to_string(), serde_json::json!("default"))]
104                .into_iter()
105                .collect(),
106            metadata: NodeMetadata {
107                created_at: "2026-05-25T00:00:00Z".into(),
108                updated_at: "2026-05-25T00:00:00Z".into(),
109                source_type: Some("schema".into()),
110                source_id: None,
111                realm: "default".into(),
112            },
113        };
114        let v = serde_json::to_value(&node).unwrap();
115        assert_eq!(v["id"], "default::otel_logs");
116        assert_eq!(v["labels"][0], "Table");
117        assert_eq!(v["properties"]["database"], "default");
118        assert_eq!(v["metadata"]["realm"], "default");
119        assert_eq!(v["metadata"]["source_type"], "schema");
120        assert!(v["metadata"].get("source_id").is_none());
121    }
122
123    #[test]
124    fn direction_serializes_lowercase() {
125        assert_eq!(serde_json::to_value(Direction::Forward).unwrap(), "forward");
126        assert_eq!(serde_json::to_value(Direction::Both).unwrap(), "both");
127    }
128}