morethantext-web/src/morethantext/databases.rs

239 lines
7.1 KiB
Rust

use super::{DBError, FileData};
use std::{collections::HashMap, slice, str};
#[derive(Clone)]
pub struct Databases {
db_map: HashMap<String, String>,
}
impl Databases {
pub fn new() -> Self {
Self {
db_map: HashMap::new(),
}
}
fn add_database(&mut self, name: &str, id: &str) -> Result<String, DBError> {
if name.len() == 0 {
return Err(DBError::new("database names cannot be empty"));
} else if self.db_map.contains_key(name) {
return Err(DBError::new("database already exists"));
} else {
self.db_map.insert(name.to_string(), id.to_string());
Ok(id.to_string())
}
}
fn get_database(&self, name: &str) -> Option<String> {
self.db_map.get(name).cloned()
}
fn show(&self) -> Vec<String> {
let mut names: Vec<String> = self.db_map.clone().into_keys().collect();
names.sort();
names
}
}
impl FileData<Self> for Databases {
fn to_bytes(&self) -> Vec<u8> {
let mut output = Vec::new();
for (name, id) in self.db_map.iter() {
output.append(&mut name.as_bytes().to_vec());
output.push(0);
output.append(&mut id.as_bytes().to_vec());
output.push(0);
}
output
}
fn from_bytes(data: &mut slice::Iter<u8>) -> Result<Self, DBError> {
let mut output = Databases::new();
let mut name: Vec<u8> = Vec::new();
let mut id: Vec<u8> = Vec::new();
let mut get_id = false;
let mut letter: u8;
loop {
match data.next() {
Some(a) => letter = a.clone(),
None => {
if !name.is_empty() {
return Err(DBError::new("file corruption"));
}
break;
}
}
if letter == 0 {
if get_id {
match output
.add_database(str::from_utf8(&name).unwrap(), str::from_utf8(&id).unwrap())
{
Ok(_) => (),
Err(err) => {
let mut error = DBError::new("file corruption");
error.add_source(err);
return Err(error);
}
};
name.clear();
id.clear();
}
get_id = !get_id;
} else {
if get_id {
id.push(letter);
} else {
name.push(letter);
}
}
}
Ok(output)
}
}
#[cfg(test)]
mod functions {
use super::*;
#[test]
fn add_entry() {
let name = "fred";
let id = "123456";
let mut dbs = Databases::new();
let output = dbs.add_database(name, id).unwrap();
assert_eq!(output, id.to_string());
}
#[test]
fn add_entry_no_name() {
let name = "";
let id = "123456";
let mut dbs = Databases::new();
match dbs.add_database(name, id) {
Ok(_) => assert!(false, "There should have been an error"),
Err(err) => assert_eq!(err.to_string(), "database names cannot be empty"),
}
}
#[test]
fn entry_cannot_be_over_written() {
let name = "barney";
let id = "abcde";
let mut dbs = Databases::new();
dbs.add_database(name, id).unwrap();
match dbs.add_database(name, "09876") {
Ok(_) => assert!(false, "There should have been an error"),
Err(err) => assert_eq!(err.to_string(), "database already exists"),
}
let output = dbs.get_database(name);
assert_eq!(output, Some(id.to_string()));
}
#[test]
fn get_bad_database() {
let dbs = Databases::new();
let output = dbs.get_database("missing");
assert_eq!(output, None);
}
#[test]
fn list_databases() {
let mut dbs = Databases::new();
dbs.add_database("zebra", "a").unwrap();
dbs.add_database("alpha", "a").unwrap();
dbs.add_database("charlie", "a").unwrap();
dbs.add_database("wilma", "a").unwrap();
dbs.add_database("frank", "a").unwrap();
let expected = ["alpha", "charlie", "frank", "wilma", "zebra"];
let output = dbs.show();
assert_eq!(output, expected);
}
}
#[cfg(test)]
mod filedata {
use super::*;
use std::error::Error;
#[test]
fn to_bytes_new() {
let dbs = Databases::new();
let expected: Vec<u8> = Vec::new();
let output = dbs.to_bytes();
assert_eq!(output, expected);
}
#[test]
fn to_bytes_with_database() {
let mut dbs = Databases::new();
let name = "something";
let id = "id";
dbs.add_database(name, id).unwrap();
let mut expected: Vec<u8> = Vec::new();
expected.append(&mut name.as_bytes().to_vec());
expected.push(0);
expected.append(&mut id.as_bytes().to_vec());
expected.push(0);
let output = dbs.to_bytes();
assert_eq!(output, expected);
}
#[test]
fn from_bytes() {
let mut dbs = Databases::new();
dbs.add_database("one", "1").unwrap();
dbs.add_database("two", "2").unwrap();
dbs.add_database("three", "3").unwrap();
let data = dbs.to_bytes();
let mut feed = data.iter();
let output = Databases::from_bytes(&mut feed).unwrap();
assert_eq!(output.db_map, dbs.db_map);
}
#[test]
fn from_bytes_incomplete_name() {
let data = "notName".as_bytes();
let mut feed = data.iter();
match Databases::from_bytes(&mut feed) {
Ok(_) => assert!(false, "This should have failed."),
Err(err) => assert_eq!(err.to_string(), "file corruption"),
}
}
#[test]
fn from_bytes_incomplete_id() {
let mut data = "proper".as_bytes().to_vec();
data.push(0);
data.append(&mut "nope".as_bytes().to_vec());
let mut feed = data.iter();
match Databases::from_bytes(&mut feed) {
Ok(_) => assert!(false, "This should have failed."),
Err(err) => assert_eq!(err.to_string(), "file corruption"),
}
}
#[test]
fn from_bytes_handles_error() {
let mut data = "duplicate".as_bytes().to_vec();
data.push(0);
data.append(&mut "first".as_bytes().to_vec());
data.push(0);
data.append(&mut "duplicate".as_bytes().to_vec());
data.push(0);
data.append(&mut "second".as_bytes().to_vec());
data.push(0);
let mut feed = data.iter();
match Databases::from_bytes(&mut feed) {
Ok(_) => assert!(false, "This should have failed."),
Err(err) => {
assert_eq!(err.to_string(), "file corruption");
assert!(
err.source().is_some(),
"Should state file corruption cause."
);
assert_eq!(err.source().unwrap().to_string(), "database already exists")
}
}
}
}