use crate::builder::{ResultBuilder, RunBuilder};
use crate::parser::{SarifValidator, ValidationResult, validator::Validate};
use crate::types::{ExternalProperties, Run, SarifLog, Tool, ToolComponent};
#[derive(Debug, Clone)]
pub struct SarifLogBuilder {
version: String,
runs: Vec<Run>,
schema: Option<String>,
inline_external_properties: Vec<ExternalProperties>,
}
impl SarifLogBuilder {
pub fn new() -> Self {
Self {
version: "2.1.0".to_string(),
runs: Vec::new(),
schema: None,
inline_external_properties: Vec::new(),
}
}
pub fn v2_1_0() -> Self {
Self::new().with_version("2.1.0")
}
pub fn with_standard_schema() -> Self {
Self::new().with_schema("https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json")
}
pub fn with_version(mut self, version: impl Into<String>) -> Self {
self.version = version.into();
self
}
pub fn with_schema(mut self, schema: impl Into<String>) -> Self {
self.schema = Some(schema.into());
self
}
pub fn add_run(mut self, run: Run) -> Self {
self.runs.push(run);
self
}
pub fn add_runs(mut self, runs: impl IntoIterator<Item = Run>) -> Self {
self.runs.extend(runs);
self
}
pub fn add_run_with<F>(mut self, f: F) -> Self
where
F: FnOnce(RunBuilder) -> RunBuilder,
{
let default_tool = Tool {
driver: ToolComponent::new("unknown"),
extensions: None,
properties: None,
};
let builder = RunBuilder::new(default_tool);
let run = f(builder).build();
self.runs.push(run);
self
}
pub fn add_simple_run(
mut self,
tool_name: impl Into<String>,
tool_version: Option<impl Into<String>>,
) -> Self {
let run = RunBuilder::with_tool(tool_name, tool_version).build();
self.runs.push(run);
self
}
pub fn add_external_properties(mut self, properties: ExternalProperties) -> Self {
self.inline_external_properties.push(properties);
self
}
pub fn quick_sarif(
tool_name: impl Into<String>,
tool_version: impl Into<String>,
message: impl Into<String>,
file_path: impl Into<String>,
line: i32,
column: i32,
) -> Self {
let result = ResultBuilder::with_text_message(message)
.add_file_location(file_path, line, column)
.build();
let run = RunBuilder::with_tool(tool_name, Some(tool_version))
.add_result(result)
.build();
Self::new().add_run(run)
}
#[allow(clippy::too_many_arguments)]
pub fn error_finding(
tool_name: impl Into<String>,
rule_id: impl Into<String>,
message: impl Into<String>,
file_path: impl Into<String>,
start_line: i32,
start_column: i32,
end_line: i32,
end_column: i32,
) -> Self {
use crate::types::Level;
let result = ResultBuilder::with_text_message(message)
.with_rule_id(rule_id)
.with_level(Level::Error)
.add_file_region(file_path, start_line, start_column, end_line, end_column)
.build();
let run = RunBuilder::with_tool(tool_name, None::<String>)
.add_result(result)
.build();
Self::new().add_run(run)
}
#[allow(clippy::too_many_arguments)]
pub fn warning_finding(
tool_name: impl Into<String>,
rule_id: impl Into<String>,
message: impl Into<String>,
file_path: impl Into<String>,
start_line: i32,
start_column: i32,
end_line: i32,
end_column: i32,
) -> Self {
use crate::types::Level;
let result = ResultBuilder::with_text_message(message)
.with_rule_id(rule_id)
.with_level(Level::Warning)
.add_file_region(file_path, start_line, start_column, end_line, end_column)
.build();
let run = RunBuilder::with_tool(tool_name, None::<String>)
.add_result(result)
.build();
Self::new().add_run(run)
}
pub fn error_finding_with_region(
tool_name: impl Into<String>,
rule_id: impl Into<String>,
message: impl Into<String>,
file_path: impl Into<String>,
region: crate::types::Region,
) -> Self {
use crate::types::Level;
let result = ResultBuilder::with_text_message(message)
.with_rule_id(rule_id)
.with_level(Level::Error)
.add_file_location_with_region(file_path, region)
.build();
let run = RunBuilder::with_tool(tool_name, None::<String>)
.add_result(result)
.build();
Self::new().add_run(run)
}
pub fn warning_finding_with_region(
tool_name: impl Into<String>,
rule_id: impl Into<String>,
message: impl Into<String>,
file_path: impl Into<String>,
region: crate::types::Region,
) -> Self {
use crate::types::Level;
let result = ResultBuilder::with_text_message(message)
.with_rule_id(rule_id)
.with_level(Level::Warning)
.add_file_location_with_region(file_path, region)
.build();
let run = RunBuilder::with_tool(tool_name, None::<String>)
.add_result(result)
.build();
Self::new().add_run(run)
}
pub fn validate(&self, validator: &SarifValidator) -> ValidationResult<()> {
validator.validate_version(&self.version)?;
if let Some(ref schema) = self.schema {
validator.validate_uri(schema)?;
}
if self.runs.is_empty() {
return Err(crate::parser::ValidationError::missing_field(
"At least one run is required in a SARIF log",
));
}
for run in &self.runs {
validator.validate_run(run)?;
}
Ok(())
}
pub fn build(self) -> ValidationResult<SarifLog> {
let log = SarifLog {
schema: self.schema,
version: self.version,
runs: self.runs,
inline_external_properties: if self.inline_external_properties.is_empty() {
None
} else {
Some(self.inline_external_properties)
},
properties: None,
};
log.validate()?;
Ok(log)
}
pub fn build_validated(self, validator: &SarifValidator) -> ValidationResult<SarifLog> {
self.validate(validator)?;
Ok(self.build_unchecked())
}
pub fn build_unchecked(self) -> SarifLog {
SarifLog {
schema: self.schema,
version: self.version,
runs: self.runs,
inline_external_properties: if self.inline_external_properties.is_empty() {
None
} else {
Some(self.inline_external_properties)
},
properties: None,
}
}
pub fn result_count(&self) -> usize {
self.runs
.iter()
.map(|run| run.results.as_ref().map_or(0, |results| results.len()))
.sum()
}
pub fn artifact_count(&self) -> usize {
self.runs
.iter()
.map(|run| {
run.artifacts
.as_ref()
.map_or(0, |artifacts| artifacts.len())
})
.sum()
}
pub fn run_count(&self) -> usize {
self.runs.len()
}
pub fn is_empty(&self) -> bool {
self.runs.is_empty()
}
}
impl Default for SarifLogBuilder {
fn default() -> Self {
Self::new()
}
}
impl SarifLogBuilder {
pub fn minimal() -> Self {
let tool = Tool {
driver: ToolComponent::new("minimal-tool"),
extensions: None,
properties: None,
};
let run = Run::new(tool);
Self::new().add_run(run)
}
pub fn single_error(
tool_name: impl Into<String>,
message: impl Into<String>,
file_path: impl Into<String>,
line: i32,
) -> Self {
Self::error_finding(tool_name, "ERROR", message, file_path, line, 1, line, 80)
}
pub fn single_warning(
tool_name: impl Into<String>,
message: impl Into<String>,
file_path: impl Into<String>,
line: i32,
) -> Self {
Self::warning_finding(tool_name, "WARNING", message, file_path, line, 1, line, 80)
}
}