use kmp_domain::{NodeBodyDescriptor, NodeDetailProjection, PortError};
use sha2::{Digest, Sha256};
use super::detail_header;
use super::engine::{Key, ReadTx, Table};
use super::serdes::{DetailRecord, decode};
pub(super) fn read_batch(
tx: &dyn ReadTx,
node_ids: &[String],
) -> Result<Vec<Option<NodeBodyDescriptor>>, PortError> {
Ok(detail_header::read_batch(tx, node_ids)?
.into_iter()
.map(|header| {
header.map(|header| NodeBodyDescriptor {
node_id: header.node_id,
revision: header.revision,
content_hash: header.content_hash,
record_bytes: header.record_bytes,
body_bytes: header.body_bytes,
record_digest: header.record_digest,
})
})
.collect())
}
pub(super) fn read_one(
tx: &dyn ReadTx,
node_id: &str,
) -> Result<Option<NodeBodyDescriptor>, PortError> {
Ok(read_batch(tx, std::slice::from_ref(&node_id.to_string()))?
.pop()
.flatten())
}
pub(super) fn read_verified_bodies(
tx: &dyn ReadTx,
node_ids: &[String],
) -> Result<Vec<Option<NodeDetailProjection>>, PortError> {
node_ids
.iter()
.map(|id| {
let Some(raw) = tx.get(Table::Details, Key::Str(id))? else {
return Ok(None);
};
let expected = read_one(tx, id)?.ok_or_else(|| {
PortError::InvalidState(format!(
"embedded store: `{id}` has a stored body and no descriptor; this \
projection is inconsistent and the body is not delivered"
))
})?;
let actual = format!("sha256:{:x}", Sha256::digest(&raw));
if actual != expected.record_digest {
return Err(PortError::InvalidState(format!(
"embedded store: the body record of `{id}` does not match the digest its \
descriptor records ({} stored, {actual} read); it is not delivered under \
an identity it does not have",
expected.record_digest
)));
}
Ok(Some(decode::<DetailRecord>("node detail", &raw)?.into()))
})
.collect()
}