ngdp_bpsv/
document.rs

1//! BPSV document representation
2
3use crate::error::{Error, Result};
4use crate::schema::BpsvSchema;
5use crate::value::BpsvValue;
6use std::collections::HashMap;
7
8/// A single row in a BPSV document with borrowed data
9#[derive(Debug, Clone, PartialEq)]
10pub struct BpsvRow<'a> {
11    /// Raw string values as they appear in the BPSV (borrowed)
12    raw_values: Vec<&'a str>,
13    /// Typed values (lazy-loaded)
14    typed_values: Option<Vec<BpsvValue>>,
15}
16
17impl<'a> BpsvRow<'a> {
18    /// Create a new row from raw string slices
19    pub fn new(values: Vec<&'a str>) -> Self {
20        Self {
21            raw_values: values,
22            typed_values: None,
23        }
24    }
25
26    /// Create a new row from typed values
27    pub fn from_typed_values(values: Vec<BpsvValue>) -> BpsvRow<'static> {
28        // For typed values, we need to allocate since we're creating new data
29        BpsvRow {
30            raw_values: vec![],
31            typed_values: Some(values),
32        }
33    }
34
35    /// Get the number of values in this row
36    pub fn len(&self) -> usize {
37        if let Some(typed) = &self.typed_values {
38            typed.len()
39        } else {
40            self.raw_values.len()
41        }
42    }
43
44    /// Check if the row is empty
45    pub fn is_empty(&self) -> bool {
46        self.len() == 0
47    }
48
49    /// Get a raw string value by index
50    pub fn get_raw(&self, index: usize) -> Option<&str> {
51        self.raw_values.get(index).copied()
52    }
53
54    /// Get a raw string value by field name using the schema
55    pub fn get_raw_by_name(&self, field_name: &str, schema: &BpsvSchema) -> Option<&str> {
56        schema
57            .get_field(field_name)
58            .and_then(|field| self.get_raw(field.index))
59    }
60
61    /// Get all raw values
62    pub fn raw_values(&self) -> &[&'a str] {
63        &self.raw_values
64    }
65
66    /// Parse and get typed values using the schema
67    pub fn get_typed_values(&mut self, schema: &BpsvSchema) -> Result<&[BpsvValue]> {
68        if self.typed_values.is_none() {
69            if self.raw_values.len() != schema.field_count() {
70                return Err(Error::SchemaMismatch {
71                    expected: schema.field_count(),
72                    actual: self.raw_values.len(),
73                });
74            }
75
76            let mut typed = Vec::new();
77            for (value, field) in self.raw_values.iter().zip(schema.fields()) {
78                let typed_value = BpsvValue::parse(value, &field.field_type)?;
79                typed.push(typed_value);
80            }
81            self.typed_values = Some(typed);
82        }
83
84        Ok(self.typed_values.as_ref().unwrap())
85    }
86
87    /// Get a typed value by index
88    pub fn get_typed(&mut self, index: usize, schema: &BpsvSchema) -> Result<Option<&BpsvValue>> {
89        let typed_values = self.get_typed_values(schema)?;
90        Ok(typed_values.get(index))
91    }
92
93    /// Get a typed value by field name
94    pub fn get_typed_by_name(
95        &mut self,
96        field_name: &str,
97        schema: &BpsvSchema,
98    ) -> Result<Option<&BpsvValue>> {
99        if let Some(field) = schema.get_field(field_name) {
100            self.get_typed(field.index, schema)
101        } else {
102            Err(Error::FieldNotFound {
103                field: field_name.to_string(),
104            })
105        }
106    }
107
108    /// Convert row to a map of field names to raw values
109    pub fn to_map(&self, schema: &BpsvSchema) -> Result<HashMap<String, String>> {
110        if self.raw_values.len() != schema.field_count() {
111            return Err(Error::SchemaMismatch {
112                expected: schema.field_count(),
113                actual: self.raw_values.len(),
114            });
115        }
116
117        let mut map = HashMap::new();
118        for (field, value) in schema.fields().iter().zip(self.raw_values.iter()) {
119            map.insert(field.name.clone(), value.to_string());
120        }
121        Ok(map)
122    }
123
124    /// Convert row to a map of field names to typed values
125    pub fn to_typed_map(&mut self, schema: &BpsvSchema) -> Result<HashMap<String, BpsvValue>> {
126        let typed_values = self.get_typed_values(schema)?;
127        let mut map = HashMap::new();
128
129        for (field, value) in schema.fields().iter().zip(typed_values.iter()) {
130            map.insert(field.name.clone(), value.clone());
131        }
132        Ok(map)
133    }
134
135    /// Convert to BPSV line format
136    pub fn to_bpsv_line(&self) -> String {
137        if let Some(typed) = &self.typed_values {
138            typed
139                .iter()
140                .map(|v| v.to_bpsv_string())
141                .collect::<Vec<_>>()
142                .join("|")
143        } else {
144            self.raw_values.join("|")
145        }
146    }
147}
148
149/// An owned version of BpsvRow for when we need to store data
150#[derive(Debug, Clone, PartialEq)]
151#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
152pub struct OwnedBpsvRow {
153    /// Raw string values as they appear in the BPSV
154    raw_values: Vec<String>,
155    /// Typed values (lazy-loaded)
156    typed_values: Option<Vec<BpsvValue>>,
157}
158
159impl OwnedBpsvRow {
160    /// Create a new row from owned string values
161    pub fn new(values: Vec<String>) -> Self {
162        Self {
163            raw_values: values,
164            typed_values: None,
165        }
166    }
167
168    /// Create from a borrowed row
169    pub fn from_borrowed(row: &BpsvRow<'_>) -> Self {
170        Self {
171            raw_values: row.raw_values.iter().map(|&s| s.to_string()).collect(),
172            typed_values: row.typed_values.clone(),
173        }
174    }
175
176    /// Convert to borrowed row
177    pub fn as_borrowed(&self) -> BpsvRow<'_> {
178        BpsvRow {
179            raw_values: self.raw_values.iter().map(|s| s.as_str()).collect(),
180            typed_values: self.typed_values.clone(),
181        }
182    }
183}
184
185/// Represents a complete BPSV document with borrowed data
186#[derive(Debug, Clone, PartialEq)]
187pub struct BpsvDocument<'a> {
188    /// The original content (for zero-copy)
189    content: &'a str,
190    /// The schema defining field structure
191    schema: BpsvSchema,
192    /// Sequence number (optional)
193    sequence_number: Option<u32>,
194    /// All data rows
195    rows: Vec<BpsvRow<'a>>,
196}
197
198impl<'a> BpsvDocument<'a> {
199    /// Create a new BPSV document
200    pub fn new(content: &'a str, schema: BpsvSchema) -> Self {
201        Self {
202            content,
203            schema,
204            sequence_number: None,
205            rows: Vec::new(),
206        }
207    }
208
209    /// Parse a BPSV document from string content
210    ///
211    /// # Examples
212    ///
213    /// ```
214    /// use ngdp_bpsv::BpsvDocument;
215    ///
216    /// let content = "Region!STRING:0|BuildId!DEC:4\n## seqn = 12345\nus|1234\neu|5678";
217    ///
218    /// let doc = BpsvDocument::parse(content)?;
219    /// assert_eq!(doc.sequence_number(), Some(12345));
220    /// assert_eq!(doc.rows().len(), 2);
221    /// # Ok::<(), ngdp_bpsv::Error>(())
222    /// ```
223    pub fn parse(content: &'a str) -> Result<Self> {
224        crate::parser::BpsvParser::parse(content)
225    }
226
227    /// Get the schema
228    pub fn schema(&self) -> &BpsvSchema {
229        &self.schema
230    }
231
232    /// Get the sequence number
233    pub fn sequence_number(&self) -> Option<u32> {
234        self.sequence_number
235    }
236
237    /// Set the sequence number
238    pub fn set_sequence_number(&mut self, seqn: Option<u32>) {
239        self.sequence_number = seqn;
240    }
241
242    /// Get all rows
243    pub fn rows(&self) -> &[BpsvRow<'a>] {
244        &self.rows
245    }
246
247    /// Get a mutable reference to all rows
248    pub fn rows_mut(&mut self) -> &mut [BpsvRow<'a>] {
249        &mut self.rows
250    }
251
252    /// Get the number of rows
253    pub fn row_count(&self) -> usize {
254        self.rows.len()
255    }
256
257    /// Check if the document has no data rows
258    pub fn is_empty(&self) -> bool {
259        self.rows.is_empty()
260    }
261
262    /// Add a row from raw string slices
263    pub fn add_row(&mut self, values: Vec<&'a str>) -> Result<()> {
264        // Validate against schema
265        let validated = self.schema.validate_row_refs(&values)?;
266        self.rows.push(BpsvRow::new(validated));
267        Ok(())
268    }
269
270    /// Add a row from typed values
271    pub fn add_typed_row(&mut self, values: Vec<BpsvValue>) -> Result<()> {
272        if values.len() != self.schema.field_count() {
273            return Err(Error::SchemaMismatch {
274                expected: self.schema.field_count(),
275                actual: values.len(),
276            });
277        }
278
279        // Validate compatibility
280        for (value, field) in values.iter().zip(self.schema.fields()) {
281            if !value.is_compatible_with(&field.field_type) {
282                return Err(Error::InvalidValue {
283                    field: field.name.clone(),
284                    field_type: field.field_type.to_string(),
285                    value: value.to_bpsv_string(),
286                });
287            }
288        }
289
290        self.rows.push(BpsvRow::from_typed_values(values));
291        Ok(())
292    }
293
294    /// Get a row by index
295    pub fn get_row(&self, index: usize) -> Option<&BpsvRow<'a>> {
296        self.rows.get(index)
297    }
298
299    /// Get a mutable row by index
300    pub fn get_row_mut(&mut self, index: usize) -> Option<&mut BpsvRow<'a>> {
301        self.rows.get_mut(index)
302    }
303
304    /// Find rows where a field matches a specific value
305    pub fn find_rows_by_field(&self, field_name: &str, value: &str) -> Result<Vec<usize>> {
306        let field = self
307            .schema
308            .get_field(field_name)
309            .ok_or_else(|| Error::FieldNotFound {
310                field: field_name.to_string(),
311            })?;
312
313        let mut matching_indices = Vec::new();
314        for (index, row) in self.rows.iter().enumerate() {
315            if let Some(row_value) = row.get_raw(field.index) {
316                if row_value == value {
317                    matching_indices.push(index);
318                }
319            }
320        }
321
322        Ok(matching_indices)
323    }
324
325    /// Convert the entire document back to BPSV format
326    pub fn to_bpsv_string(&self) -> String {
327        let mut lines = Vec::new();
328
329        // Header line
330        lines.push(self.schema.to_header_line());
331
332        // Sequence number line
333        if let Some(seqn) = self.sequence_number {
334            lines.push(format!("## seqn = {seqn}"));
335        }
336
337        // Data rows
338        for row in &self.rows {
339            lines.push(row.to_bpsv_line());
340        }
341
342        lines.join("\n")
343    }
344
345    /// Get all values for a specific field
346    pub fn get_column(&self, field_name: &str) -> Result<Vec<&str>> {
347        let field = self
348            .schema
349            .get_field(field_name)
350            .ok_or_else(|| Error::FieldNotFound {
351                field: field_name.to_string(),
352            })?;
353
354        let mut values = Vec::new();
355        for row in &self.rows {
356            if let Some(value) = row.get_raw(field.index) {
357                values.push(value);
358            }
359        }
360
361        Ok(values)
362    }
363
364    /// Convert all rows to maps for easier access
365    pub fn to_maps(&self) -> Result<Vec<HashMap<String, String>>> {
366        let mut maps = Vec::new();
367        for row in &self.rows {
368            maps.push(row.to_map(&self.schema)?);
369        }
370        Ok(maps)
371    }
372}
373
374/// An owned version of BpsvDocument for serialization
375#[derive(Debug, Clone, PartialEq)]
376#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
377pub struct OwnedBpsvDocument {
378    /// The schema defining field structure
379    schema: BpsvSchema,
380    /// Sequence number (optional)
381    sequence_number: Option<u32>,
382    /// All data rows
383    rows: Vec<OwnedBpsvRow>,
384}
385
386impl OwnedBpsvDocument {
387    /// Create a new owned document
388    pub fn new(schema: BpsvSchema) -> Self {
389        Self {
390            schema,
391            sequence_number: None,
392            rows: Vec::new(),
393        }
394    }
395
396    /// Set the sequence number
397    pub fn set_sequence_number(&mut self, seqn: Option<u32>) {
398        self.sequence_number = seqn;
399    }
400
401    /// Add a row to the document
402    pub fn add_row(&mut self, row: OwnedBpsvRow) {
403        self.rows.push(row);
404    }
405
406    /// Get the schema
407    pub fn schema(&self) -> &BpsvSchema {
408        &self.schema
409    }
410
411    /// Get the sequence number
412    pub fn sequence_number(&self) -> Option<u32> {
413        self.sequence_number
414    }
415
416    /// Get the number of rows
417    pub fn row_count(&self) -> usize {
418        self.rows.len()
419    }
420
421    /// Get all rows
422    pub fn rows(&self) -> &[OwnedBpsvRow] {
423        &self.rows
424    }
425
426    /// Create from a borrowed document
427    pub fn from_borrowed(doc: &BpsvDocument<'_>) -> Self {
428        Self {
429            schema: doc.schema.clone(),
430            sequence_number: doc.sequence_number,
431            rows: doc.rows.iter().map(OwnedBpsvRow::from_borrowed).collect(),
432        }
433    }
434
435    /// Convert to BPSV string
436    pub fn to_bpsv_string(&self) -> String {
437        let mut lines = Vec::new();
438
439        // Header line
440        lines.push(self.schema.to_header_line());
441
442        // Sequence number line
443        if let Some(seqn) = self.sequence_number {
444            lines.push(format!("## seqn = {seqn}"));
445        }
446
447        // Data rows
448        for row in &self.rows {
449            lines.push(row.as_borrowed().to_bpsv_line());
450        }
451
452        lines.join("\n")
453    }
454}
455
456#[cfg(test)]
457mod tests {
458    use super::*;
459    use crate::{BpsvFieldType, BpsvSchema};
460
461    fn create_test_schema() -> BpsvSchema {
462        let mut schema = BpsvSchema::new();
463        schema
464            .add_field("Region".to_string(), BpsvFieldType::String(0))
465            .unwrap();
466        schema
467            .add_field("BuildConfig".to_string(), BpsvFieldType::Hex(16))
468            .unwrap();
469        schema
470            .add_field("BuildId".to_string(), BpsvFieldType::Decimal(4))
471            .unwrap();
472        schema
473    }
474
475    #[test]
476    fn test_row_operations() {
477        let schema = create_test_schema();
478        let mut row = BpsvRow::new(vec!["us", "abcd1234abcd1234abcd1234abcd1234", "1234"]);
479
480        assert_eq!(row.len(), 3);
481        assert_eq!(row.get_raw(0), Some("us"));
482        assert_eq!(row.get_raw_by_name("Region", &schema), Some("us"));
483
484        let typed_values = row.get_typed_values(&schema).unwrap();
485        assert_eq!(typed_values.len(), 3);
486        assert_eq!(typed_values[0], BpsvValue::String("us".to_string()));
487        assert_eq!(
488            typed_values[1],
489            BpsvValue::Hex("abcd1234abcd1234abcd1234abcd1234".to_string())
490        );
491        assert_eq!(typed_values[2], BpsvValue::Decimal(1234));
492    }
493
494    #[test]
495    fn test_document_creation() {
496        let content = "";
497        let schema = create_test_schema();
498        let mut doc = BpsvDocument::new(content, schema);
499
500        doc.set_sequence_number(Some(12345));
501        assert_eq!(doc.sequence_number(), Some(12345));
502
503        doc.add_row(vec!["us", "abcd1234abcd1234abcd1234abcd1234", "1234"])
504            .unwrap();
505        doc.add_row(vec!["eu", "1234abcd1234abcd1234abcd1234abcd", "5678"])
506            .unwrap();
507
508        assert_eq!(doc.row_count(), 2);
509        assert!(!doc.is_empty());
510    }
511
512    #[test]
513    fn test_find_rows() {
514        let content = "";
515        let schema = create_test_schema();
516        let mut doc = BpsvDocument::new(content, schema);
517
518        doc.add_row(vec!["us", "abcd1234abcd1234abcd1234abcd1234", "1234"])
519            .unwrap();
520        doc.add_row(vec!["eu", "1234abcd1234abcd1234abcd1234abcd", "5678"])
521            .unwrap();
522        doc.add_row(vec!["us", "deadbeefdeadbeefdeadbeefdeadbeef", "9999"])
523            .unwrap();
524
525        let us_rows = doc.find_rows_by_field("Region", "us").unwrap();
526        assert_eq!(us_rows, vec![0, 2]);
527
528        let eu_rows = doc.find_rows_by_field("Region", "eu").unwrap();
529        assert_eq!(eu_rows, vec![1]);
530    }
531
532    #[test]
533    fn test_column_access() {
534        let content = "";
535        let schema = create_test_schema();
536        let mut doc = BpsvDocument::new(content, schema);
537
538        doc.add_row(vec!["us", "abcd1234abcd1234abcd1234abcd1234", "1234"])
539            .unwrap();
540        doc.add_row(vec!["eu", "1234abcd1234abcd1234abcd1234abcd", "5678"])
541            .unwrap();
542
543        let regions = doc.get_column("Region").unwrap();
544        assert_eq!(regions, vec!["us", "eu"]);
545
546        let build_ids = doc.get_column("BuildId").unwrap();
547        assert_eq!(build_ids, vec!["1234", "5678"]);
548    }
549
550    #[test]
551    fn test_to_bpsv_string() {
552        let content = "";
553        let schema = create_test_schema();
554        let mut doc = BpsvDocument::new(content, schema);
555        doc.set_sequence_number(Some(12345));
556        doc.add_row(vec!["us", "abcd1234abcd1234abcd1234abcd1234", "1234"])
557            .unwrap();
558
559        let bpsv_string = doc.to_bpsv_string();
560        let lines: Vec<&str> = bpsv_string.lines().collect();
561
562        assert_eq!(lines[0], "Region!STRING:0|BuildConfig!HEX:16|BuildId!DEC:4");
563        assert_eq!(lines[1], "## seqn = 12345");
564        assert_eq!(lines[2], "us|abcd1234abcd1234abcd1234abcd1234|1234");
565    }
566
567    #[test]
568    fn test_schema_mismatch() {
569        let content = "";
570        let schema = create_test_schema();
571        let mut doc = BpsvDocument::new(content, schema);
572
573        // Too few values
574        let result = doc.add_row(vec!["us"]);
575        assert!(matches!(result, Err(Error::SchemaMismatch { .. })));
576
577        // Too many values
578        let result = doc.add_row(vec!["us", "hex", "123", "extra"]);
579        assert!(matches!(result, Err(Error::SchemaMismatch { .. })));
580    }
581}