warg-protocol 0.10.0

An implementation of the operator and package log protocols for Warg registries.
Documentation
use anyhow::Result;
use pretty_assertions::assert_eq;
use serde::{Deserialize, Serialize};
use std::{
    fs::{self, DirEntry},
    path::Path,
};
use warg_crypto::hash::Sha256;
use warg_crypto::signing;
use warg_protobuf::protocol as protobuf;
use warg_protocol::{
    package::{self, LogState},
    registry::RecordId,
    ProtoEnvelope,
};

#[test]
fn test_package_logs() {
    let mut entries: Vec<DirEntry> = fs::read_dir("./tests/package-logs")
        .unwrap()
        .collect::<Result<Vec<_>, _>>()
        .unwrap();
    entries.sort_by_key(|e| e.file_name());

    fs::create_dir_all("./tests/package-logs/output").unwrap();

    for entry in entries {
        if entry.metadata().unwrap().is_file() {
            execute_test(&entry.path());
        }
    }
}

fn validate_input(input: Vec<EnvelopeData>) -> Result<LogState> {
    input
        .into_iter()
        .scan(None, |last, e_data| {
            let key = signing::PrivateKey::decode(e_data.key.clone()).unwrap();
            let mut record: package::PackageRecord = e_data.contents.try_into().unwrap();

            record.prev.clone_from(last);

            let envelope = ProtoEnvelope::signed_contents(&key, record).unwrap();

            *last = Some(RecordId::package_record::<Sha256>(&envelope));

            Some(envelope)
        })
        .try_fold(LogState::new(), |state, record| {
            let state = state.validate(&record)?;
            Ok(state)
        })
}

fn execute_test(input_path: &Path) {
    let output_path = Path::new("./tests/package-logs/output").join(
        input_path
            .file_name()
            .expect("expected a file name for test input"),
    );
    let input: Vec<EnvelopeData> = serde_json::from_str(
        &fs::read_to_string(input_path)
            .map_err(|e| {
                format!(
                    "failed to read input file `{path}`: {e}",
                    path = input_path.display()
                )
            })
            .unwrap(),
    )
    .map_err(|e| {
        format!(
            "failed to deserialize input file `{path}`: {e}",
            path = input_path.display()
        )
    })
    .unwrap();

    let output = match validate_input(input) {
        Ok(state) => Output::Valid(state),
        Err(e) => Output::Error(e.to_string()),
    };

    if std::env::var_os("BLESS").is_some() {
        // Update the test baseline
        fs::write(&output_path, serde_json::to_string_pretty(&output).unwrap())
            .map_err(|e| {
                format!(
                    "failed to write output file `{path}`: {e}",
                    path = output_path.display()
                )
            })
            .unwrap();
    } else {
        assert_eq!(
            serde_json::from_str::<Output>(
                &fs::read_to_string(&output_path)
                    .map_err(|e| {
                        format!(
                            "failed to read output file `{path}`: {e}",
                            path = output_path.display()
                        )
                    })
                    .unwrap()
            )
            .map_err(|e| format!(
                "failed to deserialize output file `{path}`: {e}",
                path = output_path.display()
            ))
            .unwrap(),
            output
        );
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EnvelopeData {
    key: String,
    contents: protobuf::PackageRecord,
}

#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Output {
    Valid(LogState),
    Error(String),
}