use serde_json; use serde_json::json; use std::error::Error; extern crate custom_error; use custom_error::custom_error; 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` and name of referred value. /// /// For values inside JSON arrays it stores array's /// `Vec` and referred index. /// /// `Invalid` can be used to return a failed state. enum ValueReference<'a> { Object(&'a mut serde_json::Map, String), Array(&'a mut Vec, 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, } impl ToString for File { fn to_string(&self) -> String { self.contents.to_string() } } impl File { /// Creates an empty file that will contain an empty JSON object. pub fn empty() -> File { File { contents: json!({}), } } /// Loads JSON value from the specified file. pub fn load(file_contents: String) -> Result> { 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, new_value: serde_json::Value, ) -> Result<(), IncorrectPointer> { self.touch(pointer)?; match self.contents.pointer_mut(pointer) { Some(v) => *v = new_value, _ => { // If after `touch()` call we still don't have an existing value - // something is wrong with the `pointer` return Err(IncorrectPointer { pointer: pointer.to_owned(), }); } }; Ok(()) } /// Removes (and returns) value specified by theJSON pointer. /// If it did not exist - returns `None`. pub fn remove(&mut self, pointer: &str) -> Option { match self.pointer_to_reference(pointer) { ValueReference::Object(map, variable_name) => map.remove(&variable_name), ValueReference::Array(vec, variable_index) => { if variable_index < vec.len() { return Some(vec.remove(variable_index)); } None } _ => None, } } /// 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() { return Ok(()); } // Otherwise - try to create it match self.pointer_to_reference(pointer) { ValueReference::Object(map, variable_name) => { map.insert(variable_name, json!(null)); } ValueReference::Array(vec, variable_index) => { // We've checked at the beginning of this method that value // at `variable_index` does not exist, which guarantees // that array is to short and we won't shrink it vec.resize(variable_index + 1, json!(null)); } _ => { return Err(IncorrectPointer { pointer: pointer.to_owned(), }) } }; 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; } // Extract variable name (that `pointer` points to) // and reference to it's container // // i.e. given file with '{"obj":{"arr":[1,3,5,2,4]}}', // for pointer `/obj/arr/5`, // it will return, basically, `(&[1,3,5,2,4], "5")` let container_variable_pair = pop_json_pointer(pointer).and_then(move |(path, variable_name)| { match self.contents.pointer_mut(&path) { Some(v) => Some((v, variable_name)), _ => None, } }); let (json_container, variable_name) = match container_variable_pair { Some(v) => v, _ => return ValueReference::Invalid, }; // For arrays we also need to confirm validity of the variable name // and convert it into `usize` match json_container { serde_json::Value::Object(map) => ValueReference::Object(map, variable_name), serde_json::Value::Array(vec) => { let index: usize = match variable_name.parse() { Ok(v) => v, _ => return ValueReference::Invalid, }; ValueReference::Array(vec, index) } _ => ValueReference::Invalid, } } } // 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) { Some(v) => v, _ => { return None; } }; if last_separator_index >= pointer.len() { pointer.pop(); return Some((pointer, String::new())); } let var_name = pointer.split_off(last_separator_index + 1); pointer.pop(); Some((pointer, var_name)) } #[test] fn test_pop_json_pointer() { assert_eq!( pop_json_pointer("/a/b/c/d"), Some(("/a/b/c".to_owned(), "d".to_owned())) ); assert_eq!(pop_json_pointer("/"), Some(("".to_owned(), "".to_owned()))); assert_eq!( pop_json_pointer("/a/b/"), Some(("/a/b".to_owned(), "".to_owned())) ); assert_eq!(pop_json_pointer(""), None); // This pointer is incorrect assert_eq!(pop_json_pointer("var"), None); }