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}