use crate::document::OwnedBpsvDocument;
use crate::error::{Error, Result};
use crate::field_type::BpsvFieldType;
use crate::schema::BpsvSchema;
use crate::value::BpsvValue;
#[derive(Debug, Clone)]
pub struct BpsvBuilder {
schema: BpsvSchema,
sequence_number: Option<u32>,
rows: Vec<Vec<BpsvValue>>,
}
impl BpsvBuilder {
#[must_use]
pub fn new() -> Self {
Self {
schema: BpsvSchema::new(),
sequence_number: None,
rows: Vec::new(),
}
}
#[must_use]
pub fn from_schema(schema: BpsvSchema) -> Self {
Self {
schema,
sequence_number: None,
rows: Vec::new(),
}
}
pub fn add_field(&mut self, name: &str, field_type: BpsvFieldType) -> Result<&mut Self> {
self.schema.add_field(name.to_string(), field_type)?;
Ok(self)
}
pub fn set_sequence_number(&mut self, seqn: u32) -> &mut Self {
self.sequence_number = Some(seqn);
self
}
pub fn clear_sequence_number(&mut self) -> &mut Self {
self.sequence_number = None;
self
}
pub fn add_row(&mut self, values: Vec<BpsvValue>) -> Result<&mut Self> {
if values.len() != self.schema.field_count() {
return Err(Error::SchemaMismatch {
expected: self.schema.field_count(),
actual: values.len(),
});
}
for (value, field) in values.iter().zip(self.schema.fields()) {
if !value.is_compatible_with(&field.field_type) {
return Err(Error::InvalidValue {
field: field.name.clone(),
field_type: field.field_type.to_string(),
value: value.to_bpsv_string(),
});
}
let value_str = value.to_bpsv_string();
field
.field_type
.validate_value(&value_str)
.map_err(|mut err| {
if let Error::InvalidValue {
field: err_field, ..
} = &mut err
{
err_field.clone_from(&field.name);
}
err
})?;
}
self.rows.push(values);
Ok(self)
}
pub fn add_raw_row(&mut self, values: &[String]) -> Result<&mut Self> {
if values.len() != self.schema.field_count() {
return Err(Error::SchemaMismatch {
expected: self.schema.field_count(),
actual: values.len(),
});
}
let mut typed_values = Vec::new();
for (value, field) in values.iter().zip(self.schema.fields()) {
let typed_value = BpsvValue::parse(value, &field.field_type)?;
typed_values.push(typed_value);
}
self.rows.push(typed_values);
Ok(self)
}
pub fn add_values_row<T>(&mut self, values: Vec<T>) -> Result<&mut Self>
where
T: Into<BpsvValue>,
{
let typed_values: Vec<BpsvValue> =
values.into_iter().map(std::convert::Into::into).collect();
self.add_row(typed_values)
}
#[must_use]
pub fn field_count(&self) -> usize {
self.schema.field_count()
}
#[must_use]
pub fn row_count(&self) -> usize {
self.rows.len()
}
#[must_use]
pub fn has_fields(&self) -> bool {
self.schema.field_count() > 0
}
#[must_use]
pub fn has_rows(&self) -> bool {
!self.rows.is_empty()
}
#[must_use]
pub fn schema(&self) -> &BpsvSchema {
&self.schema
}
pub fn clear_rows(&mut self) -> &mut Self {
self.rows.clear();
self
}
pub fn reset(&mut self) -> &mut Self {
self.schema = BpsvSchema::new();
self.sequence_number = None;
self.rows.clear();
self
}
pub fn build(self) -> Result<OwnedBpsvDocument> {
if self.schema.field_count() == 0 {
return Err(Error::InvalidHeader {
reason: "No fields defined in schema".to_string(),
});
}
let mut document = OwnedBpsvDocument::new(self.schema);
document.set_sequence_number(self.sequence_number);
for row in self.rows {
let raw_values: Vec<String> = row.iter().map(|v| v.to_bpsv_string()).collect();
document.add_row(crate::document::OwnedBpsvRow::new(raw_values));
}
Ok(document)
}
pub fn build_string(self) -> Result<String> {
let document = self.build()?;
Ok(document.to_bpsv_string())
}
pub fn validate(&self) -> Result<()> {
if self.schema.field_count() == 0 {
return Err(Error::InvalidHeader {
reason: "No fields defined".to_string(),
});
}
for (row_index, row) in self.rows.iter().enumerate() {
if row.len() != self.schema.field_count() {
return Err(Error::RowValidation {
row_index,
reason: format!(
"Expected {} fields, got {}",
self.schema.field_count(),
row.len()
),
});
}
for (value, field) in row.iter().zip(self.schema.fields()) {
if !value.is_compatible_with(&field.field_type) {
return Err(Error::RowValidation {
row_index,
reason: format!(
"Value '{}' is not compatible with field '{}' of type {}",
value.to_bpsv_string(),
field.name,
field.field_type
),
});
}
}
}
Ok(())
}
pub fn from_bpsv(content: &str) -> Result<Self> {
let document = crate::parser::BpsvParser::parse(content)?;
let mut builder = Self::from_schema(document.schema().clone());
builder.sequence_number = document.sequence_number();
for row in document.rows() {
let typed_values: Vec<BpsvValue> = row
.raw_values()
.iter()
.zip(document.schema().fields())
.map(|(value, field)| BpsvValue::parse(value, &field.field_type))
.collect::<Result<Vec<_>>>()?;
builder.rows.push(typed_values);
}
Ok(builder)
}
}
impl Default for BpsvBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_building() {
let mut builder = BpsvBuilder::new();
builder
.add_field("Region", BpsvFieldType::String(0))
.unwrap();
builder
.add_field("BuildId", BpsvFieldType::Decimal(4))
.unwrap();
builder.set_sequence_number(12345);
builder
.add_row(vec![
BpsvValue::String("us".to_string()),
BpsvValue::Decimal(1234),
])
.unwrap();
builder
.add_row(vec![
BpsvValue::String("eu".to_string()),
BpsvValue::Decimal(5678),
])
.unwrap();
let document = builder.build().unwrap();
assert_eq!(document.sequence_number(), Some(12345));
assert_eq!(document.row_count(), 2);
assert_eq!(document.schema().field_count(), 2);
}
#[test]
fn test_raw_row_addition() {
let mut builder = BpsvBuilder::new();
builder
.add_field("Region", BpsvFieldType::String(0))
.unwrap();
builder
.add_field("BuildId", BpsvFieldType::Decimal(4))
.unwrap();
builder
.add_raw_row(&["us".to_string(), "1234".to_string()])
.unwrap();
builder
.add_raw_row(&["eu".to_string(), "5678".to_string()])
.unwrap();
let document = builder.build().unwrap();
assert_eq!(document.row_count(), 2);
}
#[test]
fn test_values_row_addition() {
let mut builder = BpsvBuilder::new();
builder
.add_field("Region", BpsvFieldType::String(0))
.unwrap();
builder
.add_field("BuildId", BpsvFieldType::Decimal(4))
.unwrap();
builder
.add_raw_row(&["us".to_string(), "1234".to_string()])
.unwrap();
builder
.add_raw_row(&["eu".to_string(), "5678".to_string()])
.unwrap();
let document = builder.build().unwrap();
assert_eq!(document.row_count(), 2);
}
#[test]
fn test_build_string() {
let mut builder = BpsvBuilder::new();
builder
.add_field("Region", BpsvFieldType::String(0))
.unwrap();
builder
.add_field("BuildId", BpsvFieldType::Decimal(4))
.unwrap();
builder.set_sequence_number(12345);
builder
.add_raw_row(&["us".to_string(), "1234".to_string()])
.unwrap();
let bpsv_string = builder.build_string().unwrap();
let lines: Vec<&str> = bpsv_string.lines().collect();
assert_eq!(lines[0], "Region!STRING:0|BuildId!DEC:4");
assert_eq!(lines[1], "## seqn = 12345");
assert_eq!(lines[2], "us|1234");
}
#[test]
fn test_from_bpsv() {
let content = r"Region!STRING:0|BuildId!DEC:4
## seqn = 12345
us|1234
eu|5678";
let builder = BpsvBuilder::from_bpsv(content).unwrap();
assert_eq!(builder.field_count(), 2);
assert_eq!(builder.row_count(), 2);
let rebuilt = builder.build_string().unwrap();
let original_doc = crate::parser::BpsvParser::parse(content).unwrap();
let rebuilt_doc = crate::parser::BpsvParser::parse(&rebuilt).unwrap();
assert_eq!(
original_doc.sequence_number(),
rebuilt_doc.sequence_number()
);
assert_eq!(original_doc.row_count(), rebuilt_doc.row_count());
assert_eq!(
original_doc.schema().field_count(),
rebuilt_doc.schema().field_count()
);
}
#[test]
fn test_validation() {
let mut builder = BpsvBuilder::new();
assert!(builder.validate().is_err());
builder
.add_field("Region", BpsvFieldType::String(0))
.unwrap();
builder
.add_field("BuildId", BpsvFieldType::Decimal(4))
.unwrap();
assert!(builder.validate().is_ok());
builder
.add_row(vec![
BpsvValue::String("us".to_string()),
BpsvValue::Decimal(1234),
])
.unwrap();
assert!(builder.validate().is_ok());
}
#[test]
fn test_schema_mismatch_errors() {
let mut builder = BpsvBuilder::new();
builder
.add_field("Region", BpsvFieldType::String(0))
.unwrap();
let result = builder.add_row(vec![
BpsvValue::String("us".to_string()),
BpsvValue::Decimal(1234),
]);
assert!(matches!(result, Err(Error::SchemaMismatch { .. })));
let result = builder.add_row(vec![]);
assert!(matches!(result, Err(Error::SchemaMismatch { .. })));
}
#[test]
fn test_incompatible_value_types() {
let mut builder = BpsvBuilder::new();
builder
.add_field("Region", BpsvFieldType::String(0))
.unwrap();
let result = builder.add_row(vec![BpsvValue::Decimal(1234)]);
assert!(matches!(result, Err(Error::InvalidValue { .. })));
}
#[test]
fn test_builder_state_methods() {
let mut builder = BpsvBuilder::new();
assert_eq!(builder.field_count(), 0);
assert_eq!(builder.row_count(), 0);
assert!(!builder.has_fields());
assert!(!builder.has_rows());
builder
.add_field("Region", BpsvFieldType::String(0))
.unwrap();
assert_eq!(builder.field_count(), 1);
assert!(builder.has_fields());
assert!(!builder.has_rows());
builder.add_values_row(vec!["us"]).unwrap();
assert_eq!(builder.row_count(), 1);
assert!(builder.has_rows());
builder.clear_rows();
assert_eq!(builder.row_count(), 0);
assert!(!builder.has_rows());
assert!(builder.has_fields());
builder.reset();
assert_eq!(builder.field_count(), 0);
assert_eq!(builder.row_count(), 0);
assert!(!builder.has_fields());
assert!(!builder.has_rows());
}
}