asdf
This commit is contained in:
		
							parent
							
								
									3432ea9857
								
							
						
					
					
						commit
						d66b8ab090
					
				
							
								
								
									
										113
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										113
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@ -1,5 +1,10 @@
 | 
				
			|||||||
# This file is automatically @generated by Cargo.
 | 
					# This file is automatically @generated by Cargo.
 | 
				
			||||||
# It is not intended for manual editing.
 | 
					# It is not intended for manual editing.
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "autocfg"
 | 
				
			||||||
 | 
					version = "1.0.1"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "avarice"
 | 
					name = "avarice"
 | 
				
			||||||
version = "0.1.0"
 | 
					version = "0.1.0"
 | 
				
			||||||
@ -7,6 +12,7 @@ dependencies = [
 | 
				
			|||||||
 "custom_error 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
					 "custom_error 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
 "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
					 "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
 "serde_json 1.0.59 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
					 "serde_json 1.0.59 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
 | 
					 "simplelog 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
@ -14,6 +20,18 @@ name = "cfg-if"
 | 
				
			|||||||
version = "0.1.10"
 | 
					version = "0.1.10"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "chrono"
 | 
				
			||||||
 | 
					version = "0.4.19"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "libc 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
 | 
					 "num-integer 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
 | 
					 "num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
 | 
					 "time 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
 | 
					 "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "custom_error"
 | 
					name = "custom_error"
 | 
				
			||||||
version = "1.8.0"
 | 
					version = "1.8.0"
 | 
				
			||||||
@ -24,6 +42,11 @@ name = "itoa"
 | 
				
			|||||||
version = "0.4.6"
 | 
					version = "0.4.6"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "libc"
 | 
				
			||||||
 | 
					version = "0.2.80"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "log"
 | 
					name = "log"
 | 
				
			||||||
version = "0.4.11"
 | 
					version = "0.4.11"
 | 
				
			||||||
@ -32,6 +55,23 @@ dependencies = [
 | 
				
			|||||||
 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
					 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "num-integer"
 | 
				
			||||||
 | 
					version = "0.1.44"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
 | 
					 "num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "num-traits"
 | 
				
			||||||
 | 
					version = "0.2.14"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "ryu"
 | 
					name = "ryu"
 | 
				
			||||||
version = "1.0.5"
 | 
					version = "1.0.5"
 | 
				
			||||||
@ -52,11 +92,84 @@ dependencies = [
 | 
				
			|||||||
 "serde 1.0.117 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
					 "serde 1.0.117 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "simplelog"
 | 
				
			||||||
 | 
					version = "0.8.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "chrono 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
 | 
					 "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
 | 
					 "termcolor 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "termcolor"
 | 
				
			||||||
 | 
					version = "1.1.2"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "time"
 | 
				
			||||||
 | 
					version = "0.1.44"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "libc 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
 | 
					 "wasi 0.10.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
 | 
					 "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "wasi"
 | 
				
			||||||
 | 
					version = "0.10.0+wasi-snapshot-preview1"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "winapi"
 | 
				
			||||||
 | 
					version = "0.3.9"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
 | 
					 "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "winapi-i686-pc-windows-gnu"
 | 
				
			||||||
 | 
					version = "0.4.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "winapi-util"
 | 
				
			||||||
 | 
					version = "0.1.5"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "winapi-x86_64-pc-windows-gnu"
 | 
				
			||||||
 | 
					version = "0.4.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[metadata]
 | 
					[metadata]
 | 
				
			||||||
 | 
					"checksum autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
 | 
				
			||||||
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
 | 
					"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
 | 
				
			||||||
 | 
					"checksum chrono 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)" = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
 | 
				
			||||||
"checksum custom_error 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51ac5e99a7fea3ee8a03fa4721a47e2efd3fbb38358fc61192a54d4c6f866c12"
 | 
					"checksum custom_error 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51ac5e99a7fea3ee8a03fa4721a47e2efd3fbb38358fc61192a54d4c6f866c12"
 | 
				
			||||||
"checksum itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
 | 
					"checksum itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
 | 
				
			||||||
 | 
					"checksum libc 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
 | 
				
			||||||
"checksum log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
 | 
					"checksum log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
 | 
				
			||||||
 | 
					"checksum num-integer 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
 | 
				
			||||||
 | 
					"checksum num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
 | 
				
			||||||
"checksum ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
 | 
					"checksum ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
 | 
				
			||||||
"checksum serde 1.0.117 (registry+https://github.com/rust-lang/crates.io-index)" = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
 | 
					"checksum serde 1.0.117 (registry+https://github.com/rust-lang/crates.io-index)" = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
 | 
				
			||||||
"checksum serde_json 1.0.59 (registry+https://github.com/rust-lang/crates.io-index)" = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95"
 | 
					"checksum serde_json 1.0.59 (registry+https://github.com/rust-lang/crates.io-index)" = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95"
 | 
				
			||||||
 | 
					"checksum simplelog 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2b2736f58087298a448859961d3f4a0850b832e72619d75adc69da7993c2cd3c"
 | 
				
			||||||
 | 
					"checksum termcolor 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
 | 
				
			||||||
 | 
					"checksum time 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
 | 
				
			||||||
 | 
					"checksum wasi 0.10.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
 | 
				
			||||||
 | 
					"checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
 | 
				
			||||||
 | 
					"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 | 
				
			||||||
 | 
					"checksum winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
 | 
				
			||||||
 | 
					"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,7 @@ edition = "2018"
 | 
				
			|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
					# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies]
 | 
					[dependencies]
 | 
				
			||||||
 | 
					simplelog = "0.8"
 | 
				
			||||||
log = "0.4"
 | 
					log = "0.4"
 | 
				
			||||||
serde_json = "1.0"
 | 
					serde_json = "1.0"
 | 
				
			||||||
custom_error = "1.8.0"
 | 
					custom_error = "1.8.0"
 | 
				
			||||||
							
								
								
									
										179
									
								
								src/database/file.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								src/database/file.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,179 @@
 | 
				
			|||||||
 | 
					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}" }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum ValueReference<'a> {
 | 
				
			||||||
 | 
					    Object(&'a mut serde_json::Map<String, serde_json::Value>, String),
 | 
				
			||||||
 | 
					    Array(&'a mut Vec<serde_json::Value>, usize),
 | 
				
			||||||
 | 
					    Invalid,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					pub struct File {
 | 
				
			||||||
 | 
					    contents: serde_json::Value,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ToString for File {
 | 
				
			||||||
 | 
					    fn to_string(&self) -> String {
 | 
				
			||||||
 | 
					        self.contents.to_string()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl File {
 | 
				
			||||||
 | 
					    pub fn empty() -> File {
 | 
				
			||||||
 | 
					        File {
 | 
				
			||||||
 | 
					            contents: json!({}),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn new(file_contents: String) -> Result<File, Box<dyn Error>> {
 | 
				
			||||||
 | 
					        Ok(File {
 | 
				
			||||||
 | 
					            contents: serde_json::from_str(&file_contents)?,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn root(&self) -> &serde_json::Value {
 | 
				
			||||||
 | 
					        &self.contents
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get(&self, pointer: &str) -> Option<&serde_json::Value> {
 | 
				
			||||||
 | 
					        self.contents.pointer(pointer)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn contains(&self, pointer: &str) -> bool {
 | 
				
			||||||
 | 
					        self.get(pointer) != None
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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),
 | 
				
			||||||
 | 
					            ValueReference::Array(vec, variable_index) => {
 | 
				
			||||||
 | 
					                if variable_index < vec.len() {
 | 
				
			||||||
 | 
					                    return Some(vec.remove(variable_index));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                None
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            _ => None,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										211
									
								
								src/database/io.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								src/database/io.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,211 @@
 | 
				
			|||||||
 | 
					use super::*;
 | 
				
			||||||
 | 
					use log::{info, error, warn};
 | 
				
			||||||
 | 
					use std::collections::HashMap;
 | 
				
			||||||
 | 
					use std::error::Error;
 | 
				
			||||||
 | 
					use std::fs;
 | 
				
			||||||
 | 
					use std::path;
 | 
				
			||||||
 | 
					use std::path::Path;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern crate custom_error;
 | 
				
			||||||
 | 
					use custom_error::custom_error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const JSON_EXTENSION: &str = "JSON";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					custom_error! { pub IOError
 | 
				
			||||||
 | 
					    NotDirectory{path: String} = "Path to the database should point at a directory: {path}",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn read(db_path: &path::Path) -> Result<Vec<Group>, Box<dyn Error>> {
 | 
				
			||||||
 | 
					    if !db_path.is_dir() {
 | 
				
			||||||
 | 
					        error!("Loading database from a non-directory {} was attempted.", db_path.display());
 | 
				
			||||||
 | 
					        return Err(Box::new(IOError::NotDirectory {
 | 
				
			||||||
 | 
					            path: db_path.display().to_string(),
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    info!("Loading database from {}.", db_path.display());
 | 
				
			||||||
 | 
					    let mut groups = Vec::new();
 | 
				
			||||||
 | 
					    for entry in fs::read_dir(db_path)? {
 | 
				
			||||||
 | 
					        let path = match entry {
 | 
				
			||||||
 | 
					            Ok(r) => r,
 | 
				
			||||||
 | 
					            _ => continue,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .path();
 | 
				
			||||||
 | 
					        if !check_valid_group_dir(path.as_path()) {
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        match read_group(&path)? {
 | 
				
			||||||
 | 
					            Some(g) => groups.push(g),
 | 
				
			||||||
 | 
					            _ => (),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    info!("Correctly finished leading database.");
 | 
				
			||||||
 | 
					    Ok(groups)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn write(db_path: &path::Path, db: &Database) -> Result<(), Box<dyn Error>> {
 | 
				
			||||||
 | 
					    if db_path.exists() && !db_path.is_dir() {
 | 
				
			||||||
 | 
					        error!("Cannot write database into a non-directory {}", db_path.display());
 | 
				
			||||||
 | 
					        return Err(Box::new(IOError::NotDirectory {
 | 
				
			||||||
 | 
					            path: db_path.display().to_string(),
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    fs::create_dir(db_path)?;
 | 
				
			||||||
 | 
					    for group in db.group_names().iter() {
 | 
				
			||||||
 | 
					        let group_path = db_path.join(group);
 | 
				
			||||||
 | 
					        if !group_path.exists() || !group_path.is_dir() {
 | 
				
			||||||
 | 
					            fs::create_dir(group_path.clone())?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for file in db.file_names_in(group).iter() {
 | 
				
			||||||
 | 
					            let file_path = group_path.join(file);
 | 
				
			||||||
 | 
					            match db.file(group, file) {
 | 
				
			||||||
 | 
					                Some(file) => fs::write(file_path, file.to_string())?,
 | 
				
			||||||
 | 
					                _ => (),
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn clear_dir(db_path: &path::Path) -> Result<(), Box<dyn Error>> {
 | 
				
			||||||
 | 
					    info!("Clearing directory {} from database files.", db_path.display());
 | 
				
			||||||
 | 
					    for entry in fs::read_dir(db_path)? {
 | 
				
			||||||
 | 
					        let dir_path = match entry {
 | 
				
			||||||
 | 
					            Ok(r) => r,
 | 
				
			||||||
 | 
					            _ => continue,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .path();
 | 
				
			||||||
 | 
					        if !check_valid_group_dir(dir_path.as_path()) {
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for entry in fs::read_dir(dir_path.clone())? {
 | 
				
			||||||
 | 
					            let file_path = match entry {
 | 
				
			||||||
 | 
					                Ok(r) => r,
 | 
				
			||||||
 | 
					                _ => continue,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .path();
 | 
				
			||||||
 | 
					            if !check_valid_data_file(file_path.as_path()) {
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            fs::remove_file(file_path)?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        let _ = fs::remove_dir(dir_path);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let _ = fs::remove_dir(db_path);
 | 
				
			||||||
 | 
					    info!("Correctly finished clearing database files.");
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn read_group(group_path: &path::Path) -> Result<Option<Group>, Box<dyn Error>> {
 | 
				
			||||||
 | 
					    let mut files = HashMap::new();
 | 
				
			||||||
 | 
					    for entry in fs::read_dir(group_path)? {
 | 
				
			||||||
 | 
					        let path = match entry {
 | 
				
			||||||
 | 
					            Ok(r) => r,
 | 
				
			||||||
 | 
					            _ => continue,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .path();
 | 
				
			||||||
 | 
					        if !check_valid_data_file(path.as_path()) {
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        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)?);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if files.len() > 0 {
 | 
				
			||||||
 | 
					        return Ok(Some(Group {
 | 
				
			||||||
 | 
					            name: get_file_name(group_path),
 | 
				
			||||||
 | 
					            files,
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    Ok(None)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn check_valid_group_dir(dir_path: &path::Path) -> bool {
 | 
				
			||||||
 | 
					    if !dir_path.is_dir() {
 | 
				
			||||||
 | 
					        warn!(
 | 
				
			||||||
 | 
					            r#"Skipping {}, because only directories are expected in database's root."#,
 | 
				
			||||||
 | 
					            dir_path.display()
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if !is_name_valid(&get_file_name(dir_path)) {
 | 
				
			||||||
 | 
					        warn!(
 | 
				
			||||||
 | 
					            r#"Skipping directory {}, because it does not have a valid name."#,
 | 
				
			||||||
 | 
					            dir_path.display()
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn check_valid_data_file(file_path: &path::Path) -> bool {
 | 
				
			||||||
 | 
					    if file_path.is_dir() {
 | 
				
			||||||
 | 
					        warn!(
 | 
				
			||||||
 | 
					            r#"Skipping directory {}, because group directories are only\
 | 
				
			||||||
 | 
					                supposed to contain files."#,
 | 
				
			||||||
 | 
					            file_path.display()
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let name = get_file_name(file_path);
 | 
				
			||||||
 | 
					    if !is_name_valid(&name) {
 | 
				
			||||||
 | 
					        warn!(
 | 
				
			||||||
 | 
					            r#"Skipping file {}, because it does not have a valid name."#,
 | 
				
			||||||
 | 
					            file_path.display()
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let extension = get_file_extension(file_path);
 | 
				
			||||||
 | 
					    if !JSON_EXTENSION.eq_ignore_ascii_case(&extension) {
 | 
				
			||||||
 | 
					        warn!(
 | 
				
			||||||
 | 
					            r#"Skipping file {}, because it does not have "json" extension."#,
 | 
				
			||||||
 | 
					            file_path.display()
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn get_file_name(path: &path::Path) -> String {
 | 
				
			||||||
 | 
					    path.file_stem()
 | 
				
			||||||
 | 
					        .and_then(|x| x.to_str())
 | 
				
			||||||
 | 
					        .unwrap_or_default()
 | 
				
			||||||
 | 
					        .to_string()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn get_file_extension(path: &path::Path) -> String {
 | 
				
			||||||
 | 
					    path.extension()
 | 
				
			||||||
 | 
					        .and_then(|x| x.to_str())
 | 
				
			||||||
 | 
					        .unwrap_or_default()
 | 
				
			||||||
 | 
					        .to_string()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[test]
 | 
				
			||||||
 | 
					fn test_file_name_extension_extraction() {
 | 
				
			||||||
 | 
					    assert_eq!(get_file_name(Path::new("/dir/file")), "file".to_owned());
 | 
				
			||||||
 | 
					    assert_eq!(
 | 
				
			||||||
 | 
					        get_file_name(Path::new("/dir/sub_dir/some.ext")),
 | 
				
			||||||
 | 
					        "some".to_owned()
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    assert_eq!(
 | 
				
			||||||
 | 
					        get_file_name(Path::new("/dir/sub_dir/.ext")),
 | 
				
			||||||
 | 
					        ".ext".to_owned()
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    assert_eq!(
 | 
				
			||||||
 | 
					        get_file_name(Path::new("/dir/sub_dir/thing.")),
 | 
				
			||||||
 | 
					        "thing".to_owned()
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert_eq!(get_file_extension(Path::new("/dir/file")), "".to_owned());
 | 
				
			||||||
 | 
					    assert_eq!(
 | 
				
			||||||
 | 
					        get_file_extension(Path::new("/dir/sub_dir/some.ext")),
 | 
				
			||||||
 | 
					        "ext".to_owned()
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    assert_eq!(
 | 
				
			||||||
 | 
					        get_file_extension(Path::new("/dir/sub_dir/.ext")),
 | 
				
			||||||
 | 
					        "".to_owned()
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    assert_eq!(
 | 
				
			||||||
 | 
					        get_file_extension(Path::new("/dir/sub_dir/thing.")),
 | 
				
			||||||
 | 
					        "".to_owned()
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,40 +1,30 @@
 | 
				
			|||||||
#[cfg(test)]
 | 
					#[cfg(test)]
 | 
				
			||||||
mod tests;
 | 
					mod tests;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use log::warn;
 | 
					 | 
				
			||||||
use serde_json;
 | 
					use serde_json;
 | 
				
			||||||
use serde_json::json;
 | 
					 | 
				
			||||||
use std::collections::HashMap;
 | 
					use std::collections::HashMap;
 | 
				
			||||||
use std::error::Error;
 | 
					use std::error::Error;
 | 
				
			||||||
use std::fmt;
 | 
					 | 
				
			||||||
use std::fs;
 | 
					 | 
				
			||||||
use std::path;
 | 
					use std::path;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
extern crate custom_error;
 | 
					extern crate custom_error;
 | 
				
			||||||
use custom_error::custom_error;
 | 
					use custom_error::custom_error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const JSON_POINTER_SEPARATOR: &str = "/";
 | 
					pub mod file;
 | 
				
			||||||
 | 
					pub use file::File;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub mod io;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
custom_error! { pub DBError
 | 
					custom_error! { pub DBError
 | 
				
			||||||
    NotDirectory{path: String} = "Path to the database should point at a directory: {path}",
 | 
					 | 
				
			||||||
    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 in the group "{group_name}""#,
 | 
					    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> {
 | 
					 | 
				
			||||||
    Object(&'a mut serde_json::Map<String, serde_json::Value>, String),
 | 
					 | 
				
			||||||
    Array(&'a mut Vec<serde_json::Value>, usize),
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type FileID<'a> = (&'a str, &'a str);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub struct Group {
 | 
					pub struct Group {
 | 
				
			||||||
    name: String,
 | 
					    name: String,
 | 
				
			||||||
    files: HashMap<String, serde_json::Value>,
 | 
					    files: HashMap<String, File>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct Database {
 | 
					pub struct Database {
 | 
				
			||||||
@ -42,53 +32,40 @@ pub struct Database {
 | 
				
			|||||||
    groups: Vec<Group>,
 | 
					    groups: Vec<Group>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl fmt::Display for Group {
 | 
					 | 
				
			||||||
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
					 | 
				
			||||||
        writeln!(f, "[{}]", self.name)?;
 | 
					 | 
				
			||||||
        for (name, contents) in self.files.iter() {
 | 
					 | 
				
			||||||
            writeln!(f, r#"  File "{}": {}"#, name, contents.to_string())?;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        Ok(())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl fmt::Display for Database {
 | 
					 | 
				
			||||||
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
					 | 
				
			||||||
        writeln!(f, "DB: {}", self.storage_path.display())?;
 | 
					 | 
				
			||||||
        for g in self.groups.iter() {
 | 
					 | 
				
			||||||
            writeln!(f, "{}", g)?;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        Ok(())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Database {
 | 
					impl Database {
 | 
				
			||||||
    pub fn new(storage_path: &path::Path) -> Result<Database, Box<dyn Error>> {
 | 
					    pub fn new(storage_path: &path::Path) -> Result<Database, Box<dyn Error>> {
 | 
				
			||||||
        if !storage_path.is_dir() {
 | 
					 | 
				
			||||||
            return Err(Box::new(DBError::NotDirectory {
 | 
					 | 
				
			||||||
                path: storage_path.display().to_string(),
 | 
					 | 
				
			||||||
            }));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        let mut groups = Vec::new();
 | 
					 | 
				
			||||||
        for entry in fs::read_dir(storage_path)? {
 | 
					 | 
				
			||||||
            let entry = entry?;
 | 
					 | 
				
			||||||
            let path = entry.path();
 | 
					 | 
				
			||||||
            if !path.is_dir() {
 | 
					 | 
				
			||||||
                warn!(
 | 
					 | 
				
			||||||
                    r#"File {} found where only group directories are supposed to be"#,
 | 
					 | 
				
			||||||
                    path.display()
 | 
					 | 
				
			||||||
                );
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                let group = load_group(&path)?;
 | 
					 | 
				
			||||||
                groups.push(group);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        Ok(Database {
 | 
					        Ok(Database {
 | 
				
			||||||
            storage_path: storage_path.to_path_buf(),
 | 
					            storage_path: storage_path.to_path_buf(),
 | 
				
			||||||
            groups,
 | 
					            groups: io::read(storage_path)?,
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn clear(&mut self) {
 | 
				
			||||||
 | 
					        self.groups = Vec::new();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn path(&mut self) -> path::PathBuf {
 | 
				
			||||||
 | 
					        self.storage_path.clone()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn change_path(&mut self, new_path: &path::Path) -> Result<(), Box<dyn Error>> {
 | 
				
			||||||
 | 
					        self.write_copy(new_path)?;
 | 
				
			||||||
 | 
					        io::clear_dir(&self.storage_path)?;
 | 
				
			||||||
 | 
					        self.storage_path = new_path.to_path_buf();
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn write_copy(&self, new_path: &path::Path) -> Result<(), Box<dyn Error>> {
 | 
				
			||||||
 | 
					        io::clear_dir(new_path)?;
 | 
				
			||||||
 | 
					        io::write(new_path, &self)?;
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn erase(self) -> Result<(), Box<dyn Error>> {
 | 
				
			||||||
 | 
					        io::clear_dir(&self.storage_path)?;
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn group_names(&self) -> Vec<String> {
 | 
					    pub fn group_names(&self) -> Vec<String> {
 | 
				
			||||||
        self.groups.iter().map(|x| x.name.clone()).collect()
 | 
					        self.groups.iter().map(|x| x.name.clone()).collect()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -100,21 +77,13 @@ impl Database {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn group_index(&self, group_name: &str) -> Option<usize> {
 | 
					 | 
				
			||||||
        self.groups.iter().position(|x| x.name.eq(group_name))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn contains_group(&self, group_name: &str) -> bool {
 | 
					    pub fn contains_group(&self, group_name: &str) -> bool {
 | 
				
			||||||
        self.group_index(group_name).is_some()
 | 
					        self.group_index(group_name).is_ok()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn create_group(&mut self, group_name: &str) -> Result<(), DBError> {
 | 
					    pub fn create_group(&mut self, group_name: &str) -> Result<(), DBError> {
 | 
				
			||||||
        verify_name(group_name)?;
 | 
					        assert_name_is_valid(group_name)?;
 | 
				
			||||||
        if self.group_index(group_name).is_some() {
 | 
					        self.assert_no_group(group_name)?;
 | 
				
			||||||
            return Err(DBError::GroupAlreadyExists {
 | 
					 | 
				
			||||||
                group_name: group_name.to_owned(),
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        self.groups.push(Group {
 | 
					        self.groups.push(Group {
 | 
				
			||||||
            name: group_name.to_owned(),
 | 
					            name: group_name.to_owned(),
 | 
				
			||||||
            files: HashMap::new(),
 | 
					            files: HashMap::new(),
 | 
				
			||||||
@ -123,147 +92,100 @@ impl Database {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn remove_group(&mut self, group_name: &str) -> Result<(), DBError> {
 | 
					    pub fn remove_group(&mut self, group_name: &str) -> Result<(), DBError> {
 | 
				
			||||||
        match self.group_index(group_name) {
 | 
					        let index = self.group_index(group_name)?;
 | 
				
			||||||
            Some(index) => self.groups.remove(index),
 | 
					        self.groups.remove(index);
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn contains_file(&self, group_name: &str, file_name: &str) -> bool {
 | 
				
			||||||
 | 
					        self.group_files(group_name)
 | 
				
			||||||
 | 
					            .and_then(|x| Ok(x.contains_key(&file_name.to_owned())))
 | 
				
			||||||
 | 
					            .unwrap_or(false)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn create_file(&mut self, group_name: &str, file_name: &str) -> Result<&mut File, DBError> {
 | 
				
			||||||
 | 
					        assert_name_is_valid(&file_name)?;
 | 
				
			||||||
 | 
					        let files = self.group_files_mut(group_name)?;
 | 
				
			||||||
 | 
					        if files.contains_key(&file_name.to_owned()) {
 | 
				
			||||||
 | 
					            return Err(DBError::FileAlreadyExists {
 | 
				
			||||||
 | 
					                group_name: group_name.to_owned(),
 | 
				
			||||||
 | 
					                file_name: file_name.to_owned(),
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        let new_file = File::empty();
 | 
				
			||||||
 | 
					        files.insert(file_name.to_owned(), new_file);
 | 
				
			||||||
 | 
					        Ok(files
 | 
				
			||||||
 | 
					            .get_mut(file_name)
 | 
				
			||||||
 | 
					            .expect("Missing value that was just inserted."))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn remove_file(&mut self, group_name: &str, file_name: &str) -> Result<(), DBError> {
 | 
				
			||||||
 | 
					        if self
 | 
				
			||||||
 | 
					            .group_files_mut(group_name)?
 | 
				
			||||||
 | 
					            .remove(file_name)
 | 
				
			||||||
 | 
					            .is_none()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return Err(DBError::NoFile {
 | 
				
			||||||
 | 
					                group_name: group_name.to_owned(),
 | 
				
			||||||
 | 
					                file_name: file_name.to_owned(),
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn file_mut(&mut self, group_name: &str, file_name: &str) -> Option<&mut File> {
 | 
				
			||||||
 | 
					        match self.group_files_mut(group_name) {
 | 
				
			||||||
 | 
					            Ok(files) => files.get_mut(&file_name.to_owned()),
 | 
				
			||||||
 | 
					            _ => None,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn file(&self, group_name: &str, file_name: &str) -> Option<&File> {
 | 
				
			||||||
 | 
					        match self.group_files(group_name) {
 | 
				
			||||||
 | 
					            Ok(files) => files.get(&file_name.to_owned()),
 | 
				
			||||||
 | 
					            _ => None,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn assert_no_group(&self, group_name: &str) -> Result<(), DBError> {
 | 
				
			||||||
 | 
					        if self.group_index(group_name).is_ok() {
 | 
				
			||||||
 | 
					            return Err(DBError::GroupAlreadyExists {
 | 
				
			||||||
 | 
					                group_name: group_name.to_owned(),
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn group_index(&self, group_name: &str) -> Result<usize, DBError> {
 | 
				
			||||||
 | 
					        match self.groups.iter().position(|x| x.name.eq(group_name)) {
 | 
				
			||||||
 | 
					            Some(index) => Ok(index),
 | 
				
			||||||
            _ => {
 | 
					            _ => {
 | 
				
			||||||
                return Err(DBError::NoGroup {
 | 
					                return Err(DBError::NoGroup {
 | 
				
			||||||
                    group_name: group_name.to_owned(),
 | 
					                    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> {
 | 
					    fn group_files_mut(&mut self, group_name: &str) -> Result<&mut HashMap<String, File>, DBError> {
 | 
				
			||||||
        let group_name = group_name.to_owned();
 | 
					        let group_index = self.group_index(group_name)?;
 | 
				
			||||||
        let file_name = file_name.to_owned();
 | 
					        Ok(&mut (&mut self.groups[group_index]).files)
 | 
				
			||||||
        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> {
 | 
					    fn group_files(&self, group_name: &str) -> Result<&HashMap<String, File>, DBError> {
 | 
				
			||||||
        let group_name = group_name.to_owned();
 | 
					        let group_index = self.group_index(group_name)?;
 | 
				
			||||||
        let file_name = file_name.to_owned();
 | 
					        Ok(&self.groups[group_index].files)
 | 
				
			||||||
        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> {
 | 
					 | 
				
			||||||
        match self.group_index(group_name) {
 | 
					 | 
				
			||||||
            Some(index) => self.groups[index].files.get_mut(&file_name.to_owned()),
 | 
					 | 
				
			||||||
            _ => None,
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn as_json(&self, (group_name, file_name): FileID) -> Option<&serde_json::Value> {
 | 
					fn is_name_valid(entity_name: &str) -> bool {
 | 
				
			||||||
        match self.group_index(group_name) {
 | 
					    entity_name
 | 
				
			||||||
            Some(index) => self.groups[index].files.get(&file_name.to_owned()),
 | 
					 | 
				
			||||||
            _ => None,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn as_string(&self, file_id: FileID) -> Option<String> {
 | 
					 | 
				
			||||||
        self.as_json(file_id).map(|x| x.to_string())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn get_json(&self, file_id: FileID, pointer: &str) -> Option<&serde_json::Value> {
 | 
					 | 
				
			||||||
        match self.as_json(file_id) {
 | 
					 | 
				
			||||||
            Some(v) => v.pointer(pointer),
 | 
					 | 
				
			||||||
            _ => None,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn get_string(&self, file_id: FileID, pointer: &str) -> Option<String> {
 | 
					 | 
				
			||||||
        self.get_json(file_id, pointer).map(|x| x.to_string())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn contains_value(&self, file_id: FileID, pointer: &str) -> bool {
 | 
					 | 
				
			||||||
        self.get_json(file_id, pointer) != None
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn set_json(
 | 
					 | 
				
			||||||
        &mut self,
 | 
					 | 
				
			||||||
        (group_name, file_name): FileID,
 | 
					 | 
				
			||||||
        pointer: &str,
 | 
					 | 
				
			||||||
        new_value: serde_json::Value,
 | 
					 | 
				
			||||||
    ) -> Result<(), Box<dyn Error>> {
 | 
					 | 
				
			||||||
        let file_json = match self.as_json_mut((group_name, file_name)) {
 | 
					 | 
				
			||||||
            Some(file_json) => file_json,
 | 
					 | 
				
			||||||
            _ => {
 | 
					 | 
				
			||||||
                return Err(Box::new(DBError::NoFile {
 | 
					 | 
				
			||||||
                    group_name: group_name.to_owned(),
 | 
					 | 
				
			||||||
                    file_name: file_name.to_owned(),
 | 
					 | 
				
			||||||
                }))
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        touch(file_json, pointer)?;
 | 
					 | 
				
			||||||
        match file_json.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(Box::new(DBError::IncorrectPointer {
 | 
					 | 
				
			||||||
                    pointer: pointer.to_owned(),
 | 
					 | 
				
			||||||
                }));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        Ok(())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn remove(
 | 
					 | 
				
			||||||
        &mut self,
 | 
					 | 
				
			||||||
        (group_name, file_name): FileID,
 | 
					 | 
				
			||||||
        pointer: &str,
 | 
					 | 
				
			||||||
    ) -> Option<serde_json::Value> {
 | 
					 | 
				
			||||||
        let file_json = match self.as_json_mut((group_name, file_name)) {
 | 
					 | 
				
			||||||
            Some(file_json) => file_json,
 | 
					 | 
				
			||||||
            _ => return None,
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        match pointer_to_reference(file_json, pointer) {
 | 
					 | 
				
			||||||
            Some(ValueReference::Object(map, variable_name)) => map.remove(&variable_name),
 | 
					 | 
				
			||||||
            Some(ValueReference::Array(vec, variable_index)) => {
 | 
					 | 
				
			||||||
                if variable_index < vec.len() {
 | 
					 | 
				
			||||||
                    return Some(vec.remove(variable_index));
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                None
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            _ => None,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn verify_name(entity_name: &str) -> Result<(), DBError> {
 | 
					 | 
				
			||||||
    let is_valid = entity_name
 | 
					 | 
				
			||||||
        .chars()
 | 
					        .chars()
 | 
				
			||||||
        .all(|x| x.is_ascii_alphabetic() || x.is_ascii_digit());
 | 
					        .all(|x| x.is_ascii_alphabetic() || x.is_ascii_digit())
 | 
				
			||||||
    if is_valid {
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn assert_name_is_valid(entity_name: &str) -> Result<(), DBError> {
 | 
				
			||||||
 | 
					    if is_name_valid(entity_name) {
 | 
				
			||||||
        return Ok(());
 | 
					        return Ok(());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return Err(DBError::InvalidEntityName {
 | 
					    return Err(DBError::InvalidEntityName {
 | 
				
			||||||
@ -271,107 +193,7 @@ fn verify_name(entity_name: &str) -> Result<(), DBError> {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn load_group(group_path: &path::Path) -> Result<Group, Box<dyn Error>> {
 | 
					 | 
				
			||||||
    let mut files = HashMap::new();
 | 
					 | 
				
			||||||
    for entry in fs::read_dir(group_path)? {
 | 
					 | 
				
			||||||
        let entry = entry?;
 | 
					 | 
				
			||||||
        let path = entry.path();
 | 
					 | 
				
			||||||
        if path.is_dir() {
 | 
					 | 
				
			||||||
            warn!(
 | 
					 | 
				
			||||||
                r#"Directory {} found where only data files are supposed to be"#,
 | 
					 | 
				
			||||||
                path.display()
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            let file_contents = fs::read_to_string(&path)?;
 | 
					 | 
				
			||||||
            let file_name = get_file_name(path.as_path());
 | 
					 | 
				
			||||||
            let file_contents = serde_json::from_str(&file_contents)?;
 | 
					 | 
				
			||||||
            files.insert(file_name, file_contents);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    Ok(Group {
 | 
					 | 
				
			||||||
        name: get_file_name(group_path),
 | 
					 | 
				
			||||||
        files,
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn touch(json_root: &mut serde_json::Value, pointer: &str) -> (Result<(), Box<dyn Error>>) {
 | 
					 | 
				
			||||||
    if pointer.is_empty() || json_root.pointer_mut(pointer).is_some() {
 | 
					 | 
				
			||||||
        return Ok(());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    match pointer_to_reference(json_root, pointer) {
 | 
					 | 
				
			||||||
        Some(ValueReference::Object(map, variable_name)) => {
 | 
					 | 
				
			||||||
            map.insert(variable_name, json!(null));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        Some(ValueReference::Array(vec, variable_index)) => {
 | 
					 | 
				
			||||||
            //  Since values at the index does not exist - resize will increase the sizeof the array
 | 
					 | 
				
			||||||
            vec.resize(variable_index + 1, json!(null));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        _ => {
 | 
					 | 
				
			||||||
            return Err(Box::new(DBError::IncorrectPointer {
 | 
					 | 
				
			||||||
                pointer: pointer.to_owned(),
 | 
					 | 
				
			||||||
            }))
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    Ok(())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn pointer_to_reference<'a>(
 | 
					 | 
				
			||||||
    json_root: &'a mut serde_json::Value,
 | 
					 | 
				
			||||||
    pointer: &str,
 | 
					 | 
				
			||||||
) -> Option<ValueReference<'a>> {
 | 
					 | 
				
			||||||
    if pointer.is_empty() {
 | 
					 | 
				
			||||||
        return None;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    let container_variable_pair =
 | 
					 | 
				
			||||||
        pop_json_pointer(pointer).and_then(move |(path, variable_name)| {
 | 
					 | 
				
			||||||
            match json_root.pointer_mut(&path) {
 | 
					 | 
				
			||||||
                Some(v) => Some((v, variable_name)),
 | 
					 | 
				
			||||||
                _ => None,
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    let (json_container, variable_name) = match container_variable_pair {
 | 
					 | 
				
			||||||
        Some(v) => v,
 | 
					 | 
				
			||||||
        _ => return None,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    match json_container {
 | 
					 | 
				
			||||||
        serde_json::Value::Object(map) => Some(ValueReference::Object(map, variable_name)),
 | 
					 | 
				
			||||||
        serde_json::Value::Array(vec) => {
 | 
					 | 
				
			||||||
            let index: usize = match variable_name.parse() {
 | 
					 | 
				
			||||||
                Ok(v) => v,
 | 
					 | 
				
			||||||
                _ => return None,
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            Some(ValueReference::Array(vec, index))
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        _ => None,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn get_file_name(path: &path::Path) -> String {
 | 
					 | 
				
			||||||
    path.file_stem()
 | 
					 | 
				
			||||||
        .and_then(|x| x.to_str())
 | 
					 | 
				
			||||||
        .unwrap_or_default()
 | 
					 | 
				
			||||||
        .to_string()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
// 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
 | 
				
			||||||
// TODO check that file name is appropriate
 | 
					 | 
				
			||||||
// TODO handle parsing errors differently
 | 
					// TODO handle parsing errors differently
 | 
				
			||||||
// TODO add logs
 | 
					// TODO add logs
 | 
				
			||||||
 | 
					// TODO add docs
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,22 @@ 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]
 | 
				
			||||||
 | 
					fn db_path() {
 | 
				
			||||||
 | 
					    let mut db = Database::new(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE);
 | 
				
			||||||
 | 
					    assert!(db.path() == path::Path::new(TEST_DB_PATH));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[test]
 | 
				
			||||||
 | 
					fn db_clear() {
 | 
				
			||||||
 | 
					    let mut db = Database::new(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE);
 | 
				
			||||||
 | 
					    db.clear();
 | 
				
			||||||
 | 
					    assert!(!db.contains_group("game"));
 | 
				
			||||||
 | 
					    assert!(!db.contains_file("administration", "registered"));
 | 
				
			||||||
 | 
					    let names = db.group_names();
 | 
				
			||||||
 | 
					    assert_eq!(names.len(), 0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[test]
 | 
					#[test]
 | 
				
			||||||
fn db_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);
 | 
				
			||||||
@ -85,31 +101,31 @@ fn db_group_create() -> Result<(), Box<dyn Error>> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[test]
 | 
					#[test]
 | 
				
			||||||
fn db_file_check() {
 | 
					fn db_file_check() {
 | 
				
			||||||
    let 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);
 | 
				
			||||||
    //  Success
 | 
					    //  Success
 | 
				
			||||||
    assert!(db.contains_file(("administration", "registered")));
 | 
					    assert!(db.contains_file("administration", "registered"));
 | 
				
			||||||
    assert!(db.contains_file(("game", "general")));
 | 
					    assert!(db.file("game", "general").is_some());
 | 
				
			||||||
    assert!(db.contains_file(("game", "perks")));
 | 
					    assert!(db.file_mut("game", "perks").is_some());
 | 
				
			||||||
    //  Failure
 | 
					    //  Failure
 | 
				
			||||||
    assert!(!db.contains_file(("game", "perk")));
 | 
					    assert!(!db.contains_file("game", "perk"));
 | 
				
			||||||
    assert!(!db.contains_file(("games", "perks")));
 | 
					    assert!(db.file("games", "perks").is_none());
 | 
				
			||||||
    assert!(!db.contains_file(("random", "rnd_file")));
 | 
					    assert!(db.file_mut("random", "rnd_file").is_none());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[test]
 | 
					#[test]
 | 
				
			||||||
fn db_file_create() -> Result<(), Box<dyn Error>> {
 | 
					fn db_file_create() -> 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);
 | 
				
			||||||
    //  Success
 | 
					    //  Success
 | 
				
			||||||
    db.create_file(("administration", "secrets"))?;
 | 
					    let file = db.create_file("administration", "secrets")?;
 | 
				
			||||||
    assert!(db.contains_file(("administration", "secrets")));
 | 
					    assert_eq!(file.to_string(), "{}");
 | 
				
			||||||
    assert_eq!(db.as_string(("administration", "secrets")).unwrap(), "{}");
 | 
					    assert!(db.contains_file("administration", "secrets"));
 | 
				
			||||||
    assert!(db.contains_file(("game", "perks")));
 | 
					    assert!(db.contains_file("game", "perks"));
 | 
				
			||||||
    //  Failure
 | 
					    //  Failure
 | 
				
			||||||
    db.create_file(("administration", "secrets"))
 | 
					    db.create_file("administration", "secrets")
 | 
				
			||||||
        .expect_err("Testing whether creating existing file causes errors.");
 | 
					        .expect_err("Testing whether creating existing file causes errors.");
 | 
				
			||||||
    db.create_file(("none", "secrets"))
 | 
					    db.create_file("none", "secrets")
 | 
				
			||||||
        .expect_err("Testing whether creating existing file in non-existent group causes errors.");
 | 
					        .expect_err("Testing whether creating existing file in non-existent group causes errors.");
 | 
				
			||||||
    db.create_file(("game", "sec_rets"))
 | 
					    db.create_file("game", "sec_rets")
 | 
				
			||||||
        .expect_err("Testing whether creating existing file with invalid name causes errors.");
 | 
					        .expect_err("Testing whether creating existing file with invalid name causes errors.");
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -118,25 +134,25 @@ fn db_file_create() -> Result<(), Box<dyn Error>> {
 | 
				
			|||||||
fn db_file_remove() -> Result<(), Box<dyn Error>> {
 | 
					fn db_file_remove() -> 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);
 | 
				
			||||||
    //  Success
 | 
					    //  Success
 | 
				
			||||||
    db.remove_file(("administration", "registered"))?;
 | 
					    db.remove_file("administration", "registered")?;
 | 
				
			||||||
    assert!(!db.contains_file(("administration", "registered")));
 | 
					    assert!(!db.contains_file("administration", "registered"));
 | 
				
			||||||
    assert!(db.contains_group("administration"));
 | 
					    assert!(db.contains_group("administration"));
 | 
				
			||||||
    db.remove_file(("game", "perks"))?;
 | 
					    db.remove_file("game", "perks")?;
 | 
				
			||||||
    assert_eq!(db.file_names_in("game").len(), 1);
 | 
					    assert_eq!(db.file_names_in("game").len(), 1);
 | 
				
			||||||
    //  Failure
 | 
					    //  Failure
 | 
				
			||||||
    db.remove_file(("administration", "registered"))
 | 
					    db.remove_file("administration", "registered")
 | 
				
			||||||
        .expect_err("Testing whether removing non-existent files causes errors.");
 | 
					        .expect_err("Testing whether removing non-existent files causes errors.");
 | 
				
			||||||
    db.remove_file(("administration", "never"))
 | 
					    db.remove_file("administration", "never")
 | 
				
			||||||
        .expect_err("Testing whether removing non-existent files causes errors.");
 | 
					        .expect_err("Testing whether removing non-existent files causes errors.");
 | 
				
			||||||
    db.remove_file(("never", "file"))
 | 
					    db.remove_file("never", "file")
 | 
				
			||||||
        .expect_err("Testing whether removing non-existent files causes errors.");
 | 
					        .expect_err("Testing whether removing non-existent files causes errors.");
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[test]
 | 
					#[test]
 | 
				
			||||||
fn db_json_contents() {
 | 
					fn file_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);
 | 
				
			||||||
    let registered = db.as_json(("administration", "registered")).unwrap();
 | 
					    let registered = db.file("administration", "registered").unwrap().root();
 | 
				
			||||||
    let user_map = registered
 | 
					    let user_map = registered
 | 
				
			||||||
        .as_object()
 | 
					        .as_object()
 | 
				
			||||||
        .expect("Read value is not an object.");
 | 
					        .expect("Read value is not an object.");
 | 
				
			||||||
@ -160,103 +176,73 @@ fn db_json_contents() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[test]
 | 
					#[test]
 | 
				
			||||||
fn db_string_contents() {
 | 
					fn file_json_get() {
 | 
				
			||||||
    let db = Database::new(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE);
 | 
					 | 
				
			||||||
    let general = db.as_string(("game", "general")).unwrap();
 | 
					 | 
				
			||||||
    assert!(general.contains(r#""dosh_thrown":527624"#));
 | 
					 | 
				
			||||||
    assert!(general
 | 
					 | 
				
			||||||
        .contains(r#""achievements":["kf:LabCleaner","kf:ChickenFarmer","scrn:playedscrn"]"#));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[test]
 | 
					 | 
				
			||||||
fn db_json_sub_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);
 | 
				
			||||||
    //  Test empty path
 | 
					    //  Test empty path
 | 
				
			||||||
    let file_id = ("administration", "registered");
 | 
					    let file = db.file("administration", "registered").unwrap();
 | 
				
			||||||
    assert_eq!(db.as_json(file_id), db.get_json(file_id, ""));
 | 
					    assert_eq!(file.root(), file.get("").unwrap());
 | 
				
			||||||
    //  Test complex path
 | 
					    //  Test complex path
 | 
				
			||||||
    let received = db
 | 
					    let expected = file.get("/76561198025127722/allowed_ips/1").unwrap();
 | 
				
			||||||
        .get_json(file_id, "/76561198025127722/allowed_ips/1")
 | 
					    assert_eq!(expected.as_str().unwrap(), "192.168.0.100");
 | 
				
			||||||
        .unwrap();
 | 
					 | 
				
			||||||
    assert_eq!(received.as_str().unwrap(), "192.168.0.100");
 | 
					 | 
				
			||||||
    //  Test bad paths
 | 
					    //  Test bad paths
 | 
				
			||||||
    assert!(db.get_json(file_id, "/777") == None);
 | 
					    assert!(file.get("/777") == None);
 | 
				
			||||||
    assert!(db.get_json(file_id, "/76561198025127722/allowed_ips/2") == None);
 | 
					    assert!(file.get("/76561198025127722/allowed_ips/2") == None);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[test]
 | 
					#[test]
 | 
				
			||||||
fn db_string_sub_contents() {
 | 
					fn file_contains_check() {
 | 
				
			||||||
    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);
 | 
				
			||||||
    //  Test empty path
 | 
					    let registered_file = db.file("administration", "registered").unwrap();
 | 
				
			||||||
    let file_id = ("administration", "registered");
 | 
					    let perks_file = db.file("game", "perks").unwrap();
 | 
				
			||||||
    assert_eq!(db.as_string(file_id), db.get_string(file_id, ""));
 | 
					 | 
				
			||||||
    //  Test complex path
 | 
					 | 
				
			||||||
    let received = db
 | 
					 | 
				
			||||||
        .get_string(file_id, "/76561198025127722/allowed_ips/0")
 | 
					 | 
				
			||||||
        .unwrap();
 | 
					 | 
				
			||||||
    assert_eq!(received, r#""127.0.0.1""#);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[test]
 | 
					 | 
				
			||||||
fn db_contains_check() {
 | 
					 | 
				
			||||||
    let db = Database::new(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE);
 | 
					 | 
				
			||||||
    let registered_id = ("administration", "registered");
 | 
					 | 
				
			||||||
    let perks_id = ("game", "perks");
 | 
					 | 
				
			||||||
    //  These exist
 | 
					    //  These exist
 | 
				
			||||||
    assert!(db.contains_value(registered_id, "/76561198025127722/password_hash"));
 | 
					    assert!(registered_file.contains("/76561198025127722/password_hash"));
 | 
				
			||||||
    assert!(db.contains_value(registered_id, "/76561198044316328/groups/0"));
 | 
					    assert!(registered_file.contains("/76561198044316328/groups/0"));
 | 
				
			||||||
    assert!(db.contains_value(perks_id, "/76561198025127722/headshots"));
 | 
					    assert!(perks_file.contains("/76561198025127722/headshots"));
 | 
				
			||||||
    assert!(db.contains_value(perks_id, "/76561198044316328"));
 | 
					    assert!(perks_file.contains("/76561198044316328"));
 | 
				
			||||||
    //  These do not exist
 | 
					    //  These do not exist
 | 
				
			||||||
    assert!(!db.contains_value(registered_id, "/76561198025127722/password/"));
 | 
					    assert!(!registered_file.contains("/76561198025127722/password/"));
 | 
				
			||||||
    assert!(!db.contains_value(registered_id, "/76561198044316328/groups/2"));
 | 
					    assert!(!registered_file.contains("/76561198044316328/groups/2"));
 | 
				
			||||||
    assert!(!db.contains_value(perks_id, "/76561198025127722/assault_rifle_damage/9067"));
 | 
					    assert!(!perks_file.contains("/76561198025127722/assault_rifle_damage/9067"));
 | 
				
			||||||
    assert!(!db.contains_value(perks_id, "/76561198044316328/headshots"));
 | 
					    assert!(!perks_file.contains("/76561198044316328/headshots"));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[test]
 | 
					#[test]
 | 
				
			||||||
fn db_set_success() -> Result<(), Box<dyn Error>> {
 | 
					fn db_insert_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 registered_id = ("administration", "registered");
 | 
					    let registered_file = db.file_mut("administration", "registered").unwrap();
 | 
				
			||||||
    let general_id = ("game", "general");
 | 
					 | 
				
			||||||
    //  Modify existing
 | 
					    //  Modify existing
 | 
				
			||||||
    db.set_json(registered_id, "/76561198025127722/ip_lock", json!(false))?;
 | 
					    registered_file.insert("/76561198025127722/ip_lock", json!(false))?;
 | 
				
			||||||
    assert_eq!(
 | 
					    assert_eq!(
 | 
				
			||||||
        db.get_string(registered_id, "/76561198025127722/ip_lock")
 | 
					        registered_file
 | 
				
			||||||
            .unwrap(),
 | 
					            .get("/76561198025127722/ip_lock")
 | 
				
			||||||
 | 
					            .unwrap()
 | 
				
			||||||
 | 
					            .to_string(),
 | 
				
			||||||
        "false"
 | 
					        "false"
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    db.set_json(
 | 
					    registered_file.insert("/76561198044316328/password_hash", json!({"var":13524}))?;
 | 
				
			||||||
        registered_id,
 | 
					 | 
				
			||||||
        "/76561198044316328/password_hash",
 | 
					 | 
				
			||||||
        json!({"var":13524}),
 | 
					 | 
				
			||||||
    )?;
 | 
					 | 
				
			||||||
    assert_eq!(
 | 
					    assert_eq!(
 | 
				
			||||||
        db.get_string(registered_id, "/76561198044316328/password_hash")
 | 
					        registered_file
 | 
				
			||||||
            .unwrap(),
 | 
					            .get("/76561198044316328/password_hash")
 | 
				
			||||||
 | 
					            .unwrap()
 | 
				
			||||||
 | 
					            .to_string(),
 | 
				
			||||||
        r#"{"var":13524}"#
 | 
					        r#"{"var":13524}"#
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    //  Reset whole file
 | 
					    //  Reset whole file
 | 
				
			||||||
    db.set_json(registered_id, "", json!({}))?;
 | 
					    registered_file.insert("", json!({}))?;
 | 
				
			||||||
    assert_eq!(db.as_json(registered_id).unwrap().to_string(), "{}");
 | 
					    assert_eq!(registered_file.root().to_string(), "{}");
 | 
				
			||||||
    //  Add new values
 | 
					    //  Add new values
 | 
				
			||||||
    db.set_json(
 | 
					    registered_file.insert("/new_var", json!([42, {"word":"life"}, null]))?;
 | 
				
			||||||
        registered_id,
 | 
					 | 
				
			||||||
        "/new_var",
 | 
					 | 
				
			||||||
        json!([42, {"word":"life"}, null]),
 | 
					 | 
				
			||||||
    )?;
 | 
					 | 
				
			||||||
    assert_eq!(
 | 
					    assert_eq!(
 | 
				
			||||||
        db.as_json(registered_id).unwrap().to_string(),
 | 
					        registered_file.root().to_string(),
 | 
				
			||||||
        r#"{"new_var":[42,{"word":"life"},null]}"#
 | 
					        r#"{"new_var":[42,{"word":"life"},null]}"#
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    db.set_json(
 | 
					    let general_file = db.file_mut("game", "general").unwrap();
 | 
				
			||||||
        general_id,
 | 
					    general_file.insert("/76561198025127722/achievements/5", json!("kf:bugged"))?;
 | 
				
			||||||
        "/76561198025127722/achievements/5",
 | 
					 | 
				
			||||||
        json!("kf:bugged"),
 | 
					 | 
				
			||||||
    )?;
 | 
					 | 
				
			||||||
    assert_eq!(
 | 
					    assert_eq!(
 | 
				
			||||||
        db.get_string(general_id, "/76561198025127722/achievements")
 | 
					        general_file
 | 
				
			||||||
            .unwrap(),
 | 
					            .get("/76561198025127722/achievements")
 | 
				
			||||||
 | 
					            .unwrap()
 | 
				
			||||||
 | 
					            .to_string(),
 | 
				
			||||||
        r#"["kf:LabCleaner","kf:ChickenFarmer","scrn:playedscrn",null,null,"kf:bugged"]"#
 | 
					        r#"["kf:LabCleaner","kf:ChickenFarmer","scrn:playedscrn",null,null,"kf:bugged"]"#
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
@ -265,67 +251,45 @@ fn db_set_success() -> Result<(), Box<dyn Error>> {
 | 
				
			|||||||
#[test]
 | 
					#[test]
 | 
				
			||||||
fn db_set_failure() {
 | 
					fn db_set_failure() {
 | 
				
			||||||
    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 file = db.file_mut("administration", "registered").unwrap();
 | 
				
			||||||
    let imaginary_file_id = ("general", "everything");
 | 
					    file.insert("/76561198025127722/dir/var", json!(null))
 | 
				
			||||||
    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.");
 | 
					        .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))
 | 
					    file.insert("/76561198044316328/groups/d", json!(null))
 | 
				
			||||||
        .expect_err("Testing panic at trying to set a value at non-numeric index in an array.");
 | 
					        .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))
 | 
					    file.insert("/76561198044316328/groups/-1", json!(null))
 | 
				
			||||||
        .expect_err("Testing panic at trying to set a value at negative index in an array.");
 | 
					        .expect_err("Testing panic at trying to set a value at negative index in an array.");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[test]
 | 
					#[test]
 | 
				
			||||||
fn db_remove() {
 | 
					fn db_remove_value() {
 | 
				
			||||||
    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 file = db.file_mut("administration", "registered").unwrap();
 | 
				
			||||||
    //  Removing non-existent value
 | 
					    //  Removing non-existent value
 | 
				
			||||||
    assert_eq!(db.remove(file_id, "/76561198025127722/something"), None);
 | 
					    assert_eq!(file.remove("/76561198025127722/something"), None);
 | 
				
			||||||
    //  Remove simple value
 | 
					    //  Remove simple value
 | 
				
			||||||
    assert_eq!(
 | 
					    assert_eq!(
 | 
				
			||||||
        db.remove(file_id, "/76561198025127722/password_hash")
 | 
					        file.remove("/76561198025127722/password_hash").unwrap(),
 | 
				
			||||||
            .unwrap(),
 | 
					 | 
				
			||||||
        json!("fce798e0804dfb217f929bdba26745024f37f6b6ba7406f3775176e20dd5089d")
 | 
					        json!("fce798e0804dfb217f929bdba26745024f37f6b6ba7406f3775176e20dd5089d")
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    assert!(!db.contains_value(file_id, "/76561198025127722/password_hash"));
 | 
					    assert!(!file.contains("/76561198025127722/password_hash"));
 | 
				
			||||||
    //  Remove complex value (array)
 | 
					    //  Remove complex value (array)
 | 
				
			||||||
    assert_eq!(
 | 
					    assert_eq!(
 | 
				
			||||||
        db.remove(file_id, "/76561198044316328/groups").unwrap(),
 | 
					        file.remove("/76561198044316328/groups").unwrap(),
 | 
				
			||||||
        json!(["admin"])
 | 
					        json!(["admin"])
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    assert!(!db.contains_value(file_id, "/76561198044316328/groups/0"));
 | 
					    assert!(!file.contains("/76561198044316328/groups/0"));
 | 
				
			||||||
    assert!(!db.contains_value(file_id, "/76561198044316328/groups"));
 | 
					    assert!(!file.contains("/76561198044316328/groups"));
 | 
				
			||||||
    //  Remove array elements
 | 
					    //  Remove array elements
 | 
				
			||||||
    assert_eq!(
 | 
					    assert_eq!(
 | 
				
			||||||
        db.remove(file_id, "/76561198025127722/allowed_ips/0")
 | 
					        file.remove("/76561198025127722/allowed_ips/0").unwrap(),
 | 
				
			||||||
            .unwrap(),
 | 
					 | 
				
			||||||
        json!("127.0.0.1")
 | 
					        json!("127.0.0.1")
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    assert!(db.contains_value(file_id, "/76561198025127722/allowed_ips/0"));
 | 
					    assert!(file.contains("/76561198025127722/allowed_ips/0"));
 | 
				
			||||||
    assert!(!db.contains_value(file_id, "/76561198025127722/allowed_ips/1"));
 | 
					    assert!(!file.contains("/76561198025127722/allowed_ips/1"));
 | 
				
			||||||
    assert_eq!(
 | 
					    assert_eq!(
 | 
				
			||||||
        db.remove(file_id, "/76561198025127722/allowed_ips/0")
 | 
					        file.remove("/76561198025127722/allowed_ips/0").unwrap(),
 | 
				
			||||||
            .unwrap(),
 | 
					 | 
				
			||||||
        json!("192.168.0.100")
 | 
					        json!("192.168.0.100")
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    assert!(db.contains_value(file_id, "/76561198025127722/allowed_ips"));
 | 
					    assert!(file.contains("/76561198025127722/allowed_ips"));
 | 
				
			||||||
    assert!(!db.contains_value(file_id, "/76561198025127722/allowed_ips/0"));
 | 
					    assert!(!file.contains("/76561198025127722/allowed_ips/0"));
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[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);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -2,12 +2,15 @@ use std::env;
 | 
				
			|||||||
use std::path::Path;
 | 
					use std::path::Path;
 | 
				
			||||||
mod database;
 | 
					mod database;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use simplelog::{Config, LevelFilter, SimpleLogger};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn main() {
 | 
					fn main() {
 | 
				
			||||||
 | 
					    let _ = SimpleLogger::init(LevelFilter::Info, Config::default());
 | 
				
			||||||
    let args: Vec<String> = env::args().collect();
 | 
					    let args: Vec<String> = env::args().collect();
 | 
				
			||||||
    let filename = &args[1];
 | 
					    let filename = &args[1];
 | 
				
			||||||
    let db = database::Database::new(Path::new(filename));
 | 
					    let db = database::Database::new(Path::new(filename));
 | 
				
			||||||
    match db {
 | 
					    /*match db {
 | 
				
			||||||
        Ok(db) => print!("{}", db),
 | 
					        Ok(db) => print!("{}", db),
 | 
				
			||||||
        Err(error) => println!("OH NO: {}", error),
 | 
					        Err(error) => println!("OH NO: {}", error),
 | 
				
			||||||
    }
 | 
					    }*/
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user