use super::source::validate_atom_endpoint_range;
use super::{ChunkDescriptor, DatasetId, LocalRow, LogicalRow, ProviderError};
use crate::optional::OptionalSymbol;
use crate::{BondIndex, BondOrder, BondProvenance, BondRecord, Structure};
use std::ops::Range;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct AtomEndpoint {
dataset: DatasetId,
row: LogicalRow,
}
impl AtomEndpoint {
#[must_use]
pub const fn new(dataset: DatasetId, row: LogicalRow) -> Self {
Self { dataset, row }
}
#[must_use]
pub const fn dataset(self) -> DatasetId {
self.dataset
}
#[must_use]
pub const fn row(self) -> LogicalRow {
self.row
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BondChunkRecord {
pub atom_a: AtomEndpoint,
pub atom_b: AtomEndpoint,
pub order: BondOrder,
pub provenance: BondProvenance,
pub kind: OptionalSymbol,
}
#[derive(Clone, Debug)]
pub struct BondChunk {
descriptor: ChunkDescriptor,
structure: Structure,
source_range: Range<u32>,
atom_dataset: DatasetId,
atom_logical_start: LogicalRow,
}
impl BondChunk {
pub fn shared(
descriptor: ChunkDescriptor,
structure: Structure,
source_range: Range<u32>,
atom_dataset: DatasetId,
atom_logical_start: LogicalRow,
) -> Result<Self, ProviderError> {
if !structure.data().bonds.is_available() {
return Err(ProviderError::BondTopologyUnavailable);
}
let actual = source_range.end.checked_sub(source_range.start).ok_or(
ProviderError::BondRangeUnavailable {
first: u64::from(source_range.start),
end: u64::from(source_range.end),
rows: structure.data().bonds.len() as u64,
},
)?;
if actual != descriptor.rows() {
return Err(ProviderError::PayloadLengthMismatch {
declared: descriptor.rows(),
actual,
});
}
let table_rows = u64::try_from(structure.data().bonds.len()).map_err(|_| {
ProviderError::IdentityOverflow {
dataset: descriptor.dataset(),
field: "bond table rows",
}
})?;
if u64::from(source_range.end) > table_rows {
return Err(ProviderError::BondRangeUnavailable {
first: u64::from(source_range.start),
end: u64::from(source_range.end),
rows: table_rows,
});
}
validate_atom_endpoint_range(
descriptor.dataset(),
atom_logical_start,
structure.atom_count(),
)?;
Ok(Self {
descriptor,
structure,
source_range,
atom_dataset,
atom_logical_start,
})
}
#[must_use]
pub const fn descriptor(&self) -> ChunkDescriptor {
self.descriptor
}
#[must_use]
pub const fn atom_dataset(&self) -> DatasetId {
self.atom_dataset
}
#[must_use]
pub const fn structure(&self) -> &Structure {
&self.structure
}
pub fn record(&self, local: LocalRow) -> Result<BondChunkRecord, ProviderError> {
self.descriptor.resolve(local)?;
let source = self.source_range.start.checked_add(local.get()).ok_or(
ProviderError::IdentityOverflow {
dataset: self.descriptor.dataset(),
field: "bond source row",
},
)?;
let record = self
.structure
.data()
.bonds
.get(BondIndex::new(source))
.ok_or(ProviderError::BondRangeUnavailable {
first: u64::from(source),
end: u64::from(source) + 1,
rows: self.structure.data().bonds.len() as u64,
})?;
self.global_record(record)
}
pub fn minimum_host_bytes(&self) -> Result<u64, ProviderError> {
u64::from(self.descriptor.rows())
.checked_mul(std::mem::size_of::<BondRecord>() as u64)
.ok_or(ProviderError::IdentityOverflow {
dataset: self.descriptor.dataset(),
field: "bond payload bytes",
})
}
fn global_record(&self, record: BondRecord) -> Result<BondChunkRecord, ProviderError> {
Ok(BondChunkRecord {
atom_a: self.endpoint(record.atom_a.get())?,
atom_b: self.endpoint(record.atom_b.get())?,
order: record.order,
provenance: record.provenance,
kind: record.kind,
})
}
fn endpoint(&self, atom: u32) -> Result<AtomEndpoint, ProviderError> {
let row = self
.atom_logical_start
.get()
.checked_add(u64::from(atom))
.ok_or(ProviderError::IdentityOverflow {
dataset: self.atom_dataset,
field: "atom endpoint",
})?;
Ok(AtomEndpoint::new(self.atom_dataset, LogicalRow::new(row)))
}
}
#[cfg(test)]
#[path = "bond_payload_tests.rs"]
mod tests;