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
//! SARIF artifact-related types
//!
//! This module defines structures for representing artifacts (files) and their locations.

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use url::Url;

/// Specifies the location of an artifact
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ArtifactLocation {
    /// A string containing a valid relative or absolute URI
    pub uri: Option<String>,

    /// A string which indirectly specifies the absolute URI
    pub uri_base_id: Option<String>,

    /// The index within the 'artifacts' array of the artifact object
    pub index: Option<i32>,

    /// A short description of the artifact location
    pub description: Option<crate::types::Message>,

    /// Key/value pairs that provide additional information about the artifact location
    #[serde(flatten)]
    pub properties: Option<HashMap<String, serde_json::Value>>,
}

/// A single file
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Artifact {
    /// The location of the artifact
    pub location: Option<ArtifactLocation>,

    /// Identifies the index of the immediate parent of the artifact
    pub parent_index: Option<i32>,

    /// The offset in bytes of the artifact within its containing artifact
    pub offset: Option<i32>,

    /// The length of the artifact in bytes
    pub length: Option<i32>,

    /// The role or roles played by the artifact in the analysis
    pub roles: Option<Vec<ArtifactRole>>,

    /// The MIME type of the artifact
    pub mime_type: Option<String>,

    /// The contents of the artifact
    pub contents: Option<ArtifactContent>,

    /// Specifies the encoding for an artifact object that refers to a text file
    pub encoding: Option<String>,

    /// Specifies the source language for any artifact that contains source code
    pub source_language: Option<String>,

    /// An array of hash objects
    pub hashes: Option<Vec<Hash>>,

    /// The Coordinated Universal Time (UTC) date and time at which the artifact was most recently modified
    pub last_modified_time_utc: Option<String>,

    /// A short description of the artifact
    pub description: Option<crate::types::Message>,

    /// Key/value pairs that provide additional information about the artifact
    #[serde(flatten)]
    pub properties: Option<HashMap<String, serde_json::Value>>,
}

/// The contents of an artifact
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ArtifactContent {
    /// UTF-8-encoded content from a text artifact
    pub text: Option<String>,

    /// MIME Base64-encoded content from a binary artifact, or from a text artifact in its original encoding
    pub binary: Option<String>,

    /// An alternate rendered representation of the artifact
    pub rendered: Option<MultiformatMessageString>,

    /// Key/value pairs that provide additional information about the artifact content
    #[serde(flatten)]
    pub properties: Option<HashMap<String, serde_json::Value>>,
}

/// A message string or message format string rendered in multiple formats
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MultiformatMessageString {
    /// A plain text message string
    pub text: Option<String>,

    /// A Markdown message string
    pub markdown: Option<String>,

    /// Key/value pairs that provide additional information
    #[serde(flatten)]
    pub properties: Option<HashMap<String, serde_json::Value>>,
}

/// The role or roles played by the artifact in the analysis
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ArtifactRole {
    /// The artifact is source code
    AnalysisTarget,
    /// The artifact contains or represents an attachment
    Attachment,
    /// The artifact is a response file
    ResponseFile,
    /// The artifact is a result file
    ResultFile,
    /// The artifact is a standard stream
    StandardStream,
    /// The artifact contains tracing information
    TracedFile,
    /// The artifact was generated during the build
    Unmodified,
    /// The artifact was modified during the build
    Modified,
    /// The artifact was added during the build
    Added,
    /// The artifact was deleted during the build
    Deleted,
    /// The artifact was renamed during the build
    Renamed,
    /// The artifact was translated during the build
    Translated,
    /// The artifact is a configuration file
    ConfigurationFile,
    /// The artifact is a policy file
    PolicyFile,
    /// The artifact is a debug file
    DebugOutputFile,
}

/// A hash value
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Hash {
    /// The cryptographic hash of the artifact
    pub value: String,

    /// The name of the hash algorithm
    pub algorithm: String,

    /// Key/value pairs that provide additional information about the hash
    #[serde(flatten)]
    pub properties: Option<HashMap<String, serde_json::Value>>,
}

impl ArtifactLocation {
    /// Create a new artifact location with a URI
    pub fn new(uri: impl Into<String>) -> Self {
        Self {
            uri: Some(uri.into()),
            uri_base_id: None,
            index: None,
            description: None,
            properties: None,
        }
    }

    /// Create an artifact location from an index
    pub fn from_index(index: i32) -> Self {
        Self {
            uri: None,
            uri_base_id: None,
            index: Some(index),
            description: None,
            properties: None,
        }
    }

    /// Set the URI base ID
    pub fn with_uri_base_id(mut self, uri_base_id: impl Into<String>) -> Self {
        self.uri_base_id = Some(uri_base_id.into());
        self
    }

    /// Set the description
    pub fn with_description(mut self, description: impl Into<crate::types::Message>) -> Self {
        self.description = Some(description.into());
        self
    }

    /// Parse the URI if present
    pub fn parse_uri(&self) -> Result<Option<Url>, url::ParseError> {
        match &self.uri {
            Some(uri) => Ok(Some(Url::parse(uri)?)),
            None => Ok(None),
        }
    }
}

impl Artifact {
    /// Create a new artifact with a location
    pub fn new(location: ArtifactLocation) -> Self {
        Self {
            location: Some(location),
            parent_index: None,
            offset: None,
            length: None,
            roles: None,
            mime_type: None,
            contents: None,
            encoding: None,
            source_language: None,
            hashes: None,
            last_modified_time_utc: None,
            description: None,
            properties: None,
        }
    }

    /// Add a role to the artifact
    pub fn add_role(mut self, role: ArtifactRole) -> Self {
        self.roles.get_or_insert_with(Vec::new).push(role);
        self
    }

    /// Set the MIME type
    pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
        self.mime_type = Some(mime_type.into());
        self
    }

    /// Set text content
    pub fn with_text_content(mut self, text: impl Into<String>) -> Self {
        let content = ArtifactContent {
            text: Some(text.into()),
            binary: None,
            rendered: None,
            properties: None,
        };
        self.contents = Some(content);
        self
    }
}

impl Hash {
    /// Create a new hash
    pub fn new(algorithm: impl Into<String>, value: impl Into<String>) -> Self {
        Self {
            algorithm: algorithm.into(),
            value: value.into(),
            properties: None,
        }
    }

    /// Create an MD5 hash
    pub fn md5(value: impl Into<String>) -> Self {
        Self::new("md5", value)
    }

    /// Create a SHA-1 hash
    pub fn sha1(value: impl Into<String>) -> Self {
        Self::new("sha1", value)
    }

    /// Create a SHA-256 hash
    pub fn sha256(value: impl Into<String>) -> Self {
        Self::new("sha256", value)
    }
}