use serde_json::Value;
use crate::analysis::findings::{Finding, Severity};
use crate::languages::runner::ToolOutputError;
use crate::languages::spec::ToolSpec;
use super::{expect_array, json_payload, path_or_root};
pub(super) fn parse_json(
spec: &ToolSpec,
output: &str,
root_name: &str,
) -> Result<Vec<Finding>, ToolOutputError> {
let Some(payload) = json_payload(spec, output)? else {
return Ok(Vec::new());
};
let entries = expect_array(spec, &payload)?;
let mut findings = Vec::new();
for entry in entries {
let obj = entry.as_object().ok_or_else(|| {
ToolOutputError(format!("{}: expected objects in the array", spec.name))
})?;
if let Some(location) = obj.get("location") {
let location = location.as_object();
let row = location
.and_then(|l| l.get("row"))
.and_then(Value::as_u64)
.map(|n| n as u32)
.unwrap_or(1);
let column = location
.and_then(|l| l.get("column"))
.and_then(Value::as_u64)
.map(|n| n as u32);
let file_path = path_or_root(obj.get("filename"), root_name);
let message = obj
.get("message")
.and_then(Value::as_str)
.unwrap_or("")
.to_owned();
let kind = obj
.get("code")
.and_then(Value::as_str)
.map(str::to_owned)
.unwrap_or_else(|| spec.name.to_owned());
let suggestion = obj
.get("fix")
.and_then(|f| f.get("message"))
.and_then(Value::as_str)
.map(str::to_owned);
findings.push(Finding::deterministic(
kind,
Severity::Error,
file_path,
row,
column,
message,
suggestion,
));
continue;
}
let file_path = path_or_root(obj.get("filePath"), root_name);
for message in obj
.get("messages")
.and_then(Value::as_array)
.into_iter()
.flatten()
{
let obj = message.as_object();
let line = obj
.and_then(|m| m.get("line"))
.and_then(Value::as_u64)
.map(|n| n as u32)
.unwrap_or(1);
let column = obj
.and_then(|m| m.get("column"))
.and_then(Value::as_u64)
.map(|n| n as u32);
let message_text = obj
.and_then(|m| m.get("message"))
.and_then(Value::as_str)
.unwrap_or("")
.to_owned();
let kind = obj
.and_then(|m| m.get("ruleId"))
.and_then(Value::as_str)
.map(str::to_owned)
.unwrap_or_else(|| spec.name.to_owned());
findings.push(Finding::deterministic(
kind,
match obj.and_then(|m| m.get("severity")).and_then(Value::as_u64) {
Some(1) => Severity::Warning,
_ => Severity::Error,
},
file_path.clone(),
line,
column,
message_text,
None,
));
}
}
Ok(findings)
}
pub(super) fn parse_ktlint(
spec: &ToolSpec,
output: &str,
root_name: &str,
) -> Result<Vec<Finding>, ToolOutputError> {
let Some(payload) = json_payload(spec, output)? else {
return Ok(Vec::new());
};
let entries = expect_array(spec, &payload)?;
let mut findings = Vec::new();
for entry in entries {
let file_path = path_or_root(entry.get("file"), root_name);
for error in entry
.get("errors")
.and_then(Value::as_array)
.into_iter()
.flatten()
{
findings.push(Finding::deterministic(
error
.get("rule")
.and_then(Value::as_str)
.map(str::to_owned)
.unwrap_or_else(|| spec.name.to_owned()),
Severity::Error,
file_path.clone(),
error
.get("line")
.and_then(Value::as_u64)
.map(|n| n as u32)
.unwrap_or(1),
error
.get("column")
.and_then(Value::as_u64)
.map(|n| n as u32),
error
.get("message")
.and_then(Value::as_str)
.unwrap_or("")
.to_owned(),
None,
));
}
}
Ok(findings)
}