Solution for exercise 1: open & close


#![allow(unused)]
fn main() {
use libc::{c_void, size_t};
use std::ffi::CString;
use std::path::Path;
use std::ptr;
use std::ptr::NonNull;

use leveldb_sys::{
    leveldb_close, leveldb_create_iterator, leveldb_free, leveldb_get, leveldb_iter_destroy,
    leveldb_iter_next, leveldb_iter_seek_to_first, leveldb_iter_valid, leveldb_iter_value,
    leveldb_iterator_t, leveldb_open, leveldb_options_create, leveldb_options_destroy,
    leveldb_options_set_create_if_missing, leveldb_options_t, leveldb_put,
    leveldb_readoptions_create, leveldb_readoptions_destroy, leveldb_readoptions_t, leveldb_t,
    leveldb_writeoptions_create, leveldb_writeoptions_destroy, leveldb_writeoptions_t,
};

struct DBHandle {
    ptr: NonNull<leveldb_t>,
}

impl Drop for DBHandle {
    fn drop(&mut self) {
        unsafe { leveldb_close(self.ptr.as_ptr()) }
    }
}

pub struct Options {
    ptr: NonNull<leveldb_options_t>,
}

impl Drop for Options {
    fn drop(&mut self) {
        unsafe { leveldb_options_destroy(self.ptr.as_ptr()) }
    }
}

impl Options {
    pub fn new() -> Options {
        unsafe {
            let ptr = leveldb_options_create();
            Options {
                ptr: NonNull::new_unchecked(ptr),
            }
        }
    }

    pub fn create_if_missing(&mut self, value: bool) {
        unsafe { leveldb_options_set_create_if_missing(self.as_ptr(), value as u8) }
    }

    fn as_ptr(&self) -> *mut leveldb_options_t {
        self.ptr.as_ptr()
    }
}

pub struct Database {
    handle: DBHandle,
}

unsafe fn into_rust_string(ptr: *const i8) -> String {
    let error_s = CStr::from_ptr(ptr).to_string_lossy().to_string();
    leveldb_free(ptr as *mut c_void);
    error_s
}

#[derive(Debug, Eq, PartialEq)]
pub enum Error {
    OpenFail(String),
    InvalidString,
}

impl Database {
    pub fn open<P: AsRef<Path>>(path: P, options: Options) -> Result<Database, Error> {
        let mut error = ptr::null_mut();

        let c_string = CString::new(path.as_ref().to_str().ok_or(Error::InvalidString)?)
            .map_err(|_| Error::InvalidString)?;
        unsafe {
            let db = leveldb_open(options.as_ptr(), c_string.as_ptr(), &mut error);

            if error == ptr::null_mut() {
                Ok(Database {
                    handle: DBHandle {
                        ptr: NonNull::new_unchecked(db),
                    },
                })
            } else {
                Err(Error::OpenFail(into_rust_string(error)))
            }
        }
    }
}

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

    #[test]
    fn test_open() {
        let tmp = TempDir::new("test_open").unwrap();

        let mut options = Options::new();
        options.create_if_missing(true);

        let database = Database::open(tmp.path().join("database"), options);
        assert!(database.is_ok());
    }
}
}