molframe_core/provider/
bond_payload.rs1use super::source::validate_atom_endpoint_range;
9use super::{ChunkDescriptor, DatasetId, LocalRow, LogicalRow, ProviderError};
10use crate::optional::OptionalSymbol;
11use crate::{BondIndex, BondOrder, BondProvenance, BondRecord, Structure};
12use std::ops::Range;
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
16pub struct AtomEndpoint {
17 dataset: DatasetId,
18 row: LogicalRow,
19}
20
21impl AtomEndpoint {
22 #[must_use]
24 pub const fn new(dataset: DatasetId, row: LogicalRow) -> Self {
25 Self { dataset, row }
26 }
27
28 #[must_use]
30 pub const fn dataset(self) -> DatasetId {
31 self.dataset
32 }
33
34 #[must_use]
36 pub const fn row(self) -> LogicalRow {
37 self.row
38 }
39}
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
43pub struct BondChunkRecord {
44 pub atom_a: AtomEndpoint,
46 pub atom_b: AtomEndpoint,
48 pub order: BondOrder,
50 pub provenance: BondProvenance,
52 pub kind: OptionalSymbol,
60}
61
62#[derive(Clone, Debug)]
64pub struct BondChunk {
65 descriptor: ChunkDescriptor,
66 structure: Structure,
67 source_range: Range<u32>,
68 atom_dataset: DatasetId,
69 atom_logical_start: LogicalRow,
70}
71
72impl BondChunk {
73 pub fn shared(
80 descriptor: ChunkDescriptor,
81 structure: Structure,
82 source_range: Range<u32>,
83 atom_dataset: DatasetId,
84 atom_logical_start: LogicalRow,
85 ) -> Result<Self, ProviderError> {
86 if !structure.data().bonds.is_available() {
87 return Err(ProviderError::BondTopologyUnavailable);
88 }
89 let actual = source_range.end.checked_sub(source_range.start).ok_or(
90 ProviderError::BondRangeUnavailable {
91 first: u64::from(source_range.start),
92 end: u64::from(source_range.end),
93 rows: structure.data().bonds.len() as u64,
94 },
95 )?;
96 if actual != descriptor.rows() {
97 return Err(ProviderError::PayloadLengthMismatch {
98 declared: descriptor.rows(),
99 actual,
100 });
101 }
102 let table_rows = u64::try_from(structure.data().bonds.len()).map_err(|_| {
103 ProviderError::IdentityOverflow {
104 dataset: descriptor.dataset(),
105 field: "bond table rows",
106 }
107 })?;
108 if u64::from(source_range.end) > table_rows {
109 return Err(ProviderError::BondRangeUnavailable {
110 first: u64::from(source_range.start),
111 end: u64::from(source_range.end),
112 rows: table_rows,
113 });
114 }
115 validate_atom_endpoint_range(
116 descriptor.dataset(),
117 atom_logical_start,
118 structure.atom_count(),
119 )?;
120 Ok(Self {
121 descriptor,
122 structure,
123 source_range,
124 atom_dataset,
125 atom_logical_start,
126 })
127 }
128
129 #[must_use]
131 pub const fn descriptor(&self) -> ChunkDescriptor {
132 self.descriptor
133 }
134
135 #[must_use]
137 pub const fn atom_dataset(&self) -> DatasetId {
138 self.atom_dataset
139 }
140
141 #[must_use]
143 pub const fn structure(&self) -> &Structure {
144 &self.structure
145 }
146
147 pub fn record(&self, local: LocalRow) -> Result<BondChunkRecord, ProviderError> {
154 self.descriptor.resolve(local)?;
155 let source = self.source_range.start.checked_add(local.get()).ok_or(
156 ProviderError::IdentityOverflow {
157 dataset: self.descriptor.dataset(),
158 field: "bond source row",
159 },
160 )?;
161 let record = self
162 .structure
163 .data()
164 .bonds
165 .get(BondIndex::new(source))
166 .ok_or(ProviderError::BondRangeUnavailable {
167 first: u64::from(source),
168 end: u64::from(source) + 1,
169 rows: self.structure.data().bonds.len() as u64,
170 })?;
171 self.global_record(record)
172 }
173
174 pub fn minimum_host_bytes(&self) -> Result<u64, ProviderError> {
180 u64::from(self.descriptor.rows())
181 .checked_mul(std::mem::size_of::<BondRecord>() as u64)
182 .ok_or(ProviderError::IdentityOverflow {
183 dataset: self.descriptor.dataset(),
184 field: "bond payload bytes",
185 })
186 }
187
188 fn global_record(&self, record: BondRecord) -> Result<BondChunkRecord, ProviderError> {
189 Ok(BondChunkRecord {
190 atom_a: self.endpoint(record.atom_a.get())?,
191 atom_b: self.endpoint(record.atom_b.get())?,
192 order: record.order,
193 provenance: record.provenance,
194 kind: record.kind,
195 })
196 }
197
198 fn endpoint(&self, atom: u32) -> Result<AtomEndpoint, ProviderError> {
199 let row = self
200 .atom_logical_start
201 .get()
202 .checked_add(u64::from(atom))
203 .ok_or(ProviderError::IdentityOverflow {
204 dataset: self.atom_dataset,
205 field: "atom endpoint",
206 })?;
207 Ok(AtomEndpoint::new(self.atom_dataset, LogicalRow::new(row)))
208 }
209}
210
211#[cfg(test)]
212#[path = "bond_payload_tests.rs"]
213mod tests;