1use serde::{Deserialize, Serialize};
4
5pub const SCHEMA_VERSION: u32 = 1;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[non_exhaustive]
11pub enum NodeType {
12 Domain,
13 Subdomain,
14 Ip,
15 Port,
16 Service,
17 Tech,
18 Endpoint,
19 Secret,
20 Cloud,
21 Finding,
22}
23
24impl std::fmt::Display for NodeType {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 match self {
27 NodeType::Domain => write!(f, "domain"),
28 NodeType::Subdomain => write!(f, "subdomain"),
29 NodeType::Ip => write!(f, "ip"),
30 NodeType::Port => write!(f, "port"),
31 NodeType::Service => write!(f, "service"),
32 NodeType::Tech => write!(f, "tech"),
33 NodeType::Endpoint => write!(f, "endpoint"),
34 NodeType::Secret => write!(f, "secret"),
35 NodeType::Cloud => write!(f, "cloud"),
36 NodeType::Finding => write!(f, "finding"),
37 }
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
43#[non_exhaustive]
44pub enum EdgeType {
45 ResolvesTo,
46 Hosts,
47 Runs,
48 Exposes,
49 Leaks,
50 Misconfigured,
51 HasFinding,
52 HasService,
53}
54
55impl std::fmt::Display for EdgeType {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 match self {
58 EdgeType::ResolvesTo => write!(f, "RESOLVES_TO"),
59 EdgeType::Hosts => write!(f, "HOSTS"),
60 EdgeType::Runs => write!(f, "RUNS"),
61 EdgeType::Exposes => write!(f, "EXPOSES"),
62 EdgeType::Leaks => write!(f, "LEAKS"),
63 EdgeType::Misconfigured => write!(f, "MISCONFIGURED"),
64 EdgeType::HasFinding => write!(f, "HAS_FINDING"),
65 EdgeType::HasService => write!(f, "HAS_SERVICE"),
66 }
67 }
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
72pub struct GraphSchema {
73 pub version: u32,
74}
75
76impl Default for GraphSchema {
77 fn default() -> Self {
78 Self {
79 version: SCHEMA_VERSION,
80 }
81 }
82}
83
84impl GraphSchema {
85 #[must_use]
87 pub fn current() -> Self {
88 Self::default()
89 }
90
91 pub fn validate(&self) -> Result<(), SchemaError> {
98 if self.version > SCHEMA_VERSION {
99 return Err(SchemaError::UnsupportedVersion {
100 found: self.version,
101 max_supported: SCHEMA_VERSION,
102 });
103 }
104 Ok(())
105 }
106}
107
108#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
110pub enum SchemaError {
111 #[error("unsupported schema version {found}, max supported is {max_supported}")]
112 UnsupportedVersion { found: u32, max_supported: u32 },
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn current_schema_validates() {
121 assert!(GraphSchema::current().validate().is_ok());
122 }
123
124 #[test]
125 fn future_schema_fails() {
126 let future = GraphSchema {
127 version: SCHEMA_VERSION + 1,
128 };
129 assert!(future.validate().is_err());
130 }
131
132 #[test]
133 fn node_type_roundtrip() {
134 let types = vec![
135 NodeType::Domain,
136 NodeType::Subdomain,
137 NodeType::Ip,
138 NodeType::Port,
139 NodeType::Service,
140 NodeType::Tech,
141 NodeType::Endpoint,
142 NodeType::Secret,
143 NodeType::Cloud,
144 NodeType::Finding,
145 ];
146 for t in types {
147 let s = serde_json::to_string(&t).unwrap();
148 let back: NodeType = serde_json::from_str(&s).unwrap();
149 assert_eq!(t, back);
150 }
151 }
152
153 #[test]
154 fn edge_type_roundtrip() {
155 let types = vec![
156 EdgeType::ResolvesTo,
157 EdgeType::Hosts,
158 EdgeType::Runs,
159 EdgeType::Exposes,
160 EdgeType::Leaks,
161 EdgeType::Misconfigured,
162 EdgeType::HasFinding,
163 EdgeType::HasService,
164 ];
165 for t in types {
166 let s = serde_json::to_string(&t).unwrap();
167 let back: EdgeType = serde_json::from_str(&s).unwrap();
168 assert_eq!(t, back);
169 }
170 }
171}