use crate::encode::candidates::CandidateKind;
use crate::encode::encode_with;
use crate::error::{Error, Result};
use crate::field::FieldId;
use crate::field::FieldStore;
use crate::field::ingest::IngestReport;
#[cfg(not(feature = "package"))]
use crate::field::ingest::ingest_pdf_with;
use crate::field::ingest::with_observation_index;
#[cfg(feature = "package")]
use crate::field::ingest::{IngestOutcome, ingest_with};
#[cfg(feature = "package")]
use crate::field::ingest_package::PackageIngestReport;
use crate::integrity::{sha256, to_hex};
use crate::limits::Limits;
use crate::parallel::WorkerPool;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuildProfile {
Runtime,
}
impl BuildProfile {
pub const fn name(self) -> &'static str {
match self {
BuildProfile::Runtime => "runtime",
}
}
pub const fn candidate(self) -> CandidateKind {
match self {
BuildProfile::Runtime => CandidateKind::Raw,
}
}
pub fn parse(s: &str) -> Result<Self> {
match s {
"runtime" => Ok(BuildProfile::Runtime),
other => Err(Error::usage(format!(
"unknown --profile {other:?}; expected one of: runtime"
))),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DirectIngest {
Pdf(IngestReport),
#[cfg(feature = "package")]
Package(PackageIngestReport),
}
impl DirectIngest {
pub fn field_id(&self) -> FieldId {
match self {
DirectIngest::Pdf(r) => r.field,
#[cfg(feature = "package")]
DirectIngest::Package(r) => r.field,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DirectBuildReport {
pub profile: &'static str,
pub candidate: &'static str,
pub candidates_evaluated: u32,
pub source_sha256: String,
pub source_len: u64,
pub encoded_len: u64,
pub descriptor_sha256: String,
pub ingest: DirectIngest,
}
pub fn build_field(
source: &[u8],
store: &mut FieldStore,
limits: Limits,
profile: BuildProfile,
) -> Result<DirectBuildReport> {
build_field_with(source, store, limits, profile, None)
}
pub fn build_field_with(
source: &[u8],
store: &mut FieldStore,
limits: Limits,
profile: BuildProfile,
pool: Option<&WorkerPool>,
) -> Result<DirectBuildReport> {
let (descriptor_bytes, encode_report) = encode_with(source, limits, Some(profile.candidate()))?;
let authority = with_observation_index(&descriptor_bytes, limits)?;
drop(descriptor_bytes);
let encoded_len = authority.len() as u64;
let descriptor_sha256 = to_hex(&sha256(&authority));
let ingest = direct_ingest(store, &authority, limits, pool)?;
Ok(DirectBuildReport {
profile: profile.name(),
candidate: encode_report.kind.name(),
candidates_evaluated: encode_report.candidates_evaluated,
source_sha256: encode_report.sha256_hex,
source_len: encode_report.source_len,
encoded_len,
descriptor_sha256,
ingest,
})
}
#[cfg(feature = "package")]
fn direct_ingest(
store: &mut FieldStore,
descriptor_bytes: &[u8],
limits: Limits,
pool: Option<&WorkerPool>,
) -> Result<DirectIngest> {
match ingest_with(store, descriptor_bytes, limits, pool)? {
IngestOutcome::Package(r) => Ok(DirectIngest::Package(r)),
IngestOutcome::Pdf(r) => Ok(DirectIngest::Pdf(r)),
}
}
#[cfg(not(feature = "package"))]
fn direct_ingest(
store: &mut FieldStore,
descriptor_bytes: &[u8],
limits: Limits,
pool: Option<&WorkerPool>,
) -> Result<DirectIngest> {
Ok(DirectIngest::Pdf(ingest_pdf_with(
store,
descriptor_bytes,
limits,
pool,
)?))
}
#[cfg(test)]
mod tests {
use super::*;
fn temp_root(label: &str) -> std::path::PathBuf {
let mut p = std::env::temp_dir();
p.push(format!(
"vole-build-{label}-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::create_dir_all(&p).unwrap();
p
}
#[test]
fn runtime_profile_prices_one_candidate_and_is_exact() {
let source: Vec<u8> = b"the runtime path must not search the portfolio\n".repeat(64);
let root = temp_root("exact");
let mut store = FieldStore::open(&root).unwrap();
let report =
build_field(&source, &mut store, Limits::DEFAULT, BuildProfile::Runtime).unwrap();
store.sync().unwrap();
assert_eq!(report.profile, "runtime");
assert_eq!(report.candidate, "RAW");
assert_eq!(
report.candidates_evaluated, 1,
"a fixed profile must price exactly one candidate"
);
assert_eq!(report.source_len, source.len() as u64);
let field_id = report.ingest.field_id();
let field = crate::field::Field::open(&store, &field_id, Limits::DEFAULT).unwrap();
assert_eq!(field.materialize_exact(Limits::DEFAULT).unwrap(), source);
}
#[test]
fn stable_repeated_build_gives_identical_bytes_and_structure() {
let source = b"determinism is part of the contract".repeat(40);
let a = temp_root("det-a");
let b = temp_root("det-b");
let mut sa = FieldStore::open(&a).unwrap();
let mut sb = FieldStore::open(&b).unwrap();
let ra = build_field(&source, &mut sa, Limits::DEFAULT, BuildProfile::Runtime).unwrap();
let rb = build_field(&source, &mut sb, Limits::DEFAULT, BuildProfile::Runtime).unwrap();
assert_eq!(
ra.ingest.field_id(),
rb.ingest.field_id(),
"the same source must yield the same field id"
);
}
#[test]
fn profile_parse_rejects_unknown() {
assert_eq!(
BuildProfile::parse("runtime").unwrap(),
BuildProfile::Runtime
);
assert!(BuildProfile::parse("fastest").is_err());
}
}