use std::{
fmt::{Display, Formatter},
path::PathBuf,
};
use serde::{Deserialize, Serialize};
use crate::GHASError;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Sarif {
#[serde(rename = "$schema")]
pub schema: String,
pub version: String,
pub runs: Vec<SarifRun>,
}
impl TryFrom<PathBuf> for Sarif {
type Error = GHASError;
fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
let file = std::fs::File::open(value)?;
let reader = std::io::BufReader::new(file);
let sarif: Sarif = serde_json::from_reader(reader)?;
Ok(sarif)
}
}
impl TryFrom<String> for Sarif {
type Error = GHASError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Sarif::try_from(PathBuf::from(value))
}
}
impl Sarif {
pub fn new() -> Self {
Sarif {
schema: String::from(
"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
),
version: String::from("2.1.0"),
runs: vec![],
}
}
pub fn get_results(&self) -> Vec<SarifResult> {
let mut results = vec![];
for run in &self.runs {
results.extend(run.results.clone());
}
results
}
pub fn write(&self, path: PathBuf) -> Result<(), GHASError> {
let file = std::fs::File::create(path)?;
let writer = std::io::BufWriter::new(file);
serde_json::to_writer_pretty(writer, self)?;
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SarifRun {
pub tool: SarifTool,
pub results: Vec<SarifResult>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SarifResult {
#[serde(rename = "ruleId")]
pub rule_id: String,
#[serde(rename = "ruleIndex")]
pub rule_index: i32,
pub rule: SarifRule,
#[serde(default)]
pub level: String,
pub message: SarifMessage,
pub locations: Vec<SarifLocation>,
}
impl Display for SarifResult {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: {}", self.rule_id, self.message.text)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SarifRule {
pub id: String,
pub index: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SarifLocation {
#[serde(rename = "physicalLocation")]
pub physical_location: SarifPhysicalLocation,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SarifPhysicalLocation {
#[serde(rename = "artifactLocation")]
pub artifact_location: SarifArtifactLocation,
pub region: SarifRegion,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SarifArtifactLocation {
pub uri: String,
#[serde(rename = "uriBaseId")]
pub uri_base_id: String,
#[serde(default)]
pub id: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SarifRegion {
#[serde(rename = "startLine")]
pub start_line: i32,
#[serde(default, rename = "startColumn")]
pub start_column: i32,
#[serde(rename = "endLine")]
pub end_line: Option<i32>,
#[serde(rename = "endColumn")]
pub end_column: Option<i32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SarifTool {
pub driver: SarifToolDriver,
}
impl Display for SarifTool {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if let Some(version) = &self.driver.version {
write!(f, "{} v{}", self.driver.name, version)
} else {
write!(f, "{}", self.driver.name)
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SarifToolDriver {
pub name: String,
pub organization: Option<String>,
#[serde(rename = "semanticVersion")]
pub version: Option<String>,
pub notifications: Option<Vec<SarifToolDriverNotification>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SarifToolDriverNotification {
pub id: String,
pub name: String,
#[serde(rename = "shortDescription")]
pub short_description: SarifMessage,
#[serde(rename = "fullDescription")]
pub full_description: SarifMessage,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SarifMessage {
pub text: String,
}