lnmp_core/
builder.rs

1//! Builder pattern for constructing canonically-ordered records
2//!
3//! The `RecordBuilder` provides a fluent API for building `LnmpRecord` instances
4//! with guaranteed canonical field ordering (sorted by FID).
5//!
6//! # Example
7//!
8//! ```
9//! use lnmp_core::{RecordBuilder, LnmpField, LnmpValue};
10//!
11//! let record = RecordBuilder::new()
12//!     .add_field(LnmpField { fid: 23, value: LnmpValue::Int(3) })
13//!     .add_field(LnmpField { fid: 7, value: LnmpValue::Int(1) })
14//!     .add_field(LnmpField { fid: 12, value: LnmpValue::Int(2) })
15//!     .build();
16//!
17//! // Fields are automatically sorted by FID
18//! assert_eq!(record.fields()[0].fid, 7);
19//! assert_eq!(record.fields()[1].fid, 12);
20//! assert_eq!(record.fields()[2].fid, 23);
21//! ```
22
23use crate::{LnmpField, LnmpRecord};
24
25/// Builder for creating LnmpRecords with guaranteed canonical field ordering
26///
27/// This builder accumulates fields and sorts them by FID before creating
28/// the final record, ensuring canonical representation regardless of the
29/// order in which fields are added.
30#[derive(Debug, Clone, Default)]
31pub struct RecordBuilder {
32    fields: Vec<LnmpField>,
33}
34
35impl RecordBuilder {
36    /// Creates a new empty builder
37    ///
38    /// # Example
39    ///
40    /// ```
41    /// use lnmp_core::RecordBuilder;
42    ///
43    /// let builder = RecordBuilder::new();
44    /// ```
45    pub fn new() -> Self {
46        Self::default()
47    }
48
49    /// Adds a field to the builder
50    ///
51    /// Fields will be automatically sorted by FID when `build()` is called.
52    ///
53    /// # Example
54    ///
55    /// ```
56    /// use lnmp_core::{RecordBuilder, LnmpField, LnmpValue};
57    ///
58    /// let record = RecordBuilder::new()
59    ///     .add_field(LnmpField { fid: 10, value: LnmpValue::Int(1) })
60    ///     .build();
61    /// ```
62    pub fn add_field(mut self, field: LnmpField) -> Self {
63        self.fields.push(field);
64        self
65    }
66
67    /// Adds multiple fields at once
68    ///
69    /// # Example
70    ///
71    /// ```
72    /// use lnmp_core::{RecordBuilder, LnmpField, LnmpValue};
73    ///
74    /// let fields = vec![
75    ///     LnmpField { fid: 10, value: LnmpValue::Int(1) },
76    ///     LnmpField { fid: 20, value: LnmpValue::Int(2) },
77    /// ];
78    ///
79    /// let record = RecordBuilder::new()
80    ///     .add_fields(fields)
81    ///     .build();
82    /// ```
83    pub fn add_fields(mut self, fields: impl IntoIterator<Item = LnmpField>) -> Self {
84        self.fields.extend(fields);
85        self
86    }
87
88    /// Builds the record with fields sorted by FID
89    ///
90    /// This consumes the builder and returns an `LnmpRecord` with fields
91    /// in canonical order (sorted by field ID).
92    ///
93    /// # Example
94    ///
95    /// ```
96    /// use lnmp_core::{RecordBuilder, LnmpField, LnmpValue};
97    ///
98    /// let record = RecordBuilder::new()
99    ///     .add_field(LnmpField { fid: 23, value: LnmpValue::Int(3) })
100    ///     .add_field(LnmpField { fid: 7, value: LnmpValue::Int(1) })
101    ///     .build();
102    ///
103    /// // Fields are sorted
104    /// assert_eq!(record.fields()[0].fid, 7);
105    /// assert_eq!(record.fields()[1].fid, 23);
106    /// ```
107    pub fn build(mut self) -> LnmpRecord {
108        self.fields.sort_by_key(|f| f.fid);
109        LnmpRecord::from_sorted_fields(self.fields)
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116    use crate::LnmpValue;
117
118    #[test]
119    fn test_builder_empty() {
120        let record = RecordBuilder::new().build();
121        assert_eq!(record.fields().len(), 0);
122    }
123
124    #[test]
125    fn test_builder_single_field() {
126        let record = RecordBuilder::new()
127            .add_field(LnmpField {
128                fid: 10,
129                value: LnmpValue::Int(1),
130            })
131            .build();
132
133        assert_eq!(record.fields().len(), 1);
134        assert_eq!(record.fields()[0].fid, 10);
135    }
136
137    #[test]
138    fn test_builder_sorted_automatically() {
139        let record = RecordBuilder::new()
140            .add_field(LnmpField {
141                fid: 23,
142                value: LnmpValue::Int(3),
143            })
144            .add_field(LnmpField {
145                fid: 7,
146                value: LnmpValue::Int(1),
147            })
148            .add_field(LnmpField {
149                fid: 12,
150                value: LnmpValue::Int(2),
151            })
152            .build();
153
154        // Fields should be sorted by FID
155        assert_eq!(record.fields()[0].fid, 7);
156        assert_eq!(record.fields()[1].fid, 12);
157        assert_eq!(record.fields()[2].fid, 23);
158    }
159
160    #[test]
161    fn test_builder_add_fields_batch() {
162        let fields = vec![
163            LnmpField {
164                fid: 20,
165                value: LnmpValue::Int(2),
166            },
167            LnmpField {
168                fid: 10,
169                value: LnmpValue::Int(1),
170            },
171        ];
172
173        let record = RecordBuilder::new().add_fields(fields).build();
174
175        // Fields should be sorted
176        assert_eq!(record.fields()[0].fid, 10);
177        assert_eq!(record.fields()[1].fid, 20);
178    }
179
180    #[test]
181    fn test_builder_chaining() {
182        let record = RecordBuilder::new()
183            .add_field(LnmpField {
184                fid: 30,
185                value: LnmpValue::Int(3),
186            })
187            .add_fields(vec![
188                LnmpField {
189                    fid: 10,
190                    value: LnmpValue::Int(1),
191                },
192                LnmpField {
193                    fid: 20,
194                    value: LnmpValue::Int(2),
195                },
196            ])
197            .build();
198
199        // All fields should be sorted
200        assert_eq!(record.fields().len(), 3);
201        assert_eq!(record.fields()[0].fid, 10);
202        assert_eq!(record.fields()[1].fid, 20);
203        assert_eq!(record.fields()[2].fid, 30);
204    }
205}