lxdb_format/
relation_record.rs1use crate::{BinaryRecord, FormatError};
2pub const RELATION_RECORD_SIZE: usize = 16;
4
5#[derive(Debug, Clone, Copy, PartialEq)]
7pub struct RelationRecord {
8 id: u32,
9 source: u32,
10 target: u32,
11 weight: f32,
12}
13
14impl RelationRecord {
15 pub const SIZE: usize = RELATION_RECORD_SIZE;
16
17 pub const fn new(id: u32, source: u32, target: u32, weight: f32) -> Self {
18 Self { id, source, target, weight }
19 }
20
21 pub const fn id(self) -> u32 {
22 self.id
23 }
24
25 pub const fn source(self) -> u32 {
26 self.source
27 }
28
29 pub const fn target(self) -> u32 {
30 self.target
31 }
32
33 pub const fn weight(self) -> f32 {
34 self.weight
35 }
36
37 pub fn encode(self) -> [u8; RELATION_RECORD_SIZE] {
38 let mut bytes = [0_u8; RELATION_RECORD_SIZE];
39
40 bytes[0..4].copy_from_slice(&self.id.to_le_bytes());
41 bytes[4..8].copy_from_slice(&self.source.to_le_bytes());
42 bytes[8..12].copy_from_slice(&self.target.to_le_bytes());
43 bytes[12..16].copy_from_slice(&self.weight.to_le_bytes());
44
45 bytes
46 }
47
48 pub fn decode(bytes: &[u8]) -> Result<Self, FormatError> {
49 if bytes.len() != Self::SIZE {
50 return Err(FormatError::UnexpectedRecordSize {
51 record: "relation record",
52 expected: Self::SIZE,
53 found: bytes.len(),
54 });
55 }
56
57 let id =
58 u32::from_le_bytes(bytes[0..4].try_into().expect("relation id must occupy four bytes"));
59
60 let source = u32::from_le_bytes(
61 bytes[4..8].try_into().expect("relation source must occupy four bytes"),
62 );
63
64 let target = u32::from_le_bytes(
65 bytes[8..12].try_into().expect("relation target must occupy four bytes"),
66 );
67
68 let weight_bits = u32::from_le_bytes(
69 bytes[12..16].try_into().expect("relation weight must occupy four bytes"),
70 );
71
72 let weight = f32::from_bits(weight_bits);
73
74 Ok(Self::new(id, source, target, weight))
75 }
76}
77
78impl BinaryRecord for RelationRecord {
79 const SIZE: usize = RelationRecord::SIZE;
80
81 fn decode(bytes: &[u8]) -> Result<Self, FormatError> {
82 RelationRecord::decode(bytes)
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::{RELATION_RECORD_SIZE, RelationRecord};
89
90 #[test]
91 fn encodes_relation_record() {
92 let record = RelationRecord::new(12, 3, 9, 0.85);
93
94 let bytes = record.encode();
95
96 assert_eq!(bytes.len(), RELATION_RECORD_SIZE);
97
98 assert_eq!(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3],]), 12);
99
100 assert_eq!(u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7],]), 3);
101
102 assert_eq!(u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11],]), 9);
103
104 assert_eq!(f32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15],]), 0.85);
105 }
106
107 #[test]
108 fn preserves_weight_bits() {
109 let weight = 0.333_333_34_f32;
110 let record = RelationRecord::new(0, 1, 2, weight);
111
112 let bytes = record.encode();
113
114 let decoded = f32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
115
116 assert_eq!(decoded.to_bits(), weight.to_bits());
117 }
118
119 #[test]
120 fn exposes_relation_fields() {
121 let record = RelationRecord::new(7, 11, 13, 1.0);
122
123 assert_eq!(record.id(), 7);
124 assert_eq!(record.source(), 11);
125 assert_eq!(record.target(), 13);
126 assert_eq!(record.weight(), 1.0);
127 }
128
129 #[test]
130 fn decodes_relation_record() {
131 let original = RelationRecord::new(7, 4, 9, 0.75);
132
133 let encoded = original.encode();
134
135 let decoded =
136 RelationRecord::decode(&encoded).expect("encoded relation record should decode");
137
138 assert_eq!(decoded.id(), original.id());
139 assert_eq!(decoded.source(), original.source());
140 assert_eq!(decoded.target(), original.target());
141
142 assert_eq!(decoded.weight().to_bits(), original.weight().to_bits(),);
143 }
144
145 #[test]
146 fn rejects_invalid_relation_record_size() {
147 let bytes = [0_u8; RelationRecord::SIZE + 1];
148
149 let error =
150 RelationRecord::decode(&bytes).expect_err("oversized relation record should fail");
151
152 assert!(matches!(
153 error,
154 crate::FormatError::UnexpectedRecordSize {
155 record: "relation record",
156 expected: RelationRecord::SIZE,
157 found,
158 } if found == RelationRecord::SIZE + 1
159 ));
160 }
161}