trazaeo 0.5.0

Open-source provenance SDK and specification for verifiable EO and climate data workflows
Documentation
use crate::bootstrap::ProvenanceStartMode;
use time::format_description::well_known::Rfc3339;
use time::OffsetDateTime;

pub type ValidationErrors = Vec<String>;
pub type ValidationResult = Result<(), ValidationErrors>;

pub fn push_if_blank(errors: &mut ValidationErrors, field_name: &str, value: &str) {
    if value.trim().is_empty() {
        errors.push(format!("{field_name} must not be empty"));
    }
}

pub fn push_if_empty_vec<T>(errors: &mut ValidationErrors, field_name: &str, values: &[T]) {
    if values.is_empty() {
        errors.push(format!("{field_name} must not be empty"));
    }
}

pub fn validate_rfc3339(errors: &mut ValidationErrors, field_name: &str, value: &str) {
    if OffsetDateTime::parse(value, &Rfc3339).is_err() {
        errors.push(format!("{field_name} must be valid RFC3339"));
    }
}

pub fn validate_ref_hash_pair(
    errors: &mut ValidationErrors,
    ref_name: &str,
    hash_name: &str,
    ref_value: &Option<String>,
    hash_value: &Option<String>,
) {
    let has_ref = ref_value.as_ref().is_some_and(|v| !v.trim().is_empty());
    let has_hash = hash_value.as_ref().is_some_and(|v| !v.trim().is_empty());
    if has_ref ^ has_hash {
        errors.push(format!("{ref_name} and {hash_name} must be set together"));
    }
}

pub fn push_if_blank_option(
    errors: &mut ValidationErrors,
    field_name: &str,
    value: &Option<String>,
) {
    if value.as_ref().is_none_or(|v| v.trim().is_empty()) {
        errors.push(format!("{field_name} must not be empty"));
    }
}

pub fn validate_start_mode(errors: &mut ValidationErrors, value: &str) {
    if ProvenanceStartMode::parse(value).is_none() {
        errors.push(
            "provenance_start_mode must be one of: source_capture, transport_capture, dataset_bootstrap"
                .to_string(),
        );
    }
}

pub fn validate_capture_role(errors: &mut ValidationErrors, value: &str) {
    if !matches!(value, "source" | "transport") {
        errors.push("capture_role must be one of: source, transport".to_string());
    }
}

pub fn canonical_signed_bytes<T: serde::Serialize>(value: &T) -> Vec<u8> {
    serde_json::to_vec(value).expect("signed envelope serialization should succeed")
}