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
//! JSON parsing implementation for SARIF
//!
//! This module provides functions for parsing and serializing SARIF objects to/from JSON.

use crate::parser::{SarifError, SarifResult};
use crate::types::SarifLog;
use std::fs::File;
use std::io::{BufReader, BufWriter, Read, Write};
use std::path::Path;

/// Parse SARIF from a JSON string
pub fn parse_sarif_from_str(json: &str) -> SarifResult<SarifLog> {
    let sarif: SarifLog = serde_json::from_str(json)?;
    Ok(sarif)
}

/// Parse SARIF from a reader
pub fn parse_sarif_from_reader<R: Read>(reader: R) -> SarifResult<SarifLog> {
    let sarif: SarifLog = serde_json::from_reader(reader)?;
    Ok(sarif)
}

/// Parse SARIF from a file path
pub fn parse_sarif_from_file<P: AsRef<Path>>(path: P) -> SarifResult<SarifLog> {
    let file = File::open(path)?;
    let reader = BufReader::new(file);
    parse_sarif_from_reader(reader)
}

/// Serialize SARIF to a JSON string
pub fn serialize_sarif_to_string(sarif: &SarifLog) -> SarifResult<String> {
    let json = serde_json::to_string(sarif)?;
    Ok(json)
}

/// Serialize SARIF to a pretty-printed JSON string
pub fn serialize_sarif_to_string_pretty(sarif: &SarifLog) -> SarifResult<String> {
    let json = serde_json::to_string_pretty(sarif)?;
    Ok(json)
}

/// Serialize SARIF to a writer
pub fn serialize_sarif_to_writer<W: Write>(writer: W, sarif: &SarifLog) -> SarifResult<()> {
    serde_json::to_writer(writer, sarif)?;
    Ok(())
}

/// Serialize SARIF to a writer with pretty printing
pub fn serialize_sarif_to_writer_pretty<W: Write>(writer: W, sarif: &SarifLog) -> SarifResult<()> {
    serde_json::to_writer_pretty(writer, sarif)?;
    Ok(())
}

/// Serialize SARIF to a file path
pub fn serialize_sarif_to_file<P: AsRef<Path>>(path: P, sarif: &SarifLog) -> SarifResult<()> {
    let file = File::create(path)?;
    let writer = BufWriter::new(file);
    serialize_sarif_to_writer_pretty(writer, sarif)
}

/// Parse JSON value to a specific SARIF type
pub fn parse_json_value<T>(value: &serde_json::Value) -> SarifResult<T>
where
    T: serde::de::DeserializeOwned,
{
    let result: T = serde_json::from_value(value.clone())?;
    Ok(result)
}

/// Convert a SARIF type to a JSON value
pub fn to_json_value<T>(item: &T) -> SarifResult<serde_json::Value>
where
    T: serde::Serialize,
{
    let value = serde_json::to_value(item)?;
    Ok(value)
}

/// Minify a JSON string (remove unnecessary whitespace)
pub fn minify_json(json: &str) -> SarifResult<String> {
    let value: serde_json::Value = serde_json::from_str(json)?;
    let minified = serde_json::to_string(&value)?;
    Ok(minified)
}

/// Pretty-print a JSON string
pub fn prettify_json(json: &str) -> SarifResult<String> {
    let value: serde_json::Value = serde_json::from_str(json)?;
    let prettified = serde_json::to_string_pretty(&value)?;
    Ok(prettified)
}

/// Validate JSON structure without full parsing
pub fn validate_json_structure(json: &str) -> SarifResult<()> {
    let _: serde_json::Value = serde_json::from_str(json)?;
    Ok(())
}

/// Parse SARIF with custom configuration
pub struct SarifParser {
    /// Whether to validate the SARIF structure during parsing
    pub validate: bool,
    /// Maximum file size to parse (in bytes)
    pub max_size: Option<usize>,
    /// Whether to allow unknown fields
    pub allow_unknown_fields: bool,
}

impl Default for SarifParser {
    fn default() -> Self {
        Self {
            validate: true,
            max_size: None,
            allow_unknown_fields: true,
        }
    }
}

impl SarifParser {
    /// Create a new parser with default settings
    pub fn new() -> Self {
        Self::default()
    }

    /// Set whether to validate during parsing
    pub fn with_validation(mut self, validate: bool) -> Self {
        self.validate = validate;
        self
    }

    /// Set maximum file size
    pub fn with_max_size(mut self, max_size: usize) -> Self {
        self.max_size = Some(max_size);
        self
    }

    /// Set whether to allow unknown fields
    pub fn with_allow_unknown_fields(mut self, allow: bool) -> Self {
        self.allow_unknown_fields = allow;
        self
    }

    /// Parse SARIF from a string with this parser's configuration
    pub fn parse_from_str(&self, json: &str) -> SarifResult<SarifLog> {
        // Check size limit
        if let Some(max_size) = self.max_size
            && json.len() > max_size
        {
            return Err(SarifError::custom(format!(
                "JSON size {} exceeds maximum {}",
                json.len(),
                max_size
            )));
        }

        // Parse JSON
        let sarif = if self.allow_unknown_fields {
            serde_json::from_str(json)?
        } else {
            // TODO: Implement strict parsing that rejects unknown fields
            serde_json::from_str(json)?
        };

        // Validate if requested
        if self.validate {
            // TODO: Add validation logic
        }

        Ok(sarif)
    }

    /// Parse SARIF from a file with this parser's configuration
    pub fn parse_from_file<P: AsRef<Path>>(&self, path: P) -> SarifResult<SarifLog> {
        let content = std::fs::read_to_string(path)?;
        self.parse_from_str(&content)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::types::*;

    #[test]
    fn test_minimal_sarif_serialization() {
        let sarif = SarifLog::v2_1_0();
        let json = serialize_sarif_to_string(&sarif).unwrap();
        assert!(json.contains("\"version\":\"2.1.0\""));
        assert!(json.contains("\"runs\":[]"));
    }

    #[test]
    fn test_sarif_round_trip() {
        let original = SarifLog::v2_1_0();
        let json = serialize_sarif_to_string(&original).unwrap();
        let parsed = parse_sarif_from_str(&json).unwrap();

        // Compare fields individually to handle potential properties differences
        assert_eq!(original.schema, parsed.schema);
        assert_eq!(original.version, parsed.version);
        assert_eq!(original.runs, parsed.runs);
        assert_eq!(
            original.inline_external_properties,
            parsed.inline_external_properties
        );

        // Properties might be None or Some(empty map) - both are valid
        match (&original.properties, &parsed.properties) {
            (None, None) => {}
            (None, Some(map)) if map.is_empty() => {}
            (Some(map), None) if map.is_empty() => {}
            (Some(orig), Some(parsed)) => assert_eq!(orig, parsed),
            _ => panic!(
                "Properties don't match: original={:?}, parsed={:?}",
                original.properties, parsed.properties
            ),
        }
    }

    #[test]
    fn test_parser_configuration() {
        let parser = SarifParser::new()
            .with_validation(false)
            .with_max_size(1024);

        assert!(!parser.validate);
        assert_eq!(parser.max_size, Some(1024));
    }

    #[test]
    fn test_max_size_enforcement() {
        let parser = SarifParser::new().with_max_size(10);
        let large_json = "a".repeat(20);

        let result = parser.parse_from_str(&large_json);
        assert!(result.is_err());
    }
}