Skip to main content

lxdb_format/
relation_record.rs

1use crate::{BinaryRecord, FormatError};
2/// Number of bytes occupied by an encoded relation record.
3pub const RELATION_RECORD_SIZE: usize = 16;
4
5/// Binary representation of a semantic relation.
6#[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}