git_rs 0.1.0

A (work in progress) pure rust git implementation.
Documentation
use flate2::bufread::ZlibDecoder;
use std::io::prelude::*;
use std::io::{ BufReader };

use crate::stores::{ Queryable, StorageSet };
use crate::errors::{ Result, ErrorKind };
use crate::objects::Type;
use crate::id::Id;

type Reader = Fn(&Id) -> Result<Option<Box<std::io::Read>>> + Send + Sync;

pub struct Store {
    read: Box<Reader>,
    filter: [bool; 256]
}

impl Store {
    pub fn new<C>(func: C, filter: Option<[bool; 256]>) -> Self
        where C: Fn(&Id) -> Result<Option<Box<std::io::Read>>> + 'static + Send + Sync {
        let filter = match filter {
            Some(xs) => xs,
            None => [true; 256]
        };

        Store {
            read: Box::new(func),
            filter
        }
    }
}

impl Queryable for Store {
    fn get<W: Write, S: Queryable>(&self, id: &Id, output: &mut W, _: &StorageSet<S>) -> Result<Option<Type>> {
        if !self.filter[id.as_ref()[0] as usize] {
            return Ok(None)
        }

        let maybe_reader = (self.read)(id)?;
        if maybe_reader.is_none() {
            return Ok(None)
        }

        let mut reader = BufReader::new(
            ZlibDecoder::new(BufReader::new(maybe_reader.unwrap()))
        );

        let mut type_vec = Vec::new();
        let mut size_vec = Vec::new();
        enum Mode {
            FindSpace,
            FindNull
        };
        let mut mode = Mode::FindSpace;

        reader.read_until(0x20, &mut type_vec);
        reader.read_until(0, &mut size_vec);

        let loaded_type = match &type_vec[..] {
            b"commit " => Type::Commit,
            b"blob " => Type::Blob,
            b"tree " => Type::Tree,
            b"tag " => Type::Tag,
            &_ => return Err(ErrorKind::BadLooseObject.into())
        };

        std::io::copy(&mut reader, output)?;
        Ok(Some(loaded_type))
    }
}

#[cfg(test)]
mod tests {
    use crate::stores::{ Queryable, StorageSet };
    use crate::objects::Object;
    use crate::id::Id;

    use std::io::Cursor;
    use super::{ Store, ErrorKind };

    #[test]
    fn read_commit_works() {
        let store = Store::new(|_| Ok(Some(Box::new(include_bytes!("../../fixtures/loose_commit") as &[u8]))), None);
        let storage_set = StorageSet::new(());

        let mut stream = Vec::new();
        let option = store.get(&Id::default(), &mut stream, &storage_set).expect("it exploded");
        if let Some(xs) = option {
            let mut readable = Cursor::new(stream);
            let object = xs.load(&mut readable).expect("failed to load");

            if let Object::Commit(commit) = object {
                let message = std::str::from_utf8(commit.message()).expect("not utf8");
                assert_eq!(message, "maybe implement loose store\n");
            } else {
                panic!("expected commit");
            }
        } else {
            panic!("explode");
        }
    }

    #[test]
    fn read_tree_works() {
        let store = Store::new(|_| Ok(Some(Box::new(include_bytes!("../../fixtures/loose_tree") as &[u8]))), None);
        let storage_set = StorageSet::new(());

        let mut stream = Vec::new();
        let option = store.get(&Id::default(), &mut stream, &storage_set).expect("it exploded");
        if let Some(xs) = option {
            let mut readable = Cursor::new(stream);
            let object = xs.load(&mut readable).expect("failed to load");

            if let Object::Tree(tree) = object {
                let mut entries: Vec<&str> = tree.entries().keys()
                    .map(|xs| ::std::str::from_utf8(xs).expect("valid utf8"))
                    .collect();
                entries.sort();
                assert_eq!(entries.join("\n"), ".gitignore\nCargo.toml\nREADME.md\nfixtures\nsrc");
            } else {
                panic!("expected tree");
            }
        } else {
            panic!("explode");
        }
    }

    #[test]
    fn handles_idtoreadable_failures() {
        let store = Store::new(|_| Err(ErrorKind::BadLooseObject.into()), None);
        let storage_set = StorageSet::new(());

        match store.get(&Id::default(), &mut vec![], &storage_set) {
            Ok(_) => panic!("expected failure!"),
            Err(e) => assert_eq!(e.description(), "BadLooseObject")
        };
    }

    #[test]
    fn handles_idtoreadable_misses() {
        let store = Store::new(|_| Ok(None), None);
        let storage_set = StorageSet::new(());

        match store.get(&Id::default(), &mut vec![], &storage_set) {
            Err(_) => panic!("expected success!"),
            Ok(xs) => assert!(xs.is_none())
        };
    }
}