ssvc 0.2.0

Implementation of the SSVC specification in Rust
use std::fs;
use std::path::Path;
use typify::{TypeSpace, TypeSpaceSettings};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("cargo:rerun-if-changed=build.rs");

    // Tell Cargo to rerun this build script if the schema file changes
    let schema_targets = [
        (
            "assets/SelectionList_2_0_0.schema.json",
            "src/generated/ssvc/selection_list.rs",
        ),
        (
            "assets/DecisionPoint_2_0_0.schema.json",
            "src/generated/ssvc/decision_point.rs",
        ),
    ];

    for (file_path, target_folder) in schema_targets {
        println!("cargo:rerun-if-changed={file_path}");
        build_from_schema(file_path, target_folder)?;
    }

    Ok(())
}

fn build_from_schema(file_path: &str, target_path: &str) -> Result<(), Box<dyn std::error::Error>> {
    // Open the file and deserialize the JSON schema
    let file = std::fs::File::open(file_path)?;
    let schema = serde_json::from_reader(file)?;

    let mut type_space = TypeSpace::new(
        TypeSpaceSettings::default()
            .with_struct_builder(true)
            .with_derive("PartialEq".into())
            .with_derive("Eq".into()),
    );
    type_space.add_root_schema(schema)?;

    let mut syn_file = syn::parse2::<syn::File>(type_space.to_stream())?;
    add_generated_code_header(&mut syn_file);
    add_ignore_rustfmt(&mut syn_file);
    add_ignore_clippy(&mut syn_file);

    let content = prettyplease::unparse(&syn_file);

    let out_file = Path::new(&target_path).to_path_buf();
    if let Some(parent) = out_file.parent() {
        fs::create_dir_all(parent)?;
    }
    fs::write(out_file, content)?;

    Ok(())
}

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);
}

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

fn add_ignore_rustfmt(file: &mut syn::File) {
    let doc_attr = syn::parse_quote! { #![cfg_attr(any(), rustfmt::skip)] };
    file.attrs.push(doc_attr);
}

fn add_ignore_clippy(file: &mut syn::File) {
    let doc_attr = syn::parse_quote! { #![allow(clippy::all)] };
    file.attrs.push(doc_attr);
}