ssvc 0.3.0

Implementation of the SSVC specification in Rust
use std::fs;
use std::path::Path;

use anyhow::Result;

static GENERATED_CODE_HEADER: &str = "
 * This file is automatically generated by build.rs.
 * Do not edit manually!
 * Re-generate with: cargo build
 ";

/// Adds a documentation header to a generated file indicating it was auto-generated
pub fn add_generated_code_header(file: &mut syn::File) {
    let doc_attr: syn::Attribute = syn::parse_quote! { #![doc = #GENERATED_CODE_HEADER] };
    file.attrs.push(doc_attr);
}

/// Adds an attribute to skip rustfmt formatting on the file
pub fn add_ignore_rustfmt(file: &mut syn::File) {
    let doc_attr = syn::parse_quote! { #![cfg_attr(any(), rustfmt::skip)] };
    file.attrs.push(doc_attr);
}

/// Adds an attribute to disable all clippy lints on the file
pub fn add_ignore_clippy(file: &mut syn::File) {
    let doc_attr = syn::parse_quote! { #![allow(clippy::all)] };
    file.attrs.push(doc_attr);
}

/// Validates a JSON file against a given schema and returns the parsed JSON value
///
/// # Arguments
/// * `path` - Path to the JSON file to validate
/// * `validator` - The JSON schema validator
/// * `schema_name` - Name of the schema (used for error messages)
///
/// # Returns
/// The parsed JSON value if validation succeeds
///
/// # Errors
/// Returns an error if the file cannot be read, parsed, or fails schema validation
pub fn validate_json_file(
    path: &Path,
    validator: &jsonschema::Validator,
    schema_name: &str,
) -> Result<serde_json::Value> {
    let content = std::fs::read_to_string(path)?;
    let json: serde_json::Value = serde_json::from_str(&content)
        .map_err(|e| anyhow::anyhow!("Failed to parse JSON syntax in {}: {}", path.display(), e))?;

    if let Err(e) = validator.validate(&json) {
        return Err(anyhow::anyhow!(
            "Failed to validate {} against {} schema:\n{}",
            path.display(),
            schema_name,
            e
        ));
    }

    Ok(json)
}

/// Recursively walks through a directory and processes all JSON files using the provided closure.
///
/// # Arguments
/// * `dir` - Root directory to search for JSON files
/// * `process` - Closure that processes each JSON file found
///
/// Returns an error if directory reading fails or if the process closure returns an error.
pub fn walk_json_files(dir: &Path, process: &mut dyn FnMut(&Path) -> Result<()>) -> Result<()> {
    println!("cargo:rerun-if-changed={}", dir.display());
    for entry in fs::read_dir(dir)? {
        let path = entry?.path();
        if path.is_dir() {
            walk_json_files(&path, process)?;
        } else if path.extension().is_some_and(|ext| ext == "json") {
            process(&path)?;
        }
    }
    Ok(())
}