use crate::ascii_table::AsciiColumnFormat;
use crate::bin_table::Value;
use crate::header::Header;
use std::error::Error;
#[derive(Debug, Clone, PartialEq)]
pub struct AsciiFieldDefinition {
pub format: AsciiColumnFormat,
pub offset: usize,
pub name: String,
pub null: Option<String>,
pub scale: Option<f64>,
pub zero: Option<f64>,
}
impl AsciiFieldDefinition {
pub fn all_from_header(header: &Header) -> Result<Vec<Self>, Box<dyn Error + Send + Sync>> {
let table_fields = header
.table_fields()
.ok_or("Table header is missing its TFIELDS card")?;
let table_fields = usize::try_from(table_fields)
.map_err(|_| format!("TFIELDS must not be negative, but was {}", table_fields))?;
(0..table_fields)
.map(|index| {
let format = header.ascii_column_format(index).ok_or_else(|| {
match header.table_format(index) {
Some(format) => format!(
"Table header has an invalid ASCII TFORM{} card: {:?}",
index + 1,
format
),
None => format!("Table header is missing its TFORM{} card", index + 1),
}
})?;
let column = header.table_column(index).ok_or_else(|| {
format!("Table header is missing its TBCOL{} card", index + 1)
})?;
let offset = usize::try_from(column)
.map_err(|_| format!("TBCOL{} must not be negative", index + 1))?
.saturating_sub(1);
Ok::<_, Box<dyn Error + Send + Sync>>(Self {
format,
offset,
name: header
.table_column_type(index)
.unwrap_or_default()
.to_string(),
null: header
.table_null_value(index)
.and_then(|null| null.as_str())
.map(|null| null.trim().to_string()),
scale: header.table_scaling_factor(index),
zero: header.table_scaling_zero_point(index),
})
})
.collect()
}
pub fn decode(&self, row: &[u8]) -> crate::Result<Value> {
let width = self.format.bytes_len();
let field = row
.get(self.offset..)
.and_then(|row| row.get(..width))
.ok_or_else(|| {
crate::Error::DeserializationError(format!(
"Column {} occupies bytes {}..{} of a {} byte row",
self.name,
self.offset,
self.offset + width,
row.len()
))
})?;
if let Some(null) = &self.null
&& String::from_utf8_lossy(field).trim() == null
{
return Ok(Value::Null);
}
Ok(self.scaled(self.format.parse_into_value(field)?))
}
fn scaled(&self, value: Value) -> Value {
let scale = self.scale.unwrap_or(1.0);
let zero = self.zero.unwrap_or(0.0);
if scale == 1.0 && zero == 0.0 {
return value;
}
match value {
Value::I64(values) => Value::F64(
values
.into_iter()
.map(|raw| zero + scale * raw as f64)
.collect(),
),
Value::F64(values) => {
Value::F64(values.into_iter().map(|raw| zero + scale * raw).collect())
}
other => other,
}
}
}