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}" } 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> { enum ValueReference<'a> {
Object(&'a mut serde_json::Map<String, serde_json::Value>, String), Object(&'a mut serde_json::Map<String, serde_json::Value>, String),
Array(&'a mut Vec<serde_json::Value>, usize), Array(&'a mut Vec<serde_json::Value>, usize),
Invalid, Invalid,
} }
/// Implements database's file by wrapping JSON value (`serde_json::Value`)
/// and providing several convenient accessor methods.
#[derive(Debug)] #[derive(Debug)]
pub struct File { pub struct File {
/// File's full contents, normally a JSON object.
contents: serde_json::Value, contents: serde_json::Value,
} }
@ -27,30 +41,49 @@ impl ToString for File {
} }
impl File { impl File {
/// Creates an empty file that will contain an empty JSON object.
pub fn empty() -> File { pub fn empty() -> File {
File { File {
contents: json!({}), 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 { Ok(File {
contents: serde_json::from_str(&file_contents)?, contents: serde_json::from_str(&file_contents)?,
}) })
} }
/// Returns file's "root", - JSON value contained inside it.
pub fn root(&self) -> &serde_json::Value { pub fn root(&self) -> &serde_json::Value {
&self.contents &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> { pub fn get(&self, pointer: &str) -> Option<&serde_json::Value> {
self.contents.pointer(pointer) 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 { pub fn contains(&self, pointer: &str) -> bool {
self.get(pointer) != None 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( pub fn insert(
&mut self, &mut self,
pointer: &str, pointer: &str,
@ -70,6 +103,8 @@ impl File {
Ok(()) 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> { pub fn remove(&mut self, pointer: &str) -> Option<serde_json::Value> {
match self.pointer_to_reference(pointer) { match self.pointer_to_reference(pointer) {
ValueReference::Object(map, variable_name) => map.remove(&variable_name), 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>) { fn touch(&mut self, pointer: &str) -> (Result<(), IncorrectPointer>) {
// If value is present - we're done // If value is present - we're done
if pointer.is_empty() || self.contents.pointer_mut(pointer).is_some() { if pointer.is_empty() || self.contents.pointer_mut(pointer).is_some() {
@ -108,6 +147,7 @@ impl File {
Ok(()) Ok(())
} }
/// Helper method, - converts JSON pointer into auxiliary `ValueReference` enum.
fn pointer_to_reference<'a>(&'a mut self, pointer: &str) -> ValueReference<'a> { fn pointer_to_reference<'a>(&'a mut self, pointer: &str) -> ValueReference<'a> {
if pointer.is_empty() { if pointer.is_empty() {
return ValueReference::Invalid; return ValueReference::Invalid;
@ -145,6 +185,7 @@ impl File {
} }
} }
// Helper function to disassemble JSON path.
fn pop_json_pointer(pointer: &str) -> Option<(String, String)> { fn pop_json_pointer(pointer: &str) -> Option<(String, String)> {
let mut pointer = pointer.to_string(); let mut pointer = pointer.to_string();
let last_separator_index = match pointer.rfind(JSON_POINTER_SEPARATOR) { 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_name = get_file_name(path.as_path());
let file_contents = fs::read_to_string(&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 { if files.len() > 0 {
return Ok(Some(Group { return Ok(Some(Group {

View File

@ -17,7 +17,8 @@ pub mod io;
custom_error! { pub DBError custom_error! { pub DBError
InvalidEntityName{entity_name: String} = r#"Cannot use {entity_name} for file or group"#, InvalidEntityName{entity_name: String} = r#"Cannot use {entity_name} for file or group"#,
NoGroup{group_name: String} = r#"Group "{group_name}" does not exist"#, 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"#, GroupAlreadyExists{group_name: String} = r#"Group "{group_name}" already exists"#,
FileAlreadyExists{group_name: String, file_name: String} = r#"File "{file_name}" already exists\ FileAlreadyExists{group_name: String, file_name: String} = r#"File "{file_name}" already exists\
in the group "{group_name}""#, in the group "{group_name}""#,