bugcrowd_vrt/
cwe_mapping.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7pub struct CweId(pub String);
8
9impl CweId {
10 pub fn new(id: impl Into<String>) -> Self {
20 Self(id.into())
21 }
22
23 pub fn as_str(&self) -> &str {
25 &self.0
26 }
27
28 pub fn number(&self) -> Option<u32> {
38 self.0.strip_prefix("CWE-")?.parse().ok()
39 }
40
41 pub fn is_valid(&self) -> bool {
43 self.0.starts_with("CWE-") && self.number().is_some()
44 }
45}
46
47impl std::fmt::Display for CweId {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 write!(f, "{}", self.0)
50 }
51}
52
53impl From<String> for CweId {
54 fn from(s: String) -> Self {
55 Self(s)
56 }
57}
58
59impl From<&str> for CweId {
60 fn from(s: &str) -> Self {
61 Self(s.to_string())
62 }
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67pub struct MappingMetadata {
68 pub default: Option<String>,
70}
71
72#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
77pub struct CweMappingNode {
78 pub id: String,
80
81 #[serde(default, skip_serializing_if = "Option::is_none")]
84 pub cwe: Option<Vec<CweId>>,
85
86 #[serde(default, skip_serializing_if = "Vec::is_empty")]
88 pub children: Vec<CweMappingNode>,
89}
90
91impl CweMappingNode {
92 pub fn has_cwe_mapping(&self) -> bool {
94 self.cwe.as_ref().map_or(false, |cwes| !cwes.is_empty())
95 }
96
97 pub fn has_children(&self) -> bool {
99 !self.children.is_empty()
100 }
101
102 pub fn find_by_id(&self, vrt_id: &str) -> Option<&CweMappingNode> {
104 if self.id == vrt_id {
105 return Some(self);
106 }
107
108 for child in &self.children {
109 if let Some(found) = child.find_by_id(vrt_id) {
110 return Some(found);
111 }
112 }
113
114 None
115 }
116
117 pub fn cwe_ids(&self) -> Vec<&CweId> {
119 self.cwe
120 .as_ref()
121 .map(|cwes| cwes.iter().collect())
122 .unwrap_or_default()
123 }
124
125 pub fn all_cwe_ids(&self) -> Vec<&CweId> {
127 let mut ids = self.cwe_ids();
128
129 for child in &self.children {
130 ids.extend(child.all_cwe_ids());
131 }
132
133 ids
134 }
135
136 pub fn leaf_nodes(&self) -> Vec<&CweMappingNode> {
138 let mut leaves = Vec::new();
139
140 if self.has_cwe_mapping() && !self.has_children() {
141 leaves.push(self);
142 }
143
144 for child in &self.children {
145 leaves.extend(child.leaf_nodes());
146 }
147
148 leaves
149 }
150}
151
152#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
156pub struct CweMapping {
157 pub metadata: MappingMetadata,
159
160 pub content: Vec<CweMappingNode>,
162}
163
164impl CweMapping {
165 pub fn find_by_vrt_id(&self, vrt_id: &str) -> Option<&CweMappingNode> {
167 for node in &self.content {
168 if let Some(found) = node.find_by_id(vrt_id) {
169 return Some(found);
170 }
171 }
172 None
173 }
174
175 pub fn lookup_cwe(&self, vrt_id: &str) -> Option<Vec<&CweId>> {
191 self.find_by_vrt_id(vrt_id)
192 .and_then(|node| node.cwe.as_ref())
193 .map(|cwes| cwes.iter().collect())
194 }
195
196 pub fn all_cwe_ids(&self) -> Vec<&CweId> {
198 let mut ids = Vec::new();
199 for node in &self.content {
200 ids.extend(node.all_cwe_ids());
201 }
202
203 let mut seen = std::collections::HashSet::new();
205 ids.into_iter()
206 .filter(|id| seen.insert(id.as_str()))
207 .collect()
208 }
209
210 pub fn statistics(&self) -> MappingStatistics {
212 let mut stats = MappingStatistics::default();
213
214 for node in &self.content {
215 collect_stats(node, &mut stats);
216 }
217
218 stats
219 }
220}
221
222#[derive(Debug, Clone, Default, PartialEq, Eq)]
224pub struct MappingStatistics {
225 pub total_nodes: usize,
227 pub nodes_with_mappings: usize,
229 pub nodes_without_mappings: usize,
231 pub unique_cwe_ids: usize,
233}
234
235fn collect_stats(node: &CweMappingNode, stats: &mut MappingStatistics) {
236 stats.total_nodes += 1;
237
238 if node.has_cwe_mapping() {
239 stats.nodes_with_mappings += 1;
240 } else {
241 stats.nodes_without_mappings += 1;
242 }
243
244 for child in &node.children {
245 collect_stats(child, stats);
246 }
247}