1use serde_json::{Value, json};
8
9#[derive(Debug, Clone)]
11pub struct Finding {
12 pub rule_id: String,
13 pub level: Level,
14 pub message: String,
15 pub file: String,
16 pub line: usize,
17 pub cwe: Option<String>,
18 pub flow: Vec<FlowStep>,
20}
21
22#[derive(Debug, Clone, Copy)]
24pub enum Level {
25 Error,
26 Warning,
27 Note,
28}
29
30impl Level {
31 pub fn as_str(self) -> &'static str {
32 match self {
33 Self::Error => "error",
34 Self::Warning => "warning",
35 Self::Note => "note",
36 }
37 }
38}
39
40#[derive(Debug, Clone)]
42pub struct FlowStep {
43 pub file: String,
44 pub line: usize,
45 pub message: String,
46}
47
48pub fn generate_sarif(tool_name: &str, tool_version: &str, findings: &[Finding]) -> Value {
50 let rules: Vec<Value> = findings
51 .iter()
52 .map(|f| &f.rule_id)
53 .collect::<std::collections::HashSet<_>>()
54 .into_iter()
55 .map(|id| {
56 let cwe = findings
57 .iter()
58 .find(|f| f.rule_id == *id)
59 .and_then(|f| f.cwe.as_ref());
60
61 let mut rule = json!({
62 "id": id,
63 "shortDescription": {
64 "text": id
65 }
66 });
67
68 if let Some(cwe_id) = cwe {
69 rule["properties"] = json!({
70 "tags": [cwe_id]
71 });
72 }
73
74 rule
75 })
76 .collect();
77
78 let results: Vec<Value> = findings
79 .iter()
80 .map(|f| {
81 let mut result = json!({
82 "ruleId": f.rule_id,
83 "level": f.level.as_str(),
84 "message": {
85 "text": f.message
86 },
87 "locations": [{
88 "physicalLocation": {
89 "artifactLocation": {
90 "uri": f.file
91 },
92 "region": {
93 "startLine": f.line
94 }
95 }
96 }]
97 });
98
99 if !f.flow.is_empty() {
101 let thread_flows: Vec<Value> = f
102 .flow
103 .iter()
104 .map(|step| {
105 json!({
106 "location": {
107 "physicalLocation": {
108 "artifactLocation": {
109 "uri": step.file
110 },
111 "region": {
112 "startLine": step.line
113 }
114 },
115 "message": {
116 "text": step.message
117 }
118 }
119 })
120 })
121 .collect();
122
123 result["codeFlows"] = json!([{
124 "threadFlows": [{
125 "locations": thread_flows
126 }]
127 }]);
128 }
129
130 result
131 })
132 .collect();
133
134 json!({
135 "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json",
136 "version": "2.1.0",
137 "runs": [{
138 "tool": {
139 "driver": {
140 "name": tool_name,
141 "version": tool_version,
142 "informationUri": "https://github.com/santh-security/jsdet",
143 "rules": rules
144 }
145 },
146 "results": results
147 }]
148 })
149}