use std::collections::BTreeMap;
use serde_json::Value;
#[derive(Debug)]
pub enum CaptureError {
InvalidJson(serde_json::Error),
MissingSection(&'static str),
}
impl std::fmt::Display for CaptureError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidJson(e) => write!(f, "fingerprint JSON is invalid: {e}"),
Self::MissingSection(key) => write!(f, "fingerprint is missing section: {key}"),
}
}
}
impl std::error::Error for CaptureError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::InvalidJson(e) => Some(e),
Self::MissingSection(_) => None,
}
}
}
#[derive(Debug, Clone)]
pub struct BuildTimeFingerprintData {
pub package: PackageInfo,
pub build: BuildInfo,
pub cargo_lock_sha256: String,
pub deps: Value,
pub source: BTreeMap<String, String>,
}
#[derive(Debug, Clone)]
pub struct PackageInfo {
pub name: String,
pub version: String,
}
#[derive(Debug, Clone)]
pub struct BuildInfo {
pub features: Vec<String>,
pub opt_level: String,
pub profile: String,
pub rustc_version: String,
pub target: String,
}
pub fn parse(json: &str) -> Result<BuildTimeFingerprintData, CaptureError> {
let root: Value = serde_json::from_str(json)
.map_err(CaptureError::InvalidJson)?;
let obj = root.as_object()
.ok_or(CaptureError::MissingSection("root (expected JSON object)"))? ;
let pkg = obj.get("package")
.and_then(|v| v.as_object())
.ok_or(CaptureError::MissingSection("package"))?;
let package = PackageInfo {
name: pkg.get("name").and_then(|v| v.as_str()).unwrap_or("").to_owned(),
version: pkg.get("version").and_then(|v| v.as_str()).unwrap_or("").to_owned(),
};
let bld = obj.get("build")
.and_then(|v| v.as_object())
.ok_or(CaptureError::MissingSection("build"))?;
let features = bld.get("features")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str())
.map(str::to_owned)
.collect()
})
.unwrap_or_default();
let build = BuildInfo {
features,
opt_level: bld.get("opt_level").and_then(|v| v.as_str()).unwrap_or("").to_owned(),
profile: bld.get("profile").and_then(|v| v.as_str()).unwrap_or("").to_owned(),
rustc_version: bld.get("rustc_version").and_then(|v| v.as_str()).unwrap_or("").to_owned(),
target: bld.get("target").and_then(|v| v.as_str()).unwrap_or("").to_owned(),
};
let cargo_lock_sha256 = obj.get("cargo_lock_sha256")
.and_then(|v| v.as_str())
.ok_or(CaptureError::MissingSection("cargo_lock_sha256"))?
.to_owned();
let deps = obj.get("deps")
.ok_or(CaptureError::MissingSection("deps"))?
.clone();
let source = obj.get("source")
.and_then(|v| v.as_object())
.map(|map| {
map.iter()
.map(|(k, v)| (k.clone(), v.as_str().unwrap_or("").to_owned()))
.collect()
})
.unwrap_or_default();
Ok(BuildTimeFingerprintData { package, build, cargo_lock_sha256, deps, source })
}
#[cfg(feature = "dependency-graph-capture")]
pub use toolkit_zero_macros::dependencies;