sarif_rust 0.3.0

A comprehensive Rust library for parsing, generating, and manipulating SARIF (Static Analysis Results Interchange Format) v2.1.0 files
Documentation
//! Builder for Run objects

use crate::builder::ResultBuilder;
use crate::parser::{SarifValidator, ValidationResult};
use crate::types::run::ColumnKind;
use crate::types::{
    Address, Artifact, ArtifactLocation, ExternalPropertyFileReferences, Invocation,
    LogicalLocation, Result, Run, RunAutomationDetails, SpecialLocations, ThreadFlowLocation, Tool,
    ToolComponent, WebRequest, WebResponse,
};
use std::collections::HashMap;

/// Fluent builder for creating SARIF Run objects
#[derive(Debug, Clone)]
pub struct RunBuilder {
    tool: Tool,
    results: Vec<Result>,
    artifacts: Vec<Artifact>,
    invocations: Vec<Invocation>,
    logical_locations: Vec<LogicalLocation>,
    original_uri_base_ids: Option<HashMap<String, ArtifactLocation>>,
    automation_details: Option<RunAutomationDetails>,
    column_kind: Option<ColumnKind>,
    baseline_guid: Option<String>,
    redaction_tokens: Vec<String>,
    default_encoding: Option<String>,
    default_source_language: Option<String>,
    newline_sequences: Vec<String>,
    external_property_file_references: Option<ExternalPropertyFileReferences>,
    thread_flow_locations: Vec<ThreadFlowLocation>,
    addresses: Vec<Address>,
    // policies: Vec<ReportingDescriptor>, // TODO: Fix type - should be Vec<ToolComponent>
    web_requests: Vec<WebRequest>,
    web_responses: Vec<WebResponse>,
    special_locations: Option<SpecialLocations>,
}

impl RunBuilder {
    /// Create a new Run builder with a tool
    pub fn new(tool: Tool) -> Self {
        Self {
            tool,
            results: Vec::new(),
            artifacts: Vec::new(),
            invocations: Vec::new(),
            logical_locations: Vec::new(),
            original_uri_base_ids: None,
            automation_details: None,
            column_kind: None,
            baseline_guid: None,
            redaction_tokens: Vec::new(),
            default_encoding: None,
            default_source_language: None,
            newline_sequences: Vec::new(),
            external_property_file_references: None,
            thread_flow_locations: Vec::new(),
            addresses: Vec::new(),
            // policies: Vec::new(),
            web_requests: Vec::new(),
            web_responses: Vec::new(),
            special_locations: None,
        }
    }

    /// Create a new Run builder with a simple tool name and version
    pub fn with_tool(tool_name: impl Into<String>, version: Option<impl Into<String>>) -> Self {
        let mut tool_component = ToolComponent::new(tool_name);
        if let Some(ver) = version {
            tool_component.version = Some(ver.into());
        }

        let tool = Tool {
            driver: tool_component,
            extensions: None,
            properties: None,
        };

        Self::new(tool)
    }

    /// Add a result to the run
    pub fn add_result(mut self, result: Result) -> Self {
        self.results.push(result);
        self
    }

    /// Add multiple results
    pub fn add_results(mut self, results: impl IntoIterator<Item = Result>) -> Self {
        self.results.extend(results);
        self
    }

    /// Add a result using a builder function
    pub fn add_result_with<F>(mut self, f: F) -> Self
    where
        F: FnOnce(ResultBuilder) -> ResultBuilder,
    {
        let builder = ResultBuilder::with_text_message("");
        let result = f(builder).build();
        self.results.push(result);
        self
    }

    /// Add an artifact to the run
    pub fn add_artifact(mut self, artifact: Artifact) -> Self {
        self.artifacts.push(artifact);
        self
    }

    /// Add multiple artifacts
    pub fn add_artifacts(mut self, artifacts: impl IntoIterator<Item = Artifact>) -> Self {
        self.artifacts.extend(artifacts);
        self
    }

    /// Add a simple file artifact
    pub fn add_file_artifact(mut self, file_path: impl Into<String>) -> Self {
        let artifact = Artifact::new(ArtifactLocation::new(file_path));
        self.artifacts.push(artifact);
        self
    }

    /// Add an invocation
    pub fn add_invocation(mut self, invocation: Invocation) -> Self {
        self.invocations.push(invocation);
        self
    }

    /// Add a logical location
    pub fn add_logical_location(mut self, logical_location: LogicalLocation) -> Self {
        self.logical_locations.push(logical_location);
        self
    }

    /// Add an original URI base ID
    pub fn add_original_uri_base_id(
        mut self,
        key: impl Into<String>,
        location: ArtifactLocation,
    ) -> Self {
        if self.original_uri_base_ids.is_none() {
            self.original_uri_base_ids = Some(HashMap::new());
        }
        self.original_uri_base_ids
            .as_mut()
            .unwrap()
            .insert(key.into(), location);
        self
    }

    /// Set automation details
    pub fn with_automation_details(mut self, automation_details: RunAutomationDetails) -> Self {
        self.automation_details = Some(automation_details);
        self
    }

    /// Set column kind
    pub fn with_column_kind(mut self, column_kind: ColumnKind) -> Self {
        self.column_kind = Some(column_kind);
        self
    }

    /// Set baseline GUID
    pub fn with_baseline_guid(mut self, baseline_guid: impl Into<String>) -> Self {
        self.baseline_guid = Some(baseline_guid.into());
        self
    }

    /// Add a redaction token
    pub fn add_redaction_token(mut self, token: impl Into<String>) -> Self {
        self.redaction_tokens.push(token.into());
        self
    }

    /// Set default encoding
    pub fn with_default_encoding(mut self, encoding: impl Into<String>) -> Self {
        self.default_encoding = Some(encoding.into());
        self
    }

    /// Set default source language
    pub fn with_default_source_language(mut self, language: impl Into<String>) -> Self {
        self.default_source_language = Some(language.into());
        self
    }

    /// Add a newline sequence
    pub fn add_newline_sequence(mut self, sequence: impl Into<String>) -> Self {
        self.newline_sequences.push(sequence.into());
        self
    }

    /// Set external property file references
    pub fn with_external_property_file_references(
        mut self,
        refs: ExternalPropertyFileReferences,
    ) -> Self {
        self.external_property_file_references = Some(refs);
        self
    }

    /// Add an address
    pub fn add_address(mut self, address: Address) -> Self {
        self.addresses.push(address);
        self
    }

    // TODO: Add policy methods when type is fixed

    /// Add a web request
    pub fn add_web_request(mut self, web_request: WebRequest) -> Self {
        self.web_requests.push(web_request);
        self
    }

    /// Add a web response
    pub fn add_web_response(mut self, web_response: WebResponse) -> Self {
        self.web_responses.push(web_response);
        self
    }

    /// Set special locations
    pub fn with_special_locations(mut self, special_locations: SpecialLocations) -> Self {
        self.special_locations = Some(special_locations);
        self
    }

    /// Validate the builder state
    pub fn validate(&self, validator: &SarifValidator) -> ValidationResult<()> {
        // Validate the tool
        validator.validate_tool(&self.tool)?;

        // Validate results
        for result in &self.results {
            validator.validate_result(result)?;
        }

        // Validate artifacts
        for artifact in &self.artifacts {
            validator.validate_artifact(artifact)?;
        }

        // Validate invocations
        for invocation in &self.invocations {
            validator.validate_invocation(invocation)?;
        }

        // Validate logical locations
        for logical_location in &self.logical_locations {
            validator.validate_logical_location(logical_location)?;
        }

        Ok(())
    }

    /// Build the Run object
    pub fn build(self) -> Run {
        let mut run = Run::new(self.tool);

        // Set optional fields
        run.automation_details = self.automation_details;
        run.baseline_guid = self.baseline_guid;
        run.column_kind = self.column_kind;
        run.default_encoding = self.default_encoding;
        run.default_source_language = self.default_source_language;
        run.external_property_file_references = self.external_property_file_references;
        run.special_locations = self.special_locations;

        // Set collections only if not empty
        if !self.results.is_empty() {
            run.results = Some(self.results);
        }
        if !self.artifacts.is_empty() {
            run.artifacts = Some(self.artifacts);
        }
        if !self.invocations.is_empty() {
            run.invocations = Some(self.invocations);
        }
        if !self.logical_locations.is_empty() {
            run.logical_locations = Some(self.logical_locations);
        }
        if let Some(uri_base_ids) = self.original_uri_base_ids
            && !uri_base_ids.is_empty()
        {
            run.original_uri_base_ids = Some(uri_base_ids);
        }
        if !self.redaction_tokens.is_empty() {
            run.redaction_tokens = Some(self.redaction_tokens);
        }
        if !self.newline_sequences.is_empty() {
            run.newline_sequences = Some(self.newline_sequences);
        }
        if !self.thread_flow_locations.is_empty() {
            run.thread_flow_locations = Some(self.thread_flow_locations);
        }
        if !self.addresses.is_empty() {
            run.addresses = Some(self.addresses);
        }
        // TODO: Set policies when type is fixed
        if !self.web_requests.is_empty() {
            run.web_requests = Some(self.web_requests);
        }
        if !self.web_responses.is_empty() {
            run.web_responses = Some(self.web_responses);
        }

        run
    }

    /// Build and validate the Run object
    pub fn build_validated(self, validator: &SarifValidator) -> ValidationResult<Run> {
        self.validate(validator)?;
        Ok(self.build())
    }
}