use super::constraint::TypeConstraint;
use super::conversions::{FromVsfType, IntoVsfType};
use super::field::FieldSchema;
use super::validate::{ValidationError, ValidationResult};
use crate::VsfType;
#[derive(Debug, Clone)]
pub struct FieldValue {
pub name: String,
pub values: Vec<VsfType>,
}
impl FieldValue {
pub fn new(name: impl Into<String>, values: Vec<VsfType>) -> Self {
Self {
name: name.into(),
values,
}
}
pub fn empty(name: impl Into<String>) -> Self {
Self {
name: name.into(),
values: Vec::new(),
}
}
pub fn single(name: impl Into<String>, value: VsfType) -> Self {
Self {
name: name.into(),
values: vec![value],
}
}
pub fn flatten(&self) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.push(b'(');
bytes.extend(VsfType::d(self.name.clone()).flatten());
if !self.values.is_empty() {
bytes.push(b':');
for (i, value) in self.values.iter().enumerate() {
if i > 0 {
bytes.push(b','); }
bytes.extend(value.flatten());
}
}
bytes.push(b')');
bytes
}
}
#[derive(Debug, Clone)]
pub struct SectionSchema {
pub name: String,
pub fields: Vec<FieldSchema>,
pub description: Option<String>,
}
impl SectionSchema {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
fields: Vec::new(),
description: None,
}
}
pub fn field(mut self, name: impl Into<String>, constraint: TypeConstraint) -> Self {
self.fields.push(FieldSchema::new(name, constraint));
self
}
pub fn description(mut self, desc: impl Into<String>) -> Self {
self.description = Some(desc.into());
self
}
pub fn build(&self) -> SectionBuilder {
SectionBuilder::new(self.clone())
}
pub fn allowed_fields(&self) -> Vec<String> {
self.fields.iter().map(|f| f.name.clone()).collect()
}
pub fn validate_field(&self, name: &str) -> ValidationResult<&FieldSchema> {
self.fields
.iter()
.find(|f| f.name == name)
.ok_or_else(|| ValidationError::UnknownField {
section: self.name.clone(),
field: name.to_string(),
allowed: self.allowed_fields(),
})
}
}
#[derive(Debug)]
pub struct SectionBuilder {
schema: SectionSchema,
fields: Vec<FieldValue>, }
impl SectionBuilder {
pub fn new(schema: SectionSchema) -> Self {
Self {
schema,
fields: Vec::new(),
}
}
pub fn set<T: IntoVsfType>(
mut self,
name: impl AsRef<str>,
value: T,
) -> ValidationResult<Self> {
let name = name.as_ref();
let vsf_value = value.into_vsf_type();
let field_schema = self.schema.validate_field(name)?;
field_schema.validate(&vsf_value)?;
self.fields.retain(|f| f.name != name);
self.fields.push(FieldValue::single(name, vsf_value));
Ok(self)
}
pub fn set_empty(mut self, name: impl AsRef<str>) -> ValidationResult<Self> {
let name = name.as_ref();
self.schema.validate_field(name)?;
self.fields.retain(|f| f.name != name);
self.fields.push(FieldValue::empty(name));
Ok(self)
}
pub fn set_multi<T: IntoVsfType>(
mut self,
name: impl AsRef<str>,
values: Vec<T>,
) -> ValidationResult<Self> {
let name = name.as_ref();
let field_schema = self.schema.validate_field(name)?;
let mut vsf_values = Vec::new();
for value in values {
let vsf_value = value.into_vsf_type();
field_schema.validate(&vsf_value)?;
vsf_values.push(vsf_value);
}
self.fields.retain(|f| f.name != name);
self.fields.push(FieldValue::new(name, vsf_values));
Ok(self)
}
pub fn append_multi<T: IntoVsfType>(
mut self,
name: impl AsRef<str>,
values: Vec<T>,
) -> ValidationResult<Self> {
let name = name.as_ref();
let field_schema = self.schema.validate_field(name)?;
let mut vsf_values = Vec::new();
for value in values {
let vsf_value = value.into_vsf_type();
field_schema.validate(&vsf_value)?;
vsf_values.push(vsf_value);
}
self.fields.push(FieldValue::new(name, vsf_values));
Ok(self)
}
pub fn add_value<T: IntoVsfType>(
mut self,
name: impl AsRef<str>,
value: T,
) -> ValidationResult<Self> {
let name = name.as_ref();
let vsf_value = value.into_vsf_type();
let field_schema = self.schema.validate_field(name)?;
field_schema.validate(&vsf_value)?;
if let Some(field) = self.fields.iter_mut().find(|f| f.name == name) {
field.values.push(vsf_value);
} else {
self.fields.push(FieldValue::single(name, vsf_value));
}
Ok(self)
}
pub fn get(&self, name: &str) -> ValidationResult<&Vec<VsfType>> {
self.fields
.iter()
.find(|f| f.name == name)
.map(|f| &f.values)
.ok_or_else(|| ValidationError::Custom(format!("Field '{}' not set", name)))
}
pub fn get_value<T: FromVsfType>(&self, name: &str) -> ValidationResult<T> {
let values = self.get(name)?;
let first_value = values
.first()
.ok_or_else(|| ValidationError::Custom(format!("Field '{}' is empty", name)))?;
T::from_vsf_type(first_value)
}
pub fn get_values(&self, name: &str) -> ValidationResult<Vec<VsfType>> {
Ok(self.get(name)?.clone())
}
pub fn get_fields(&self, name: &str) -> Vec<&FieldValue> {
self.fields.iter().filter(|f| f.name == name).collect()
}
pub fn encode(&self) -> ValidationResult<Vec<u8>> {
for field_schema in &self.schema.fields {
if field_schema.required && !self.fields.iter().any(|f| f.name == field_schema.name) {
return Err(ValidationError::MissingField {
section: self.schema.name.clone(),
field: field_schema.name.clone(),
});
}
}
let mut bytes = Vec::new();
bytes.push(b'[');
for field in &self.fields {
bytes.extend(field.flatten());
}
bytes.push(b']');
Ok(bytes)
}
pub fn parse(schema: SectionSchema, section_bytes: &[u8]) -> ValidationResult<Self> {
use crate::decoding::parse::parse;
let mut ptr = 0;
if section_bytes.is_empty() || section_bytes[ptr] != b'[' {
return Err(ValidationError::Custom(format!(
"Expected '[' to start section, found {:?}",
section_bytes.get(ptr)
)));
}
ptr += 1;
let section_name = parse(section_bytes, &mut ptr)
.map_err(|e| ValidationError::Custom(format!("Failed to parse section name: {}", e)))?;
let section_name_str = match section_name {
crate::VsfType::d(name) => name,
_ => {
return Err(ValidationError::Custom(format!(
"Expected section name (d), got {:?}",
section_name
)))
}
};
if section_name_str != schema.name {
return Err(ValidationError::Custom(format!(
"Section name mismatch: expected '{}', found '{}'",
schema.name, section_name_str
)));
}
let mut builder = SectionBuilder::new(schema.clone());
loop {
while ptr < section_bytes.len() && section_bytes[ptr].is_ascii_whitespace() {
ptr += 1;
}
if ptr >= section_bytes.len() || section_bytes[ptr] == b']' {
break;
}
if section_bytes[ptr] != b'(' {
return Err(ValidationError::Custom(format!(
"Expected '(' to start field at position {}, found {:?}",
ptr,
section_bytes.get(ptr)
)));
}
ptr += 1;
let field_name_vsf = parse(section_bytes, &mut ptr).map_err(|e| {
ValidationError::Custom(format!("Failed to parse field name: {}", e))
})?;
let field_name = match field_name_vsf {
crate::VsfType::d(name) => name,
_ => {
return Err(ValidationError::Custom(format!(
"Expected field name (d), got {:?}",
field_name_vsf
)))
}
};
while ptr < section_bytes.len() && section_bytes[ptr].is_ascii_whitespace() {
ptr += 1;
}
let mut values = Vec::new();
if ptr < section_bytes.len() && section_bytes[ptr] == b':' {
ptr += 1;
loop {
let value = parse(section_bytes, &mut ptr).map_err(|e| {
ValidationError::Custom(format!(
"Failed to parse field '{}' value: {}",
field_name, e
))
})?;
let field_schema = schema.validate_field(&field_name)?;
field_schema.validate(&value)?;
values.push(value);
while ptr < section_bytes.len() && section_bytes[ptr].is_ascii_whitespace() {
ptr += 1;
}
if ptr >= section_bytes.len() {
return Err(ValidationError::Custom(format!(
"Unexpected end of input while parsing field '{}'",
field_name
)));
}
if section_bytes[ptr] == b',' {
ptr += 1; continue;
} else if section_bytes[ptr] == b')' {
break; } else {
return Err(ValidationError::Custom(format!(
"Expected ',' or ')' after value in field '{}', found {:?}",
field_name,
section_bytes.get(ptr)
)));
}
}
}
if ptr >= section_bytes.len() || section_bytes[ptr] != b')' {
return Err(ValidationError::Custom(format!(
"Expected ')' to close field '{}', found {:?}",
field_name,
section_bytes.get(ptr)
)));
}
ptr += 1;
builder.fields.push(FieldValue::new(field_name, values));
}
if ptr >= section_bytes.len() || section_bytes[ptr] != b']' {
return Err(ValidationError::Custom(format!(
"Expected ']' to close section, found {:?}",
section_bytes.get(ptr)
)));
}
Ok(builder)
}
}
#[cfg(test)]
mod tests {
use super::super::constraint::TypeConstraint;
use super::*;
#[test]
fn test_section_builder_round_trip() {
let schema = SectionSchema::new("test")
.field("width", TypeConstraint::AnyUnsigned)
.field("height", TypeConstraint::AnyUnsigned)
.field("name", TypeConstraint::AnyString);
let builder = schema
.build()
.set("width", 1920u32)
.unwrap()
.set("height", 1080u32)
.unwrap()
.set("name", "test_section".to_string())
.unwrap();
let encoded = builder.encode().unwrap();
let parsed = SectionBuilder::parse(schema.clone(), &encoded).unwrap();
let re_encoded = parsed.encode().unwrap();
assert_eq!(
encoded, re_encoded,
"Round-trip encoding should produce identical bytes"
);
}
#[test]
fn test_section_parser_validates_name() {
let schema = SectionSchema::new("test").field("value", TypeConstraint::AnyUnsigned);
let wrong_section = SectionSchema::new("wrong").field("value", TypeConstraint::AnyUnsigned);
let built = wrong_section.build().set("value", 42u16).unwrap();
let encoded = built.encode().unwrap();
let result = SectionBuilder::parse(schema, &encoded);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("name mismatch"));
}
#[test]
fn test_section_parser_with_eagle_time() {
use crate::types::EtType;
let schema = SectionSchema::new("metadata")
.field("timestamp", TypeConstraint::AnyEagleTime)
.field("count", TypeConstraint::AnyUnsigned);
let builder = schema
.build()
.set("timestamp", VsfType::e(EtType::f6(1234567.89)))
.unwrap()
.set("count", 42u32)
.unwrap();
let encoded = builder.encode().unwrap();
let parsed = SectionBuilder::parse(schema, &encoded).unwrap();
let re_encoded = parsed.encode().unwrap();
assert_eq!(encoded, re_encoded);
}
#[test]
fn test_multi_valued_fields() {
let schema = SectionSchema::new("multi")
.field("sizes", TypeConstraint::AnyUnsigned)
.field("temperatures", TypeConstraint::AnyFloat);
let builder = schema
.build()
.set_multi("sizes", vec![3u8, 4u8, 5u8])
.unwrap()
.set_multi("temperatures", vec![20.5f32, 21.0f32, 19.8f32])
.unwrap();
let encoded = builder.encode().unwrap();
let parsed = SectionBuilder::parse(schema, &encoded).unwrap();
let sizes = parsed.get_values("sizes").unwrap();
assert_eq!(sizes.len(), 3);
let temps = parsed.get_values("temperatures").unwrap();
assert_eq!(temps.len(), 3);
let re_encoded = parsed.encode().unwrap();
assert_eq!(encoded, re_encoded);
}
#[test]
fn test_empty_field() {
let schema = SectionSchema::new("weather")
.field("cloudy", TypeConstraint::AnyUnsigned)
.field("temp", TypeConstraint::AnyFloat);
let builder = schema
.build()
.set_empty("cloudy")
.unwrap()
.set("temp", 22.5f32)
.unwrap();
let encoded = builder.encode().unwrap();
let encoded_str = String::from_utf8_lossy(&encoded);
assert!(encoded_str.contains("cloudy"));
let parsed = SectionBuilder::parse(schema, &encoded).unwrap();
let cloudy_values = parsed.get("cloudy").unwrap();
assert_eq!(cloudy_values.len(), 0);
let re_encoded = parsed.encode().unwrap();
assert_eq!(encoded, re_encoded);
}
#[test]
fn test_mixed_single_multi_empty_fields() {
let schema = SectionSchema::new("data")
.field("port", TypeConstraint::AnyUnsigned) .field("sizes", TypeConstraint::AnyUnsigned) .field("cloudy", TypeConstraint::AnyUnsigned) .field("temps", TypeConstraint::AnyFloat);
let builder = schema
.build()
.set("port", 41641u16)
.unwrap()
.set_multi("sizes", vec![3u8, 4u8, 5u8])
.unwrap()
.set_empty("cloudy")
.unwrap()
.set_multi("temps", vec![20.0f32, 21.0f32])
.unwrap();
let encoded = builder.encode().unwrap();
let parsed = SectionBuilder::parse(schema, &encoded).unwrap();
assert_eq!(parsed.get_value::<u16>("port").unwrap(), 41641);
assert_eq!(parsed.get_values("sizes").unwrap().len(), 3);
assert_eq!(parsed.get_values("cloudy").unwrap().len(), 0);
assert_eq!(parsed.get_values("temps").unwrap().len(), 2);
let re_encoded = parsed.encode().unwrap();
assert_eq!(encoded, re_encoded);
}
#[test]
fn test_add_value_method() {
let schema = SectionSchema::new("test").field("values", TypeConstraint::AnyUnsigned);
let builder = schema
.build()
.add_value("values", 1u8)
.unwrap()
.add_value("values", 2u8)
.unwrap()
.add_value("values", 3u8)
.unwrap();
let values = builder.get_values("values").unwrap();
assert_eq!(values.len(), 3);
let encoded = builder.encode().unwrap();
let parsed = SectionBuilder::parse(schema, &encoded).unwrap();
let parsed_values = parsed.get_values("values").unwrap();
assert_eq!(parsed_values.len(), 3);
}
}