rotools 0.3.0

Helpful stuff.
Documentation
pub struct Rows<S> {
    path: std::path::PathBuf,
    p1: std::marker::PhantomData<S>,
}

impl<S> Rows<S>
where
    S: serde::Serialize + serde::de::DeserializeOwned,
{
    pub fn new(path: &str) -> Result<Self, Box<dyn std::error::Error>> {
        let mut url = std::path::PathBuf::new();
        url.push(path);

        if url.extension().map_or(true, |ext| ext != "csv") {
            return Err("Bad extension!")?;
        }

        // TODO should check file exists and/or is valid?

        std::fs::OpenOptions::new()
            .write(true)
            .create(true)
            .open(&url)?;

        Ok(Self {
            path: url,
            p1: std::marker::PhantomData,
        })
    }
    pub fn insert(&self, data: &S) -> Result<(), Box<dyn std::error::Error>> {
        let file = std::fs::OpenOptions::new().append(true).open(&self.path)?;
        let meta = file.metadata()?;

        let mut write = csv::WriterBuilder::new()
            .has_headers(meta.len() == 0)
            .from_writer(file);

        write.serialize(data)?;
        write.flush()?;

        Ok(())
    }
    pub fn overwrite(&self, data: Vec<S>) -> Result<(), Box<dyn std::error::Error>> {
        if data.is_empty() {
            return Err("Data is empty!")?;
        }

        let file = std::fs::File::create(&self.path)?;

        let mut write = csv::Writer::from_writer(file);

        for item in data {
            write.serialize(item)?;
        }
        write.flush()?;

        Ok(())
    }
    pub fn drop(&self) -> Result<(), Box<dyn std::error::Error>> {
        std::fs::File::create(&self.path)?;

        Ok(())
    }
    pub fn read_all(&self) -> Result<Vec<S>, Box<dyn std::error::Error>> {
        let mut read = csv::Reader::from_path(&self.path)?;
        let rdr = read.deserialize::<S>().collect::<Vec<_>>();
        let res: Result<Vec<_>, _> = rdr.into_iter().collect();
        Ok(res?)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[derive(Default, serde::Serialize, serde::Deserialize)]
    struct Scaf {
        ok: bool,
        amount: i32,
    }

    fn get_path() -> String {
        format!(
            "/tmp/{}.csv",
            std::time::SystemTime::now()
                .duration_since(std::time::SystemTime::UNIX_EPOCH)
                .unwrap()
                .as_nanos()
        )
    }

    #[test]
    fn test_create() {
        let db = Rows::<Scaf>::new(&get_path()).unwrap();
        assert_eq!(db.read_all().unwrap().len(), 0);
    }

    #[test]
    fn test_fail() {
        assert!(Rows::<Scaf>::new("").is_err());
        assert!(Rows::<Scaf>::new("data.txt").is_err());
    }

    #[test]
    fn test_insert() {
        let db = Rows::<Scaf>::new(&get_path()).unwrap();

        db.insert(&Scaf::default()).unwrap();
        assert_eq!(db.read_all().unwrap().len(), 1);

        db.insert(&Scaf::default()).unwrap();
        assert_eq!(db.read_all().unwrap().len(), 2);
    }

    #[test]
    fn test_bulk() {
        let db = Rows::<Scaf>::new(&get_path()).unwrap();

        assert!(db.overwrite(vec![]).is_err());
        assert_eq!(db.read_all().unwrap().len(), 0);

        db.overwrite(vec![Scaf::default()]).unwrap();
        assert_eq!(db.read_all().unwrap().len(), 1);

        db.drop().unwrap();
        assert_eq!(db.read_all().unwrap().len(), 0);
    }
}