Skip to main content

schema/
field.rs

1//! Field codec definitions.
2
3/// The kind of encoding for a field.
4#[derive(Debug, Clone, Copy, PartialEq)]
5pub enum FieldKind {
6    /// Boolean (1 bit).
7    Bool,
8
9    /// Unsigned integer with fixed bit width.
10    UInt {
11        /// Number of bits (1-64).
12        bits: u8,
13    },
14
15    /// Signed integer with fixed bit width.
16    SInt {
17        /// Number of bits (1-64).
18        bits: u8,
19    },
20
21    /// Variable-length unsigned integer.
22    VarUInt,
23
24    /// Variable-length signed integer (zigzag encoded).
25    VarSInt,
26
27    /// Fixed-point number with quantization.
28    FixedPoint {
29        /// Minimum value.
30        min: f32,
31        /// Maximum value.
32        max: f32,
33        /// Number of bits for encoding.
34        bits: u8,
35    },
36}
37
38/// Codec configuration for a single field.
39#[derive(Debug, Clone, PartialEq)]
40pub struct FieldCodec {
41    /// The encoding kind.
42    pub kind: FieldKind,
43
44    /// Optional change threshold for delta encoding.
45    /// If the difference is less than this threshold, the field is not sent.
46    pub threshold: Option<f32>,
47}
48
49impl FieldCodec {
50    /// Creates a boolean field codec.
51    #[must_use]
52    pub const fn bool() -> Self {
53        Self {
54            kind: FieldKind::Bool,
55            threshold: None,
56        }
57    }
58
59    /// Creates an unsigned integer field codec.
60    #[must_use]
61    pub const fn uint(bits: u8) -> Self {
62        Self {
63            kind: FieldKind::UInt { bits },
64            threshold: None,
65        }
66    }
67
68    /// Creates a signed integer field codec.
69    #[must_use]
70    pub const fn sint(bits: u8) -> Self {
71        Self {
72            kind: FieldKind::SInt { bits },
73            threshold: None,
74        }
75    }
76
77    /// Creates a variable-length unsigned integer field codec.
78    #[must_use]
79    pub const fn var_uint() -> Self {
80        Self {
81            kind: FieldKind::VarUInt,
82            threshold: None,
83        }
84    }
85
86    /// Creates a variable-length signed integer field codec.
87    #[must_use]
88    pub const fn var_sint() -> Self {
89        Self {
90            kind: FieldKind::VarSInt,
91            threshold: None,
92        }
93    }
94
95    /// Creates a fixed-point field codec.
96    #[must_use]
97    pub const fn fixed_point(min: f32, max: f32, bits: u8) -> Self {
98        Self {
99            kind: FieldKind::FixedPoint { min, max, bits },
100            threshold: None,
101        }
102    }
103
104    /// Sets the change threshold for delta encoding.
105    #[must_use]
106    pub const fn with_threshold(mut self, threshold: f32) -> Self {
107        self.threshold = Some(threshold);
108        self
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    // FieldKind tests
117    #[test]
118    fn field_kind_bool() {
119        let kind = FieldKind::Bool;
120        assert!(matches!(kind, FieldKind::Bool));
121    }
122
123    #[test]
124    fn field_kind_uint() {
125        let kind = FieldKind::UInt { bits: 16 };
126        assert!(matches!(kind, FieldKind::UInt { bits: 16 }));
127    }
128
129    #[test]
130    fn field_kind_sint() {
131        let kind = FieldKind::SInt { bits: 32 };
132        assert!(matches!(kind, FieldKind::SInt { bits: 32 }));
133    }
134
135    #[test]
136    fn field_kind_var_uint() {
137        let kind = FieldKind::VarUInt;
138        assert!(matches!(kind, FieldKind::VarUInt));
139    }
140
141    #[test]
142    fn field_kind_var_sint() {
143        let kind = FieldKind::VarSInt;
144        assert!(matches!(kind, FieldKind::VarSInt));
145    }
146
147    #[test]
148    fn field_kind_fixed_point() {
149        let kind = FieldKind::FixedPoint {
150            min: -100.0,
151            max: 100.0,
152            bits: 16,
153        };
154        match kind {
155            FieldKind::FixedPoint { min, max, bits } => {
156                assert!((min - (-100.0)).abs() < f32::EPSILON);
157                assert!((max - 100.0).abs() < f32::EPSILON);
158                assert_eq!(bits, 16);
159            }
160            _ => panic!("wrong kind"),
161        }
162    }
163
164    #[test]
165    fn field_kind_equality() {
166        let k1 = FieldKind::UInt { bits: 8 };
167        let k2 = FieldKind::UInt { bits: 8 };
168        let k3 = FieldKind::UInt { bits: 16 };
169
170        assert_eq!(k1, k2);
171        assert_ne!(k1, k3);
172    }
173
174    #[test]
175    fn field_kind_clone_copy() {
176        let kind = FieldKind::Bool;
177        let copied = kind; // Copy
178        assert_eq!(kind, copied);
179    }
180
181    // FieldCodec tests
182    #[test]
183    fn field_codec_bool() {
184        let codec = FieldCodec::bool();
185        assert!(matches!(codec.kind, FieldKind::Bool));
186        assert!(codec.threshold.is_none());
187    }
188
189    #[test]
190    fn field_codec_uint() {
191        let codec = FieldCodec::uint(16);
192        assert!(matches!(codec.kind, FieldKind::UInt { bits: 16 }));
193        assert!(codec.threshold.is_none());
194    }
195
196    #[test]
197    fn field_codec_sint() {
198        let codec = FieldCodec::sint(32);
199        assert!(matches!(codec.kind, FieldKind::SInt { bits: 32 }));
200    }
201
202    #[test]
203    fn field_codec_var_uint() {
204        let codec = FieldCodec::var_uint();
205        assert!(matches!(codec.kind, FieldKind::VarUInt));
206    }
207
208    #[test]
209    fn field_codec_var_sint() {
210        let codec = FieldCodec::var_sint();
211        assert!(matches!(codec.kind, FieldKind::VarSInt));
212    }
213
214    #[test]
215    fn field_codec_fixed_point() {
216        let codec = FieldCodec::fixed_point(-100.0, 100.0, 12);
217        match codec.kind {
218            FieldKind::FixedPoint { min, max, bits } => {
219                assert!((min - (-100.0)).abs() < f32::EPSILON);
220                assert!((max - 100.0).abs() < f32::EPSILON);
221                assert_eq!(bits, 12);
222            }
223            _ => panic!("wrong kind"),
224        }
225    }
226
227    #[test]
228    fn field_codec_with_threshold() {
229        let codec = FieldCodec::uint(8).with_threshold(0.5);
230        assert_eq!(codec.threshold, Some(0.5));
231    }
232
233    #[test]
234    fn field_codec_chained_threshold() {
235        let codec = FieldCodec::fixed_point(-100.0, 100.0, 12).with_threshold(0.1);
236        assert!(matches!(codec.kind, FieldKind::FixedPoint { bits: 12, .. }));
237        assert_eq!(codec.threshold, Some(0.1));
238    }
239
240    #[test]
241    fn field_codec_equality() {
242        let c1 = FieldCodec::uint(8);
243        let c2 = FieldCodec::uint(8);
244        let c3 = FieldCodec::uint(16);
245
246        assert_eq!(c1, c2);
247        assert_ne!(c1, c3);
248    }
249
250    #[test]
251    fn field_codec_threshold_equality() {
252        let c1 = FieldCodec::uint(8).with_threshold(0.5);
253        let c2 = FieldCodec::uint(8).with_threshold(0.5);
254        let c3 = FieldCodec::uint(8).with_threshold(1.0);
255        let c4 = FieldCodec::uint(8);
256
257        assert_eq!(c1, c2);
258        assert_ne!(c1, c3);
259        assert_ne!(c1, c4);
260    }
261
262    #[test]
263    fn field_codec_clone() {
264        let codec = FieldCodec::uint(8).with_threshold(0.5);
265        let cloned = codec.clone();
266        assert_eq!(codec, cloned);
267    }
268
269    #[test]
270    fn field_codec_const() {
271        const CODEC: FieldCodec = FieldCodec::bool();
272        assert!(matches!(CODEC.kind, FieldKind::Bool));
273    }
274}