use std::{borrow::Cow, iter::Peekable, str::FromStr};
use rkyv::{
Archive, Deserialize, Serialize,
api::high::{HighDeserializer, HighSerializer},
rancor::Error,
ser::allocator::ArenaHandle,
util::AlignedVec,
};
#[allow(dead_code)]
pub trait RkyvDeserialize<D>: Deserialize<D, HighDeserializer<Error>> {}
pub trait RkyvSerialize:
for<'a> Serialize<HighSerializer<AlignedVec, ArenaHandle<'a>, Error>>
{
}
pub trait Saveable<'a> {
fn base_file_name() -> Cow<'a, str>;
}
#[derive(Debug)]
pub struct RowParseError(pub String);
impl std::fmt::Display for RowParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "malformed asset row: {}", self.0)
}
}
impl std::error::Error for RowParseError {}
impl From<RowParseError> for std::io::Error {
fn from(value: RowParseError) -> Self {
std::io::Error::other(value.0)
}
}
fn fields(s: &str) -> Vec<&str> {
s.split('\t').collect()
}
fn field<'a>(f: &[&'a str], idx: usize, row: &str) -> Result<&'a str, RowParseError> {
f.get(idx)
.copied()
.ok_or_else(|| RowParseError(format!("missing column {idx} in `{row}`")))
}
fn num<T: FromStr + Default>(f: &[&str], idx: usize) -> T {
f.get(idx)
.and_then(|v| v.parse::<T>().ok())
.unwrap_or_default()
}
fn owned(f: &[&str], idx: usize) -> String {
f.get(idx).copied().unwrap_or("").to_string()
}
fn optional(f: &[&str], idx: usize) -> Option<String> {
match f.get(idx).copied().unwrap_or("") {
"" => None,
v => Some(v.to_string()),
}
}
#[derive(Archive, Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)]
pub struct WmiEntry {
pub vehicle_type_id: u16,
pub truck_type_id: u16,
pub make_id: u32,
pub make: String,
pub manufacturer: String,
pub country: String,
}
impl RkyvDeserialize<WmiEntry> for ArchivedWmiEntry {}
impl RkyvSerialize for WmiEntry {}
impl<'a> Saveable<'a> for WmiEntry {
fn base_file_name() -> Cow<'a, str> {
Cow::Borrowed("wmi")
}
}
impl FromStr for WmiEntry {
type Err = RowParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let f = fields(s);
Ok(WmiEntry {
vehicle_type_id: num(&f, 0),
truck_type_id: num(&f, 1),
make_id: num(&f, 2),
make: owned(&f, 3),
manufacturer: owned(&f, 4),
country: owned(&f, 5),
})
}
}
#[derive(Archive, Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct SchemaRef {
pub schema_id: u32,
pub year_from: u16,
pub year_to: u16,
}
impl RkyvDeserialize<SchemaRef> for ArchivedSchemaRef {}
impl RkyvSerialize for SchemaRef {}
impl<'a> Saveable<'a> for SchemaRef {
fn base_file_name() -> Cow<'a, str> {
Cow::Borrowed("wmi_schema")
}
}
impl SchemaRef {
pub fn covers(&self, model_year: u16) -> bool {
model_year >= self.year_from && model_year <= self.year_to
}
}
impl FromStr for SchemaRef {
type Err = RowParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let f = fields(s);
Ok(SchemaRef {
schema_id: num(&f, 0),
year_from: num(&f, 1),
year_to: num(&f, 2),
})
}
}
#[derive(Archive, Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct PatternRow {
pub keys: String,
pub element_id: u16,
pub attribute_id: String,
pub value: String,
pub updated_on: i64,
pub id: u32,
}
impl RkyvDeserialize<PatternRow> for ArchivedPatternRow {}
impl RkyvSerialize for PatternRow {}
impl<'a> Saveable<'a> for PatternRow {
fn base_file_name() -> Cow<'a, str> {
Cow::Borrowed("pattern")
}
}
impl FromStr for PatternRow {
type Err = RowParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let f = fields(s);
Ok(PatternRow {
keys: field(&f, 0, s)?.to_string(),
element_id: num(&f, 1),
attribute_id: owned(&f, 2),
value: owned(&f, 3),
updated_on: num(&f, 4),
id: num(&f, 5),
})
}
}
#[derive(Archive, Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct ModelEntry {
pub model: String,
pub make_id: u32,
pub make: String,
}
impl RkyvDeserialize<ModelEntry> for ArchivedModelEntry {}
impl RkyvSerialize for ModelEntry {}
impl<'a> Saveable<'a> for ModelEntry {
fn base_file_name() -> Cow<'a, str> {
Cow::Borrowed("model")
}
}
impl FromStr for ModelEntry {
type Err = RowParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let f = fields(s);
Ok(ModelEntry {
model: owned(&f, 0),
make_id: num(&f, 1),
make: owned(&f, 2),
})
}
}
#[derive(Archive, Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct SpecRow {
pub row_id: u32,
pub is_key: bool,
pub element_id: u16,
pub attribute_id: String,
pub value: String,
pub updated_on: i64,
}
impl RkyvDeserialize<SpecRow> for ArchivedSpecRow {}
impl RkyvSerialize for SpecRow {}
impl<'a> Saveable<'a> for SpecRow {
fn base_file_name() -> Cow<'a, str> {
Cow::Borrowed("vspec")
}
}
impl FromStr for SpecRow {
type Err = RowParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let f = fields(s);
Ok(SpecRow {
row_id: num(&f, 0),
is_key: num::<u8>(&f, 1) == 1,
element_id: num(&f, 2),
attribute_id: owned(&f, 3),
value: owned(&f, 4),
updated_on: num(&f, 5),
})
}
}
#[derive(Archive, Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct EngineModelRow {
pub element_id: u16,
pub attribute_id: String,
pub value: String,
}
impl RkyvDeserialize<EngineModelRow> for ArchivedEngineModelRow {}
impl RkyvSerialize for EngineModelRow {}
impl<'a> Saveable<'a> for EngineModelRow {
fn base_file_name() -> Cow<'a, str> {
Cow::Borrowed("engine_model")
}
}
impl FromStr for EngineModelRow {
type Err = RowParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let f = fields(s);
Ok(EngineModelRow {
element_id: num(&f, 0),
attribute_id: owned(&f, 1),
value: owned(&f, 2),
})
}
}
#[derive(Archive, Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct DefaultValueRow {
pub element_id: u16,
pub attribute_id: String,
pub value: String,
}
impl RkyvDeserialize<DefaultValueRow> for ArchivedDefaultValueRow {}
impl RkyvSerialize for DefaultValueRow {}
impl<'a> Saveable<'a> for DefaultValueRow {
fn base_file_name() -> Cow<'a, str> {
Cow::Borrowed("default_value")
}
}
impl FromStr for DefaultValueRow {
type Err = RowParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let f = fields(s);
Ok(DefaultValueRow {
element_id: num(&f, 0),
attribute_id: owned(&f, 1),
value: owned(&f, 2),
})
}
}
#[derive(Archive, Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct GenerationRow {
pub year_from: u16,
pub year_to: u16,
pub ordinal: u8,
pub code: String,
pub name: String,
}
impl RkyvDeserialize<GenerationRow> for ArchivedGenerationRow {}
impl RkyvSerialize for GenerationRow {}
impl<'a> Saveable<'a> for GenerationRow {
fn base_file_name() -> Cow<'a, str> {
Cow::Borrowed("generation")
}
}
impl GenerationRow {
pub fn covers(&self, model_year: i32) -> bool {
model_year >= self.year_from as i32
&& (self.year_to == 0 || model_year <= self.year_to as i32)
}
}
impl FromStr for GenerationRow {
type Err = RowParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let f = fields(s);
Ok(GenerationRow {
year_from: num(&f, 0),
year_to: num(&f, 1),
ordinal: num(&f, 2),
code: owned(&f, 3),
name: field(&f, 4, s)?.to_string(),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ElementMeta {
pub id: u16,
pub code: String,
pub name: String,
pub data_type: String,
pub group: Option<String>,
}
impl FromStr for ElementMeta {
type Err = RowParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let f = fields(s);
Ok(ElementMeta {
id: num(&f, 0),
code: field(&f, 1, s)?.to_string(),
name: owned(&f, 2),
data_type: owned(&f, 3),
group: optional(&f, 4),
})
}
}
pub trait UntilNextKey<'a> {
fn next_key(&mut self) -> Option<(&'a str, Vec<&'a str>)>;
}
impl<'a, I> UntilNextKey<'a> for Peekable<I>
where
I: Iterator<Item = &'a str>,
{
fn next_key(&mut self) -> Option<(&'a str, Vec<&'a str>)> {
let mut current_key: Option<&'a str> = None;
let mut values = Vec::new();
while let Some(line) = self.peek() {
let Some((key, rest)) = line.split_once('\t') else {
self.next();
continue;
};
match current_key {
Some(ck) if ck != key => return Some((ck, values)),
None => current_key = Some(key),
Some(_) => {}
}
values.push(rest);
self.next();
}
current_key.map(|key| (key, values))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn until_next_key_groups_rows_sharing_a_key() {
let table = "a\t1\na\t2\nb\t3\n";
let mut iter = table.lines().peekable();
assert_eq!(iter.next_key(), Some(("a", vec!["1", "2"])));
assert_eq!(iter.next_key(), Some(("b", vec!["3"])));
assert_eq!(iter.next_key(), None);
}
#[test]
fn until_next_key_splits_on_the_first_tab_only() {
let table = "1C4\t7\t0\t0\t\tFCA US LLC\tUNITED STATES (USA)\n1C6\t3\t0\t0\t\tFCA US LLC\tUNITED STATES (USA)";
let mut iter = table.lines().peekable();
let (k1, v1) = iter.next_key().expect("first key");
let (k2, _) = iter.next_key().expect("second key");
assert_eq!(k1, "1C4");
assert_eq!(v1, vec!["7\t0\t0\t\tFCA US LLC\tUNITED STATES (USA)"]);
assert_eq!(k2, "1C6");
assert!(iter.next_key().is_none());
}
#[test]
fn wmi_entry_leaves_make_empty_when_the_wmi_is_shared() {
let wmi: WmiEntry = "7\t0\t0\t\tFCA US LLC\tUNITED STATES (USA)"
.parse()
.expect("parse");
assert_eq!(wmi.make_id, 0);
assert!(wmi.make.is_empty());
assert_eq!(wmi.manufacturer, "FCA US LLC");
}
#[test]
fn schema_ref_covers_its_year_window_inclusively() {
let schema: SchemaRef = "12345\t2014\t2018".parse().expect("parse");
assert!(schema.covers(2014));
assert!(schema.covers(2018));
assert!(!schema.covers(2013));
assert!(!schema.covers(2019));
}
#[test]
fn pattern_row_keeps_values_containing_separators_intact() {
let row: PatternRow =
"RJFBG\t5\t7\tSport Utility Vehicle (SUV)/Multi-Purpose Vehicle (MPV)\t1425463435\t14"
.parse()
.expect("parse");
assert_eq!(
row.value,
"Sport Utility Vehicle (SUV)/Multi-Purpose Vehicle (MPV)"
);
assert_eq!(row.id, 14);
}
#[test]
fn a_current_generation_has_an_open_upper_bound() {
let current: GenerationRow = "2022\t0\t11\tFE/FL\t11th generation"
.parse()
.expect("parse");
assert!(current.covers(2022));
assert!(current.covers(2030));
assert!(!current.covers(2021));
let closed: GenerationRow = "2016\t2021\t10\tFC/FK\t10th generation"
.parse()
.expect("parse");
assert!(closed.covers(2021));
assert!(!closed.covers(2022));
}
#[test]
fn spec_row_reads_its_key_flag() {
let key: SpecRow = "14622\t1\t5\t13\tSedan/Saloon\t1692965930"
.parse()
.expect("parse");
let value: SpecRow = "14622\t0\t2\t3\tLithium-Ion/Li-Ion\t1707830521"
.parse()
.expect("parse");
assert!(key.is_key);
assert!(!value.is_key);
assert_eq!(key.row_id, value.row_id);
}
}