use anyhow::{Context, Result, bail, ensure};
use rspyts_core::envelope;
use rspyts_core::ir::Manifest;
use std::path::Path;
pub struct LoadedManifest {
pub manifest: Manifest,
pub json: Vec<u8>,
}
type AbiVersionFn = unsafe extern "C" fn() -> u32;
type ManifestFn = unsafe extern "C" fn() -> *mut u8;
type FreeFn = unsafe extern "C" fn(*mut u8, usize);
pub fn load_manifest(path: &Path) -> Result<LoadedManifest> {
unsafe {
let lib = libloading::Library::new(path)
.with_context(|| format!("cannot load `{}`", path.display()))?;
let abi_version: libloading::Symbol<AbiVersionFn> =
lib.get(b"rspyts_abi_version\0").context(
"module does not export `rspyts_abi_version` — is `rspyts::export!()` present?",
)?;
let version = abi_version();
ensure!(
version == rspyts_core::ABI_VERSION,
"module reports ABI version {version}; this rspyts CLI supports version {}",
rspyts_core::ABI_VERSION
);
let manifest_fn: libloading::Symbol<ManifestFn> = lib
.get(b"rspyts_manifest\0")
.context("module does not export `rspyts_manifest`")?;
let free_fn: libloading::Symbol<FreeFn> = lib
.get(b"rspyts_free\0")
.context("module does not export `rspyts_free`")?;
let ptr = manifest_fn();
ensure!(!ptr.is_null(), "`rspyts_manifest` returned a null envelope");
let total = envelope::total_len(ptr);
let decoded = envelope::decode(ptr);
let status = decoded.status;
let json = decoded.json.to_vec();
free_fn(ptr, total);
if status != envelope::STATUS_OK {
let payload = String::from_utf8_lossy(&json).into_owned();
bail!("`rspyts_manifest` failed (status {status}): {payload}");
}
let manifest: Manifest = serde_json::from_slice(&json)
.context("cannot deserialize the module manifest — CLI/crate version mismatch?")?;
Ok(LoadedManifest { manifest, json })
}
}