corgi-rs 0.2.4

Vehicle VIN decoding library based on NHTSA database for Rust
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(Archive, Serialize, Deserialize, Debug, PartialEq)]
pub struct Lookup {
    pub pattern: String,
    pub element_code: String,
    pub resolved: String,
    pub element_weight: Option<usize>,
}

impl RkyvDeserialize<Lookup> for ArchivedLookup {}
impl RkyvSerialize for Lookup {}

impl<'a> Saveable<'a> for Lookup {
    fn base_file_name() -> Cow<'a, str> {
        Cow::Borrowed("schema_id_lookup")
    }
}

impl FromStr for Lookup {
    type Err = std::io::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let parts = s.split(',').collect::<Vec<_>>();
        if let Some(pattern) = parts.get(0)
            && let Some(element_code) = parts.get(1)
            && let Some(resolved) = parts.get(2)
        {
            let element_weight = parts.get(3).map(|ew| ew.parse::<usize>().ok()).flatten();
            return Ok(Lookup {
                pattern: pattern.to_string(),
                element_code: element_code.to_string(),
                resolved: resolved.to_string(),
                element_weight,
            });
        };

        Err(std::io::Error::other(
            "could not construct Lookup struct from given str",
        ))
    }
}

#[derive(Archive, Serialize, Deserialize, Debug, PartialEq)]
pub struct Make {
    pub make: String,
}

impl RkyvDeserialize<Make> for ArchivedMake {}
impl RkyvSerialize for Make {}

impl<'a> Saveable<'a> for Make {
    fn base_file_name() -> Cow<'a, str> {
        Cow::Borrowed("wmi_make")
    }
}

impl FromStr for Make {
    type Err = std::io::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Make {
            make: s.to_string(),
        })
    }
}

#[derive(Archive, Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct SchemaId {
    pub schema_id: String,
}

impl RkyvDeserialize<SchemaId> for ArchivedSchemaId {}
impl RkyvSerialize for SchemaId {}

impl<'a> Saveable<'a> for SchemaId {
    fn base_file_name() -> Cow<'a, str> {
        Cow::Borrowed("wmi_schema_id")
    }
}

impl FromStr for SchemaId {
    type Err = std::io::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(SchemaId {
            schema_id: s.to_string(),
        })
    }
}

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 = None;
        let mut values = Vec::new();

        while let Some(line) = self.peek() {
            let (key, rest) = line.split_once(',').expect("must have comma");

            match current_key {
                Some(ck) if ck != key => {
                    return Some((current_key.expect("must have been set"), values));
                }
                None => {
                    current_key = Some(key);
                }
                Some(_) => {}
            };

            values.push(rest);
            self.next();
        }

        if let Some(key) = current_key {
            Some((key, values))
        } else {
            None
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::UntilNextKey;

    #[test]
    fn test_until_next_key() {
        let csv = include_str!("../assets/wmi_make.csv");
        let mut iter = csv.lines().skip(1).peekable();
        let (k1, v1) = iter.next_key().expect("must be some");
        let (k2, v2) = iter.next_key().expect("must be some");

        assert_eq!(k1, "SCF");
        assert_eq!(v1, vec!["Aston Martin"]);
        assert_eq!(k2, "SAJ");
        assert_eq!(v2, vec!["Jaguar"]);
    }

    #[test]
    fn test_until2_next_key() {
        let csv = include_str!("../assets/wmi_make.csv");
        let mut iter = csv.lines().skip(1).peekable();

        let mut last_key = "";
        while let Some((k, _)) = iter.next_key() {
            last_key = k
        }
        assert_eq!(last_key, "4C9753");
    }
}