Document database's file.rs

This commit is contained in:
Anton Tarasenko 2020-11-28 04:42:26 +07:00
parent 64b5553863
commit 2701729dcf
3 changed files with 45 additions and 3 deletions

View File

@ -9,14 +9,28 @@ const JSON_POINTER_SEPARATOR: &str = "/";
custom_error! { pub IncorrectPointer{pointer: String} = "Incorrect pointer is specified: {pointer}" }
/// This is a enum that used internally to refer to values inside of
/// JSON (their serde implementation) objects and arrays.
/// This enum helps to simplify module's code.
///
/// For values inside of JSON object it stores object's
/// `Map<String, serde_json::Value>` and name of referred value.
///
/// For values inside JSON arrays it stores array's
/// `Vec<serde_json::Value>` and referred index.
///
/// `Invalid` can be used to return a failed state.
enum ValueReference<'a> {
Object(&'a mut serde_json::Map<String, serde_json::Value>, String),
Array(&'a mut Vec<serde_json::Value>, usize),
Invalid,
}
/// Implements database's file by wrapping JSON value (`serde_json::Value`)
/// and providing several convenient accessor methods.
#[derive(Debug)]
pub struct File {
/// File's full contents, normally a JSON object.
contents: serde_json::Value,
}
@ -27,30 +41,49 @@ impl ToString for File {
}
impl File {
/// Creates an empty file that will contain an empty JSON object.
pub fn empty() -> File {
File {
contents: json!({}),
}
}
pub fn new(file_contents: String) -> Result<File, Box<dyn Error>> {
/// Loads JSON value from the specified file.
pub fn load(file_contents: String) -> Result<File, Box<dyn Error>> {
Ok(File {
contents: serde_json::from_str(&file_contents)?,
})
}
/// Returns file's "root", - JSON value contained inside it.
pub fn root(&self) -> &serde_json::Value {
&self.contents
}
/// Attempts to return JSON value, corresponding to the given JSON pointer.
/// `None` if the value is missing.
pub fn get(&self, pointer: &str) -> Option<&serde_json::Value> {
self.contents.pointer(pointer)
}
/// Checks if values at a given JSON pointer exists.
/// Returns `true` if it does.
pub fn contains(&self, pointer: &str) -> bool {
self.get(pointer) != None
}
/// Inserts new JSON value inside this file
/// (possibly in some sub-object/array).
///
/// Given pointer must point at new value:
/// 1. If it already exists, - it will be overwritten.
/// 2. If it does not exist, but it's parent object/array does -
/// it will be added.
/// 3. Otherwise an error will be raise.
///
/// If array needs to be expanded, - missing values will be filled
/// with `json!(null)`, i.e. inserting `7` at index `5` in array `[1, 2, 3]`
/// will produce `[1, 2, 3, null, null, 7]`.
pub fn insert(
&mut self,
pointer: &str,
@ -70,6 +103,8 @@ impl File {
Ok(())
}
/// Removes (and returns) value specified by theJSON pointer.
/// If it did not exist - returns `None`.
pub fn remove(&mut self, pointer: &str) -> Option<serde_json::Value> {
match self.pointer_to_reference(pointer) {
ValueReference::Object(map, variable_name) => map.remove(&variable_name),
@ -83,6 +118,10 @@ impl File {
}
}
/// Helper method to create a value if missing (as `json!(null)`).
/// Can only be done if parent container already exists.
///
/// For specifics refer to `insert()` method.
fn touch(&mut self, pointer: &str) -> (Result<(), IncorrectPointer>) {
// If value is present - we're done
if pointer.is_empty() || self.contents.pointer_mut(pointer).is_some() {
@ -108,6 +147,7 @@ impl File {
Ok(())
}
/// Helper method, - converts JSON pointer into auxiliary `ValueReference` enum.
fn pointer_to_reference<'a>(&'a mut self, pointer: &str) -> ValueReference<'a> {
if pointer.is_empty() {
return ValueReference::Invalid;
@ -145,6 +185,7 @@ impl File {
}
}
// Helper function to disassemble JSON path.
fn pop_json_pointer(pointer: &str) -> Option<(String, String)> {
let mut pointer = pointer.to_string();
let last_separator_index = match pointer.rfind(JSON_POINTER_SEPARATOR) {

View File

@ -121,7 +121,7 @@ fn read_group(group_path: &path::Path) -> Result<Option<Group>, Box<dyn Error>>
}
let file_name = get_file_name(path.as_path());
let file_contents = fs::read_to_string(&path)?;
files.insert(file_name, File::new(file_contents)?);
files.insert(file_name, File::load(file_contents)?);
}
if files.len() > 0 {
return Ok(Some(Group {

View File

@ -17,7 +17,8 @@ pub mod io;
custom_error! { pub DBError
InvalidEntityName{entity_name: String} = r#"Cannot use {entity_name} for file or group"#,
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}""#,
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}""#,