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 Result objects

use crate::parser::{SarifValidator, ValidationResult};
use crate::types::{
    ArtifactLocation, Attachment, BaselineState, CodeFlow, Fix, GraphTraversal, Kind, Level,
    Location, Message, PhysicalLocation, Region, Result, ResultProvenance, Stack, Suppression,
};
use std::collections::HashMap;

/// Fluent builder for creating SARIF Result objects
#[derive(Debug, Clone)]
pub struct ResultBuilder {
    message: Message,
    rule_id: Option<String>,
    rule_index: Option<i32>,
    level: Option<Level>,
    kind: Option<Kind>,
    locations: Vec<Location>,
    // related_locations: Vec<RelatedLocation>, // TODO: Add when RelatedLocation type exists
    code_flows: Vec<CodeFlow>,
    stacks: Vec<Stack>,
    fixes: Vec<Fix>,
    suppressions: Vec<Suppression>,
    baseline_state: Option<BaselineState>,
    rank: Option<f64>,
    guid: Option<String>,
    correlation_guid: Option<String>,
    occurrence_count: Option<i32>,
    partial_fingerprints: Option<HashMap<String, String>>,
    fingerprints: Option<HashMap<String, String>>,
    graph_traversals: Vec<GraphTraversal>,
    attachments: Vec<Attachment>,
    hosted_viewer_uri: Option<String>,
    work_item_uris: Vec<String>,
    provenance: Option<ResultProvenance>,
}

impl ResultBuilder {
    /// Create a new Result builder with a message
    pub fn new(message: impl Into<Message>) -> Self {
        Self {
            message: message.into(),
            rule_id: None,
            rule_index: None,
            level: None,
            kind: None,
            locations: Vec::new(),
            // related_locations: Vec::new(),
            code_flows: Vec::new(),
            stacks: Vec::new(),
            fixes: Vec::new(),
            suppressions: Vec::new(),
            baseline_state: None,
            rank: None,
            guid: None,
            correlation_guid: None,
            occurrence_count: None,
            partial_fingerprints: None,
            fingerprints: None,
            graph_traversals: Vec::new(),
            attachments: Vec::new(),
            hosted_viewer_uri: None,
            work_item_uris: Vec::new(),
            provenance: None,
        }
    }

    /// Create a new Result builder with a simple text message
    pub fn with_text_message(text: impl Into<String>) -> Self {
        Self::new(Message::new(text))
    }

    /// Set the rule ID
    pub fn with_rule_id(mut self, rule_id: impl Into<String>) -> Self {
        self.rule_id = Some(rule_id.into());
        self
    }

    /// Set the rule index
    pub fn with_rule_index(mut self, rule_index: i32) -> Self {
        self.rule_index = Some(rule_index);
        self
    }

    /// Set the result level
    pub fn with_level(mut self, level: Level) -> Self {
        self.level = Some(level);
        self
    }

    /// Set the result kind
    pub fn with_kind(mut self, kind: Kind) -> Self {
        self.kind = Some(kind);
        self
    }

    /// Add a location to the result
    pub fn add_location(mut self, location: Location) -> Self {
        self.locations.push(location);
        self
    }

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

    /// Add a simple file location with line and column
    pub fn add_file_location(
        self,
        file_path: impl Into<String>,
        start_line: i32,
        start_column: i32,
    ) -> Self {
        let location = Location::with_physical_location(
            PhysicalLocation::with_artifact_location(ArtifactLocation::new(file_path)).with_region(
                Region::from_coordinates(start_line, start_column, start_line, start_column + 10),
            ),
        );
        self.add_location(location)
    }

    /// Add a file location with region
    pub fn add_file_region(
        self,
        file_path: impl Into<String>,
        start_line: i32,
        start_column: i32,
        end_line: i32,
        end_column: i32,
    ) -> Self {
        let location = Location::with_physical_location(
            PhysicalLocation::with_artifact_location(ArtifactLocation::new(file_path)).with_region(
                Region::from_coordinates(start_line, start_column, end_line, end_column),
            ),
        );
        self.add_location(location)
    }

    /// Add a file location with a predefined region
    pub fn add_file_location_with_region(
        self,
        file_path: impl Into<String>,
        region: Region,
    ) -> Self {
        let location = Location::with_physical_location(
            PhysicalLocation::with_artifact_location(ArtifactLocation::new(file_path)).with_region(region),
        );
        self.add_location(location)
    }

    // TODO: Add related location methods when RelatedLocation type exists

    /// Add a code flow
    pub fn add_code_flow(mut self, code_flow: CodeFlow) -> Self {
        self.code_flows.push(code_flow);
        self
    }

    /// Add a stack trace
    pub fn add_stack(mut self, stack: Stack) -> Self {
        self.stacks.push(stack);
        self
    }

    /// Add a fix
    pub fn add_fix(mut self, fix: Fix) -> Self {
        self.fixes.push(fix);
        self
    }

    /// Add a suppression
    pub fn add_suppression(mut self, suppression: Suppression) -> Self {
        self.suppressions.push(suppression);
        self
    }

    /// Set the baseline state
    pub fn with_baseline_state(mut self, baseline_state: BaselineState) -> Self {
        self.baseline_state = Some(baseline_state);
        self
    }

    /// Set the rank (severity/priority)
    pub fn with_rank(mut self, rank: f64) -> Self {
        self.rank = Some(rank);
        self
    }

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

    /// Set the correlation GUID
    pub fn with_correlation_guid(mut self, correlation_guid: impl Into<String>) -> Self {
        self.correlation_guid = Some(correlation_guid.into());
        self
    }

    /// Set the occurrence count
    pub fn with_occurrence_count(mut self, count: i32) -> Self {
        self.occurrence_count = Some(count);
        self
    }

    /// Add a partial fingerprint
    pub fn add_partial_fingerprint(
        mut self,
        key: impl Into<String>,
        value: impl Into<String>,
    ) -> Self {
        if self.partial_fingerprints.is_none() {
            self.partial_fingerprints = Some(HashMap::new());
        }
        self.partial_fingerprints
            .as_mut()
            .unwrap()
            .insert(key.into(), value.into());
        self
    }

    /// Add a fingerprint
    pub fn add_fingerprint(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        if self.fingerprints.is_none() {
            self.fingerprints = Some(HashMap::new());
        }
        self.fingerprints
            .as_mut()
            .unwrap()
            .insert(key.into(), value.into());
        self
    }

    /// Add a work item URI
    pub fn add_work_item_uri(mut self, uri: impl Into<String>) -> Self {
        self.work_item_uris.push(uri.into());
        self
    }

    /// Set the hosted viewer URI
    pub fn with_hosted_viewer_uri(mut self, uri: impl Into<String>) -> Self {
        self.hosted_viewer_uri = Some(uri.into());
        self
    }

    /// Set the provenance
    pub fn with_provenance(mut self, provenance: ResultProvenance) -> Self {
        self.provenance = Some(provenance);
        self
    }

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

        // Validate locations
        for location in &self.locations {
            validator.validate_location(location)?;
        }

        // Validate URIs if present
        if let Some(ref uri) = self.hosted_viewer_uri {
            validator.validate_uri(uri)?;
        }

        for uri in &self.work_item_uris {
            validator.validate_uri(uri)?;
        }

        Ok(())
    }

    /// Build the Result object
    pub fn build(self) -> Result {
        let mut result = Result::new(self.message);

        result.rule_id = self.rule_id;
        result.rule_index = self.rule_index;
        result.level = self.level;
        result.kind = self.kind;
        result.baseline_state = self.baseline_state;
        result.rank = self.rank;
        result.guid = self.guid;
        result.correlation_guid = self.correlation_guid;
        result.occurrence_count = self.occurrence_count;
        result.partial_fingerprints = self.partial_fingerprints;
        result.fingerprints = self.fingerprints;
        result.hosted_viewer_uri = self.hosted_viewer_uri;
        result.provenance = self.provenance;

        // Set collections only if not empty
        if !self.locations.is_empty() {
            result.locations = Some(self.locations);
        }
        // TODO: Set related_locations when available
        if !self.code_flows.is_empty() {
            result.code_flows = Some(self.code_flows);
        }
        if !self.stacks.is_empty() {
            result.stacks = Some(self.stacks);
        }
        if !self.fixes.is_empty() {
            result.fixes = Some(self.fixes);
        }
        if !self.suppressions.is_empty() {
            result.suppressions = Some(self.suppressions);
        }
        if !self.graph_traversals.is_empty() {
            result.graph_traversals = Some(self.graph_traversals);
        }
        if !self.attachments.is_empty() {
            result.attachments = Some(self.attachments);
        }
        if !self.work_item_uris.is_empty() {
            result.work_item_uris = Some(self.work_item_uris);
        }

        result
    }

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