use serde::{Serialize, ser::SerializeMap};
use std::collections::BTreeMap;
pub struct SarifLog {
pub runs: Vec<Run>,
}
impl Serialize for SarifLog {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut m = serializer.serialize_map(Some(3))?;
m.serialize_entry("$schema", "https://json.schemastore.org/sarif-2.1.0.json")?;
m.serialize_entry("version", "2.1.0")?;
m.serialize_entry("runs", &self.runs)?;
m.end()
}
}
#[derive(Serialize)]
pub struct Run {
pub tool: Tool,
pub results: Vec<Result>,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Tool {
pub driver: Driver,
}
pub struct Driver {
pub rules: Vec<Rule>,
pub version: Option<semver::Version>,
}
impl Serialize for Driver {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut m = serializer.serialize_map(Some(4))?;
m.serialize_entry("name", "cargo-deny")?;
if let Some(v) = &self.version {
m.serialize_entry("version", &v)?;
m.serialize_entry("semanticVersion", &v)?;
} else {
m.serialize_entry("version", env!("CARGO_PKG_VERSION"))?;
m.serialize_entry("semanticVersion", env!("CARGO_PKG_VERSION"))?;
}
m.serialize_entry("rules", &self.rules)?;
m.end()
}
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Rule {
pub id: String,
pub name: String,
pub short_description: TextContent,
pub full_description: TextContent,
pub default_configuration: DefaultConfiguration,
pub help: Help,
pub properties: RuleProperties,
}
#[derive(Serialize)]
pub struct TextContent {
pub text: String,
}
#[derive(Serialize)]
pub struct DefaultConfiguration {
pub level: String,
}
pub struct Help(pub crate::diag::DiagnosticCode);
impl Serialize for Help {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut m = serializer.serialize_map(Some(2))?;
m.serialize_entry("text", "For more information, see cargo-deny documentation")?;
let (check, code): (_, &'static str) = match self.0 {
crate::diag::DiagnosticCode::Advisory(code) => ("advisories", code.into()),
crate::diag::DiagnosticCode::Bans(code) => ("bans", code.into()),
crate::diag::DiagnosticCode::License(code) => ("licenses", code.into()),
crate::diag::DiagnosticCode::Source(code) => ("sources", code.into()),
crate::diag::DiagnosticCode::General(_code) => {
return m.end();
}
};
const DOC_ROOT: &str = "https://embarkstudios.github.io/cargo-deny/";
m.serialize_entry(
"markdown",
&format!("[{check} check]({DOC_ROOT}checks/{check}/index.html)\n[{code} diagnostic]({DOC_ROOT}checks/{check}/diags.html#{code})"),
)?;
m.end()
}
}
#[derive(Serialize)]
pub struct RuleProperties {
pub tags: &'static [&'static str],
pub precision: &'static str,
#[serde(rename = "problem.severity")]
pub problem_severity: &'static str,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Result {
pub rule_id: String,
pub message: Message,
pub level: &'static str,
pub locations: Vec<Location>,
pub partial_fingerprints: BTreeMap<String, String>,
}
#[derive(Serialize, Debug)]
pub struct Message {
pub text: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub markdown: Option<String>,
}
impl Message {
#[inline]
pub fn text(text: String) -> Self {
Self {
text,
markdown: None,
}
}
}
#[derive(Serialize, Debug)]
pub struct ArtifactContent {
pub text: String,
}
#[derive(Serialize)]
pub struct Location {
#[serde(rename = "physicalLocation")]
pub physical_location: PhysicalLocation,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PhysicalLocation {
#[serde(rename = "artifactLocation")]
pub artifact_location: ArtifactLocation,
pub region: Region,
}
#[derive(Debug, Serialize)]
pub struct ArtifactLocation {
pub uri: String,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Region {
pub start_line: usize,
pub byte_offset: usize,
pub byte_length: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub snippet: Option<ArtifactContent>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<Message>,
}
#[inline]
pub fn severity_to_sarif_level(severity: crate::diag::Severity) -> &'static str {
use crate::diag::Severity;
match severity {
Severity::Error | Severity::Bug => "error",
Severity::Warning => "warning",
Severity::Note | Severity::Help => "note",
}
}