molframe_core/provider/
bond_payload.rs1use super::{ChunkDescriptor, DatasetId, LocalRow, LogicalRow, ProviderError};
9use crate::{BondIndex, BondOrder, BondProvenance, BondRecord, Structure};
10use std::ops::Range;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14pub struct AtomEndpoint {
15 dataset: DatasetId,
16 row: LogicalRow,
17}
18
19impl AtomEndpoint {
20 #[must_use]
22 pub const fn new(dataset: DatasetId, row: LogicalRow) -> Self {
23 Self { dataset, row }
24 }
25
26 #[must_use]
28 pub const fn dataset(self) -> DatasetId {
29 self.dataset
30 }
31
32 #[must_use]
34 pub const fn row(self) -> LogicalRow {
35 self.row
36 }
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub struct BondChunkRecord {
42 pub atom_a: AtomEndpoint,
44 pub atom_b: AtomEndpoint,
46 pub order: BondOrder,
48 pub provenance: BondProvenance,
50}
51
52#[derive(Clone, Debug)]
54pub struct BondChunk {
55 descriptor: ChunkDescriptor,
56 structure: Structure,
57 source_range: Range<u32>,
58 atom_dataset: DatasetId,
59 atom_logical_start: LogicalRow,
60}
61
62impl BondChunk {
63 pub fn shared(
70 descriptor: ChunkDescriptor,
71 structure: Structure,
72 source_range: Range<u32>,
73 atom_dataset: DatasetId,
74 atom_logical_start: LogicalRow,
75 ) -> Result<Self, ProviderError> {
76 if !structure.data().bonds.is_available() {
77 return Err(ProviderError::BondTopologyUnavailable);
78 }
79 let actual = source_range.end.checked_sub(source_range.start).ok_or(
80 ProviderError::BondRangeUnavailable {
81 first: u64::from(source_range.start),
82 end: u64::from(source_range.end),
83 rows: structure.data().bonds.len() as u64,
84 },
85 )?;
86 if actual != descriptor.rows() {
87 return Err(ProviderError::PayloadLengthMismatch {
88 declared: descriptor.rows(),
89 actual,
90 });
91 }
92 let table_rows = u64::try_from(structure.data().bonds.len()).map_err(|_| {
93 ProviderError::IdentityOverflow {
94 dataset: descriptor.dataset(),
95 field: "bond table rows",
96 }
97 })?;
98 if u64::from(source_range.end) > table_rows {
99 return Err(ProviderError::BondRangeUnavailable {
100 first: u64::from(source_range.start),
101 end: u64::from(source_range.end),
102 rows: table_rows,
103 });
104 }
105 validate_atom_span(
106 descriptor.dataset(),
107 atom_logical_start,
108 structure.atom_count(),
109 )?;
110 Ok(Self {
111 descriptor,
112 structure,
113 source_range,
114 atom_dataset,
115 atom_logical_start,
116 })
117 }
118
119 #[must_use]
121 pub const fn descriptor(&self) -> ChunkDescriptor {
122 self.descriptor
123 }
124
125 #[must_use]
127 pub const fn atom_dataset(&self) -> DatasetId {
128 self.atom_dataset
129 }
130
131 #[must_use]
133 pub const fn structure(&self) -> &Structure {
134 &self.structure
135 }
136
137 pub fn record(&self, local: LocalRow) -> Result<BondChunkRecord, ProviderError> {
144 self.descriptor.resolve(local)?;
145 let source = self.source_range.start.checked_add(local.get()).ok_or(
146 ProviderError::IdentityOverflow {
147 dataset: self.descriptor.dataset(),
148 field: "bond source row",
149 },
150 )?;
151 let record = self
152 .structure
153 .data()
154 .bonds
155 .get(BondIndex::new(source))
156 .ok_or(ProviderError::BondRangeUnavailable {
157 first: u64::from(source),
158 end: u64::from(source) + 1,
159 rows: self.structure.data().bonds.len() as u64,
160 })?;
161 self.global_record(record)
162 }
163
164 pub fn minimum_host_bytes(&self) -> Result<u64, ProviderError> {
170 u64::from(self.descriptor.rows())
171 .checked_mul(std::mem::size_of::<BondRecord>() as u64)
172 .ok_or(ProviderError::IdentityOverflow {
173 dataset: self.descriptor.dataset(),
174 field: "bond payload bytes",
175 })
176 }
177
178 fn global_record(&self, record: BondRecord) -> Result<BondChunkRecord, ProviderError> {
179 Ok(BondChunkRecord {
180 atom_a: self.endpoint(record.atom_a.get())?,
181 atom_b: self.endpoint(record.atom_b.get())?,
182 order: record.order,
183 provenance: record.provenance,
184 })
185 }
186
187 fn endpoint(&self, atom: u32) -> Result<AtomEndpoint, ProviderError> {
188 let row = self
189 .atom_logical_start
190 .get()
191 .checked_add(u64::from(atom))
192 .ok_or(ProviderError::IdentityOverflow {
193 dataset: self.atom_dataset,
194 field: "atom endpoint",
195 })?;
196 Ok(AtomEndpoint::new(self.atom_dataset, LogicalRow::new(row)))
197 }
198}
199
200fn validate_atom_span(
201 dataset: DatasetId,
202 first: LogicalRow,
203 rows: u32,
204) -> Result<(), ProviderError> {
205 if rows == 0 {
206 return Ok(());
207 }
208 first
209 .get()
210 .checked_add(u64::from(rows - 1))
211 .map(|_| ())
212 .ok_or(ProviderError::IdentityOverflow {
213 dataset,
214 field: "atom endpoint range",
215 })
216}
217
218#[cfg(test)]
219#[path = "bond_payload_tests.rs"]
220mod tests;