use jsonschema::Validator;
use sha2::{Digest, Sha256};
use crate::presets::types::Preset;
#[derive(Debug, thiserror::Error)]
pub enum LoadError {
#[error("preset {path}: failed to parse JSON: {source}")]
#[cfg_attr(alef, alef(error_code = 1200))]
Parse {
path: String,
#[source]
#[cfg_attr(alef, alef(skip))]
source: serde_json::Error,
},
#[error("preset {path}: failed meta-schema validation: {errors}")]
#[cfg_attr(alef, alef(error_code = 1201))]
SchemaValidation {
path: String,
errors: String,
},
#[error("preset {path}: failed to deserialize after validation: {source}")]
#[cfg_attr(alef, alef(error_code = 1202))]
Deserialize {
path: String,
#[source]
#[cfg_attr(alef, alef(skip))]
source: serde_json::Error,
},
#[error("preset {path}: id `{declared}` must match file path stem `{expected}`")]
#[cfg_attr(alef, alef(error_code = 1203))]
IdMismatch {
path: String,
declared: String,
expected: String,
},
#[error("meta-schema is invalid: {0}")]
#[cfg_attr(alef, alef(error_code = 1204))]
BadMetaSchema(String),
#[error("I/O error reading preset directory: {0}")]
#[cfg_attr(alef, alef(error_code = 1205))]
Io(
#[from]
#[cfg_attr(alef, alef(skip))]
std::io::Error,
),
}
pub struct MetaSchema {
validator: Validator,
}
impl MetaSchema {
pub fn compile(meta_schema_json: &str) -> Result<Self, LoadError> {
let schema: serde_json::Value =
serde_json::from_str(meta_schema_json).map_err(|e| LoadError::BadMetaSchema(format!("parse: {e}")))?;
let validator =
jsonschema::draft202012::new(&schema).map_err(|e| LoadError::BadMetaSchema(format!("compile: {e}")))?;
Ok(Self { validator })
}
pub fn parse_preset(&self, path: &str, raw: &[u8]) -> Result<Preset, LoadError> {
let value: serde_json::Value = serde_json::from_slice(raw).map_err(|source| LoadError::Parse {
path: path.to_string(),
source,
})?;
let issues: Vec<String> = self
.validator
.iter_errors(&value)
.map(|e| format!("- {} at {}", e, e.instance_path()))
.collect();
if !issues.is_empty() {
return Err(LoadError::SchemaValidation {
path: path.to_string(),
errors: issues.join("\n"),
});
}
let mut preset: Preset = serde_json::from_value(value).map_err(|source| LoadError::Deserialize {
path: path.to_string(),
source,
})?;
preset.fingerprint = fingerprint(raw);
Ok(preset)
}
}
pub fn fingerprint(raw: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(raw);
let digest = hasher.finalize();
let mut hex = String::with_capacity(7 + digest.len() * 2);
hex.push_str("sha256:");
for byte in digest.iter() {
use std::fmt::Write;
let _ = write!(&mut hex, "{byte:02x}");
}
hex
}