1use crate::error::{Error, Result};
4use crate::schema::BpsvSchema;
5use crate::value::BpsvValue;
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, PartialEq)]
10pub struct BpsvRow<'a> {
11 raw_values: Vec<&'a str>,
13 typed_values: Option<Vec<BpsvValue>>,
15}
16
17impl<'a> BpsvRow<'a> {
18 pub fn new(values: Vec<&'a str>) -> Self {
20 Self {
21 raw_values: values,
22 typed_values: None,
23 }
24 }
25
26 pub fn from_typed_values(values: Vec<BpsvValue>) -> BpsvRow<'static> {
28 BpsvRow {
30 raw_values: vec![],
31 typed_values: Some(values),
32 }
33 }
34
35 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 pub fn is_empty(&self) -> bool {
46 self.len() == 0
47 }
48
49 pub fn get_raw(&self, index: usize) -> Option<&str> {
51 self.raw_values.get(index).copied()
52 }
53
54 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 pub fn raw_values(&self) -> &[&'a str] {
63 &self.raw_values
64 }
65
66 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 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 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 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 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 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#[derive(Debug, Clone, PartialEq)]
151#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
152pub struct OwnedBpsvRow {
153 raw_values: Vec<String>,
155 typed_values: Option<Vec<BpsvValue>>,
157}
158
159impl OwnedBpsvRow {
160 pub fn new(values: Vec<String>) -> Self {
162 Self {
163 raw_values: values,
164 typed_values: None,
165 }
166 }
167
168 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 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#[derive(Debug, Clone, PartialEq)]
187pub struct BpsvDocument<'a> {
188 content: &'a str,
190 schema: BpsvSchema,
192 sequence_number: Option<u32>,
194 rows: Vec<BpsvRow<'a>>,
196}
197
198impl<'a> BpsvDocument<'a> {
199 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 pub fn parse(content: &'a str) -> Result<Self> {
224 crate::parser::BpsvParser::parse(content)
225 }
226
227 pub fn schema(&self) -> &BpsvSchema {
229 &self.schema
230 }
231
232 pub fn sequence_number(&self) -> Option<u32> {
234 self.sequence_number
235 }
236
237 pub fn set_sequence_number(&mut self, seqn: Option<u32>) {
239 self.sequence_number = seqn;
240 }
241
242 pub fn rows(&self) -> &[BpsvRow<'a>] {
244 &self.rows
245 }
246
247 pub fn rows_mut(&mut self) -> &mut [BpsvRow<'a>] {
249 &mut self.rows
250 }
251
252 pub fn row_count(&self) -> usize {
254 self.rows.len()
255 }
256
257 pub fn is_empty(&self) -> bool {
259 self.rows.is_empty()
260 }
261
262 pub fn add_row(&mut self, values: Vec<&'a str>) -> Result<()> {
264 let validated = self.schema.validate_row_refs(&values)?;
266 self.rows.push(BpsvRow::new(validated));
267 Ok(())
268 }
269
270 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 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 pub fn get_row(&self, index: usize) -> Option<&BpsvRow<'a>> {
296 self.rows.get(index)
297 }
298
299 pub fn get_row_mut(&mut self, index: usize) -> Option<&mut BpsvRow<'a>> {
301 self.rows.get_mut(index)
302 }
303
304 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 pub fn to_bpsv_string(&self) -> String {
327 let mut lines = Vec::new();
328
329 lines.push(self.schema.to_header_line());
331
332 if let Some(seqn) = self.sequence_number {
334 lines.push(format!("## seqn = {seqn}"));
335 }
336
337 for row in &self.rows {
339 lines.push(row.to_bpsv_line());
340 }
341
342 lines.join("\n")
343 }
344
345 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 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#[derive(Debug, Clone, PartialEq)]
376#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
377pub struct OwnedBpsvDocument {
378 schema: BpsvSchema,
380 sequence_number: Option<u32>,
382 rows: Vec<OwnedBpsvRow>,
384}
385
386impl OwnedBpsvDocument {
387 pub fn new(schema: BpsvSchema) -> Self {
389 Self {
390 schema,
391 sequence_number: None,
392 rows: Vec::new(),
393 }
394 }
395
396 pub fn set_sequence_number(&mut self, seqn: Option<u32>) {
398 self.sequence_number = seqn;
399 }
400
401 pub fn add_row(&mut self, row: OwnedBpsvRow) {
403 self.rows.push(row);
404 }
405
406 pub fn schema(&self) -> &BpsvSchema {
408 &self.schema
409 }
410
411 pub fn sequence_number(&self) -> Option<u32> {
413 self.sequence_number
414 }
415
416 pub fn row_count(&self) -> usize {
418 self.rows.len()
419 }
420
421 pub fn rows(&self) -> &[OwnedBpsvRow] {
423 &self.rows
424 }
425
426 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 pub fn to_bpsv_string(&self) -> String {
437 let mut lines = Vec::new();
438
439 lines.push(self.schema.to_header_line());
441
442 if let Some(seqn) = self.sequence_number {
444 lines.push(format!("## seqn = {seqn}"));
445 }
446
447 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 let result = doc.add_row(vec!["us"]);
575 assert!(matches!(result, Err(Error::SchemaMismatch { .. })));
576
577 let result = doc.add_row(vec!["us", "hex", "123", "extra"]);
579 assert!(matches!(result, Err(Error::SchemaMismatch { .. })));
580 }
581}