Add group/file management methods

This commit is contained in:
Anton Tarasenko 2020-11-22 19:45:43 +07:00
parent 1572d7fb5a
commit 3432ea9857
2 changed files with 289 additions and 45 deletions

View File

@ -15,10 +15,14 @@ use custom_error::custom_error;
const JSON_POINTER_SEPARATOR: &str = "/"; const JSON_POINTER_SEPARATOR: &str = "/";
custom_error! {DBError custom_error! { pub DBError
NotDirectory{path: String} = "Path to database should point at the directory: {path}", NotDirectory{path: String} = "Path to the database should point at a directory: {path}",
NoFile{group_name: String, file_name: String} = r#"There is no "{file_name}" file in group "{group_name}"."#, InvalidEntityName{entity_name: String} = r#"Cannot use {entity_name} for file or group"#,
IncorrectPointer{pointer: String} = "Incorrect pointer is specified: {pointer}.", NoGroup{group_name: String} = r#"Group "{group_name}" does not exist"#,
NoFile{group_name: String, file_name: String} = r#"There is no "{file_name}" file in group "{group_name}""#,
GroupAlreadyExists{group_name: String} = r#"Group "{group_name}" already exists"#,
FileAlreadyExists{group_name: String, file_name: String} = r#"File "{file_name}" already exists in the group "{group_name}""#,
IncorrectPointer{pointer: String} = "Incorrect pointer is specified: {pointer}",
} }
enum ValueReference<'a> { enum ValueReference<'a> {
@ -100,6 +104,77 @@ impl Database {
self.groups.iter().position(|x| x.name.eq(group_name)) self.groups.iter().position(|x| x.name.eq(group_name))
} }
pub fn contains_group(&self, group_name: &str) -> bool {
self.group_index(group_name).is_some()
}
pub fn create_group(&mut self, group_name: &str) -> Result<(), DBError> {
verify_name(group_name)?;
if self.group_index(group_name).is_some() {
return Err(DBError::GroupAlreadyExists {
group_name: group_name.to_owned(),
});
}
self.groups.push(Group {
name: group_name.to_owned(),
files: HashMap::new(),
});
Ok(())
}
pub fn remove_group(&mut self, group_name: &str) -> Result<(), DBError> {
match self.group_index(group_name) {
Some(index) => self.groups.remove(index),
_ => {
return Err(DBError::NoGroup {
group_name: group_name.to_owned(),
})
}
};
Ok(())
}
pub fn contains_file(&self, (group_name, file_name): FileID) -> bool {
match self.group_index(group_name) {
Some(index) => self.groups[index].files.contains_key(&file_name.to_owned()),
_ => false,
}
}
pub fn create_file(&mut self, (group_name, file_name): FileID) -> Result<(), DBError> {
let group_name = group_name.to_owned();
let file_name = file_name.to_owned();
verify_name(&file_name)?;
let group_index = match self.group_index(&group_name) {
Some(index) => index,
_ => return Err(DBError::NoGroup { group_name }),
};
if self.groups[group_index].files.contains_key(&file_name) {
return Err(DBError::FileAlreadyExists {
group_name,
file_name,
});
}
self.groups[group_index].files.insert(file_name, json!({}));
Ok(())
}
pub fn remove_file(&mut self, (group_name, file_name): FileID) -> Result<(), DBError> {
let group_name = group_name.to_owned();
let file_name = file_name.to_owned();
let group_index = match self.group_index(&group_name) {
Some(index) => index,
_ => return Err(DBError::NoGroup { group_name }),
};
if self.groups[group_index].files.remove(&file_name).is_none() {
return Err(DBError::NoFile {
group_name,
file_name,
});
}
Ok(())
}
fn as_json_mut(&mut self, (group_name, file_name): FileID) -> Option<&mut serde_json::Value> { fn as_json_mut(&mut self, (group_name, file_name): FileID) -> Option<&mut serde_json::Value> {
match self.group_index(group_name) { match self.group_index(group_name) {
Some(index) => self.groups[index].files.get_mut(&file_name.to_owned()), Some(index) => self.groups[index].files.get_mut(&file_name.to_owned()),
@ -129,7 +204,7 @@ impl Database {
self.get_json(file_id, pointer).map(|x| x.to_string()) self.get_json(file_id, pointer).map(|x| x.to_string())
} }
pub fn contains(&self, file_id: FileID, pointer: &str) -> bool { pub fn contains_value(&self, file_id: FileID, pointer: &str) -> bool {
self.get_json(file_id, pointer) != None self.get_json(file_id, pointer) != None
} }
@ -166,33 +241,34 @@ impl Database {
&mut self, &mut self,
(group_name, file_name): FileID, (group_name, file_name): FileID,
pointer: &str, pointer: &str,
) -> Result<(), Box<dyn Error>> { ) -> Option<serde_json::Value> {
let file_json = match self.as_json_mut((group_name, file_name)) { let file_json = match self.as_json_mut((group_name, file_name)) {
Some(file_json) => file_json, Some(file_json) => file_json,
_ => { _ => return None,
return Err(Box::new(DBError::NoFile {
group_name: group_name.to_owned(),
file_name: file_name.to_owned(),
}))
}
}; };
match pointer_to_reference(file_json, pointer) { match pointer_to_reference(file_json, pointer) {
Some(ValueReference::Object(map, variable_name)) => { Some(ValueReference::Object(map, variable_name)) => map.remove(&variable_name),
map.remove(&variable_name);
}
Some(ValueReference::Array(vec, variable_index)) => { Some(ValueReference::Array(vec, variable_index)) => {
if variable_index < vec.len() { if variable_index < vec.len() {
vec.remove(variable_index); return Some(vec.remove(variable_index));
}
None
}
_ => None,
} }
} }
_ => {
return Err(Box::new(DBError::IncorrectPointer {
pointer: pointer.to_owned(),
}))
} }
};
Ok(()) fn verify_name(entity_name: &str) -> Result<(), DBError> {
let is_valid = entity_name
.chars()
.all(|x| x.is_ascii_alphabetic() || x.is_ascii_digit());
if is_valid {
return Ok(());
} }
return Err(DBError::InvalidEntityName {
entity_name: entity_name.to_owned(),
});
} }
fn load_group(group_path: &path::Path) -> Result<Group, Box<dyn Error>> { fn load_group(group_path: &path::Path) -> Result<Group, Box<dyn Error>> {
@ -293,9 +369,6 @@ fn get_file_name(path: &path::Path) -> String {
.unwrap_or_default() .unwrap_or_default()
.to_string() .to_string()
} }
// TODO add tests for remove
// TODO add tests for panics (both add and remove)
// TODO add file addition/removal
// TODO add db saving // TODO add db saving
// TODO make sure file's main value not being an object won't break anything // TODO make sure file's main value not being an object won't break anything

View File

@ -7,7 +7,7 @@ const TEST_DB_PATH: &str = "./fixtures/database";
const NO_DB_MESSAGE: &str = "Can not find/load test database"; const NO_DB_MESSAGE: &str = "Can not find/load test database";
#[test] #[test]
fn group_names() { fn db_group_names() {
let db = Database::new(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE); let db = Database::new(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE);
let names = db.group_names(); let names = db.group_names();
@ -17,7 +17,7 @@ fn group_names() {
} }
#[test] #[test]
fn file_names() { fn db_file_names() {
let db = Database::new(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE); let db = Database::new(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE);
let names_admin = db.file_names_in("administration"); let names_admin = db.file_names_in("administration");
@ -29,6 +29,110 @@ fn file_names() {
assert_eq!(names_game.len(), 2); assert_eq!(names_game.len(), 2);
} }
#[test]
fn db_group_check() {
let db = Database::new(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE);
assert!(db.contains_group("game"));
assert!(db.contains_group("administration"));
assert!(!db.contains_group("perks"));
assert!(!db.contains_group("7random7"));
assert!(!db.contains_group(""));
}
#[test]
fn db_group_remove() -> Result<(), Box<dyn Error>> {
let mut db = Database::new(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE);
// Success
db.remove_group("administration")?;
db.remove_group("game")?;
assert!(!db.contains_group("administration"));
assert!(!db.contains_group("game"));
// Failure
db.remove_group("test")
.expect_err("Testing whether removing non-existent groups with incorrect ASCII characters causes errors.");
db.remove_group("administration")
.expect_err("Testing whether removing non-existent groups with incorrect ASCII characters causes errors.");
db.remove_group("game group").expect_err(
"Testing whether removing non-existent groups with whitespace characters causes errors.",
);
Ok(())
}
#[test]
fn db_group_create() -> Result<(), Box<dyn Error>> {
let mut db = Database::new(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE);
// Success
db.create_group("7random7")?;
assert!(db.contains_group("7random7"));
assert!(db.contains_group("game"));
// Failure
db.create_group("my_group").expect_err(
"Testing whether creating groups with incorrect ASCII characters causes errors.",
);
db.create_group("my group")
.expect_err("Testing whether creating groups with whitespace characters causes errors.");
db.create_group("Жgroup")
.expect_err("Testing whether creating groups with non-ASCII characters causes errors.");
// Create after removal
db.remove_group("game")?;
assert!(!db.contains_group("game"));
db.create_group("game")?;
assert!(db.contains_group("game"));
assert!(db.file_names_in("game").is_empty());
Ok(())
}
#[test]
fn db_file_check() {
let db = Database::new(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE);
// Success
assert!(db.contains_file(("administration", "registered")));
assert!(db.contains_file(("game", "general")));
assert!(db.contains_file(("game", "perks")));
// Failure
assert!(!db.contains_file(("game", "perk")));
assert!(!db.contains_file(("games", "perks")));
assert!(!db.contains_file(("random", "rnd_file")));
}
#[test]
fn db_file_create() -> Result<(), Box<dyn Error>> {
let mut db = Database::new(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE);
// Success
db.create_file(("administration", "secrets"))?;
assert!(db.contains_file(("administration", "secrets")));
assert_eq!(db.as_string(("administration", "secrets")).unwrap(), "{}");
assert!(db.contains_file(("game", "perks")));
// Failure
db.create_file(("administration", "secrets"))
.expect_err("Testing whether creating existing file causes errors.");
db.create_file(("none", "secrets"))
.expect_err("Testing whether creating existing file in non-existent group causes errors.");
db.create_file(("game", "sec_rets"))
.expect_err("Testing whether creating existing file with invalid name causes errors.");
Ok(())
}
#[test]
fn db_file_remove() -> Result<(), Box<dyn Error>> {
let mut db = Database::new(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE);
// Success
db.remove_file(("administration", "registered"))?;
assert!(!db.contains_file(("administration", "registered")));
assert!(db.contains_group("administration"));
db.remove_file(("game", "perks"))?;
assert_eq!(db.file_names_in("game").len(), 1);
// Failure
db.remove_file(("administration", "registered"))
.expect_err("Testing whether removing non-existent files causes errors.");
db.remove_file(("administration", "never"))
.expect_err("Testing whether removing non-existent files causes errors.");
db.remove_file(("never", "file"))
.expect_err("Testing whether removing non-existent files causes errors.");
Ok(())
}
#[test] #[test]
fn db_json_contents() { fn db_json_contents() {
let db = Database::new(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE); let db = Database::new(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE);
@ -99,50 +203,117 @@ fn db_contains_check() {
let registered_id = ("administration", "registered"); let registered_id = ("administration", "registered");
let perks_id = ("game", "perks"); let perks_id = ("game", "perks");
// These exist // These exist
assert!(db.contains(registered_id, "/76561198025127722/password_hash")); assert!(db.contains_value(registered_id, "/76561198025127722/password_hash"));
assert!(db.contains(registered_id, "/76561198044316328/groups")); assert!(db.contains_value(registered_id, "/76561198044316328/groups/0"));
assert!(db.contains(perks_id, "/76561198025127722/headshots")); assert!(db.contains_value(perks_id, "/76561198025127722/headshots"));
assert!(db.contains(perks_id, "/76561198044316328")); assert!(db.contains_value(perks_id, "/76561198044316328"));
// These do not exist // These do not exist
assert!(!db.contains(registered_id, "/76561198025127722/password/")); assert!(!db.contains_value(registered_id, "/76561198025127722/password/"));
assert!(!db.contains(registered_id, "/76561198044316328/groups/2")); assert!(!db.contains_value(registered_id, "/76561198044316328/groups/2"));
assert!(!db.contains(perks_id, "/76561198025127722/assault_rifle_damage/9067")); assert!(!db.contains_value(perks_id, "/76561198025127722/assault_rifle_damage/9067"));
assert!(!db.contains(perks_id, "/76561198044316328/headshots")); assert!(!db.contains_value(perks_id, "/76561198044316328/headshots"));
} }
#[test] #[test]
fn db_set_success() -> Result<(), Box<dyn Error>> { fn db_set_success() -> Result<(), Box<dyn Error>> {
let mut db = Database::new(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE); let mut db = Database::new(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE);
let file_id = ("administration", "registered"); let registered_id = ("administration", "registered");
let general_id = ("game", "general");
// Modify existing // Modify existing
db.set_json(file_id, "/76561198025127722/ip_lock", json!(false))?; db.set_json(registered_id, "/76561198025127722/ip_lock", json!(false))?;
assert_eq!( assert_eq!(
db.get_string(file_id, "/76561198025127722/ip_lock") db.get_string(registered_id, "/76561198025127722/ip_lock")
.unwrap(), .unwrap(),
"false" "false"
); );
db.set_json( db.set_json(
file_id, registered_id,
"/76561198044316328/password_hash", "/76561198044316328/password_hash",
json!({"var":13524}), json!({"var":13524}),
)?; )?;
assert_eq!( assert_eq!(
db.get_string(file_id, "/76561198044316328/password_hash") db.get_string(registered_id, "/76561198044316328/password_hash")
.unwrap(), .unwrap(),
r#"{"var":13524}"# r#"{"var":13524}"#
); );
// Reset whole file // Reset whole file
db.set_json(file_id, "", json!({}))?; db.set_json(registered_id, "", json!({}))?;
assert_eq!(db.as_json(file_id).unwrap().to_string(), "{}"); assert_eq!(db.as_json(registered_id).unwrap().to_string(), "{}");
// Add new values // Add new values
db.set_json(file_id, "/new_var", json!([42, {"word":"life"}, null]))?; db.set_json(
registered_id,
"/new_var",
json!([42, {"word":"life"}, null]),
)?;
assert_eq!( assert_eq!(
db.as_json(file_id).unwrap().to_string(), db.as_json(registered_id).unwrap().to_string(),
r#"{"new_var":[42,{"word":"life"},null]}"# r#"{"new_var":[42,{"word":"life"},null]}"#
); );
db.set_json(
general_id,
"/76561198025127722/achievements/5",
json!("kf:bugged"),
)?;
assert_eq!(
db.get_string(general_id, "/76561198025127722/achievements")
.unwrap(),
r#"["kf:LabCleaner","kf:ChickenFarmer","scrn:playedscrn",null,null,"kf:bugged"]"#
);
Ok(()) Ok(())
} }
#[test]
fn db_set_failure() {
let mut db = Database::new(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE);
let file_id = ("administration", "registered");
let imaginary_file_id = ("general", "everything");
db.set_json(imaginary_file_id, "", json!(null))
.expect_err("Testing panic at missing file.");
db.set_json(file_id, "/76561198025127722/dir/var", json!(null))
.expect_err("Testing panic at trying to set a value in non-existing object/array.");
db.set_json(file_id, "/76561198044316328/groups/d", json!(null))
.expect_err("Testing panic at trying to set a value at non-numeric index in an array.");
db.set_json(file_id, "/76561198044316328/groups/-1", json!(null))
.expect_err("Testing panic at trying to set a value at negative index in an array.");
}
#[test]
fn db_remove() {
let mut db = Database::new(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE);
let file_id = ("administration", "registered");
// Removing non-existent value
assert_eq!(db.remove(file_id, "/76561198025127722/something"), None);
// Remove simple value
assert_eq!(
db.remove(file_id, "/76561198025127722/password_hash")
.unwrap(),
json!("fce798e0804dfb217f929bdba26745024f37f6b6ba7406f3775176e20dd5089d")
);
assert!(!db.contains_value(file_id, "/76561198025127722/password_hash"));
// Remove complex value (array)
assert_eq!(
db.remove(file_id, "/76561198044316328/groups").unwrap(),
json!(["admin"])
);
assert!(!db.contains_value(file_id, "/76561198044316328/groups/0"));
assert!(!db.contains_value(file_id, "/76561198044316328/groups"));
// Remove array elements
assert_eq!(
db.remove(file_id, "/76561198025127722/allowed_ips/0")
.unwrap(),
json!("127.0.0.1")
);
assert!(db.contains_value(file_id, "/76561198025127722/allowed_ips/0"));
assert!(!db.contains_value(file_id, "/76561198025127722/allowed_ips/1"));
assert_eq!(
db.remove(file_id, "/76561198025127722/allowed_ips/0")
.unwrap(),
json!("192.168.0.100")
);
assert!(db.contains_value(file_id, "/76561198025127722/allowed_ips"));
assert!(!db.contains_value(file_id, "/76561198025127722/allowed_ips/0"));
}
#[test] #[test]
fn test_pop_json_pointer() { fn test_pop_json_pointer() {
assert_eq!( assert_eq!(