From f3205afed5aefdddb589148684324fbd0933b51b Mon Sep 17 00:00:00 2001 From: Joshua Thijssen Date: Fri, 10 Nov 2023 13:17:43 +0100 Subject: [PATCH 01/10] Added initial config store --- Cargo.lock | 44 ++++++++ Cargo.toml | 2 + src/config.rs | 153 +++++++++++++++++++++++++++ src/config/settings.json | 46 ++++++++ src/config/settings.rs | 123 +++++++++++++++++++++ src/config/storage.rs | 3 + src/config/storage/json_storage.rs | 29 +++++ src/config/storage/memory_storage.rs | 33 ++++++ src/config/storage/sqlite_storage.rs | 49 +++++++++ src/lib.rs | 3 + 10 files changed, 485 insertions(+) create mode 100644 src/config.rs create mode 100644 src/config/settings.json create mode 100644 src/config/settings.rs create mode 100644 src/config/storage.rs create mode 100644 src/config/storage/json_storage.rs create mode 100644 src/config/storage/memory_storage.rs create mode 100644 src/config/storage/sqlite_storage.rs diff --git a/Cargo.lock b/Cargo.lock index 3d365e8ca..7d44953f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -319,12 +319,14 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "sqlite", "test-case", "thiserror", "typed-arena", "ureq", "uuid", "walkdir", + "wildmatch", ] [[package]] @@ -528,6 +530,12 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "plotters" version = "0.3.5" @@ -801,6 +809,36 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "sqlite" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03801c10193857d6a4a71ec46cee198a15cbc659622aabe1db0d0bdbefbcf8e6" +dependencies = [ + "libc", + "sqlite3-sys", +] + +[[package]] +name = "sqlite3-src" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfc95a51a1ee38839599371685b9d4a926abb51791f0bc3bf8c3bb7867e6e454" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "sqlite3-sys" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2752c669433e40ebb08fde824146f50d9628aa0b66a3b7fc6be34db82a8063b" +dependencies = [ + "libc", + "sqlite3-src", +] + [[package]] name = "syn" version = "1.0.109" @@ -1064,6 +1102,12 @@ version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" +[[package]] +name = "wildmatch" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee583bdc5ff1cf9db20e9db5bb3ff4c3089a8f6b8b31aff265c9aba85812db86" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index ef614609b..3f45ca04a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,8 @@ colored = "2.0.4" walkdir = "2.3" nom = "7.1.3" nom_locate = "4.2.0" +sqlite = "0.32.0" +wildmatch = "2.1.1" [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 000000000..2b75b7a94 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,153 @@ +pub mod storage; +pub mod settings; + +use std::collections::HashMap; +use serde_derive::Deserialize; +use serde_json::Value; +use wildmatch::WildMatch; +use crate::config::settings::{Setting, SettingInfo}; + +const SETTINGS_JSON: &str = include_str!("./config/settings.json"); + +#[derive(Debug, Deserialize)] +struct JsonEntry { + key: String, + #[serde(rename = "type")] + entry_type: String, + default: String, + description: String, +} + +trait StorageAdapter { + /// Retrieves a setting from the storage + fn get_setting(&self, key: &str) -> Option; + /// Stores a given setting to the storage + fn set_setting(&mut self, key: &str, value: Setting); + /// Retrieves all the settings in the storage + fn get_all_settings(&self) -> HashMap; +} + +/// Configuration store is the place where the gosub engine can find all configurable options +pub struct ConfigStore { + /// A hashmap of all settings so we can search o(1) time + settings: HashMap, + /// A hashmap of all setting descriptions + settings_info: HashMap, + /// Keys of all settings so we can iterate keys easily + setting_keys: Vec, + /// The storage adapter used for persisting and loading keys + storage: Box +} + +impl ConfigStore { + /// Creates a new store with the given storage adapter and preloads the store if needed + fn new(storage: Box, preload: bool) -> Self { + let mut store = ConfigStore { + settings: HashMap::new(), + settings_info: HashMap::new(), + setting_keys: Vec::new(), + storage + }; + + // Populate the settings from the json file + store.populate_settings(); + + // preload the settings if requested + if preload { + let all_settings = store.storage.get_all_settings(); + for (key, value) in all_settings { + store.settings.insert(key, value); + } + } + + store + } + + /// Returns a list of keys that mathces the given search string (can use *) + fn find_setting_keys(&self, search: &str) -> Vec { + let search = WildMatch::new(search); + + let mut keys = Vec::new(); + for key in &self.setting_keys { + if search.matches(key.as_str()) { + let key = key.clone(); + keys.push(key); + } + } + + keys + } + + /// Returns the setting with the given key + /// If the setting does not exist, it will try and load it from the storage adapter + fn get(&mut self, key: &str, default: Option) -> Setting { + if let Some(setting) = self.settings.get(key) { + return setting.clone(); + } + + // Setting not found, try and load it from the storage adapter + if let Some(setting) = self.storage.get_setting(key) { + self.settings.insert(key.to_string(), setting.clone()); + return setting.clone(); + } + + // Panic if we can't find the setting, and we don't have a default + if default.is_none() { + panic!("Setting {} not found", key); + } + + default.unwrap() + } + + fn set(&mut self, key: &str, value: Setting) { + self.settings.insert(key.to_string(), value.clone()); + self.storage.set_setting(key, value); + } + + /// Populates the settings in the store from the settings.json file + fn populate_settings(&mut self) { + let json_data = serde_json::from_str(SETTINGS_JSON); + if json_data.is_err() { + panic!("Failed to parse settings.json"); + } + + let json_data = json_data.unwrap(); + if let Value::Object(data) = json_data { + for (section_prefix, section_entries) in data.iter() { + let section_entries: Vec = serde_json::from_value(section_entries.clone()).expect("Failed to parse settings.json"); + + for entry in section_entries { + let key = format!("{}.{}", section_prefix, entry.key); + + let info = SettingInfo{ + key: key.clone(), + description: entry.description, + default: Setting::from_string(entry.default.as_str()).expect("cannot parse default setting"), + last_accessed: 0, + }; + + self.setting_keys.push(key.clone()); + self.settings_info.insert(key.clone(), info.clone()); + self.settings.insert(key.clone(), info.default.clone()); + } + } + } + } +} + +#[cfg(test)] +mod test { + use crate::config::storage::memory_storage::MemoryStorageAdapter; + use super::*; + + #[test] + fn test_config_store() { + let mut store = ConfigStore::new(Box::new(MemoryStorageAdapter::new()), true); + let setting = store.get("dns.local_resolver.enabled", None); + assert_eq!(setting, Setting::Bool(false)); + + store.set("dns.local_resolver.enabled", Setting::Bool(true)); + let setting = store.get("dns.local_resolver.enabled", None); + assert_eq!(setting, Setting::Bool(true)); + } +} \ No newline at end of file diff --git a/src/config/settings.json b/src/config/settings.json new file mode 100644 index 000000000..573a0e5b9 --- /dev/null +++ b/src/config/settings.json @@ -0,0 +1,46 @@ +{ + "dns": [ + { + "key": "local_resolver.enabled", + "type": "b", + "default": "b:false", + "description": "This setting enables the local DNS resolver. When enabled, GosuB will use the local DNS resolver to resolve DNS queries. When disabled, GosuB will use the DNS resolver configured in the operating system." + }, + { + "key": "doh.enabled", + "type": "b", + "default": "b:false", + "description": "This setting enabled DNS over HTTPS. A secure way of communicating with DNS servers." + } + ], + "useragent": [ + { + "key": "default_page", + "type": "s", + "default": "s:about:blank", + "description": "This setting sets the default page to load when GosuB starts or when a new tab is opened." + }, + { + "key": "tab.close_button", + "type": "m", + "values": "left,right", + "default": "m:left", + "description": "Defines where the close button on tabs located" + }, + { + "key": "tab.max_opened", + "type": "i", + "values": "-1,0-9999", + "default": "i:-1", + "description": "Defines how many tabs may be opened inside a window. -1 means unlimited." + } + ], + "renderer": [ + { + "key": "opengl.enabled", + "type": "b", + "default": "b:true", + "description": "When set to true, the OpenGL renderer will be used. When set to false, the software renderer will be used." + } + ] +} \ No newline at end of file diff --git a/src/config/settings.rs b/src/config/settings.rs new file mode 100644 index 000000000..21f1518d1 --- /dev/null +++ b/src/config/settings.rs @@ -0,0 +1,123 @@ +/// A setting can be either a signed integer, unsigned integer, string, map or boolean. +/// Maps could be created by using comma separated strings maybe +#[derive(Clone, PartialEq, Debug)] +pub enum Setting { + SInt(isize), + UInt(usize), + String(String), + Bool(bool), + Map(Vec), +} + +impl Setting { + // first element is the type: + // b:true + // i:-123 + // u:234 + // s:hello world + // m:foo,bar,baz + + /// Converts a string to a setting or None when the string is invalid + pub(crate) fn from_string(p0: &str) -> Option { + let mut parts = p0.splitn(2, ':'); + let p1 = parts.next().unwrap(); + let p2 = parts.next().unwrap(); + + match p1 { + "b" => { + match p2.parse::() { + Ok(value) => Some(Setting::Bool(value)), + Err(_) => None + } + } + "i" => { + match p2.parse::() { + Ok(value) => Some(Setting::SInt(value)), + Err(_) => None + } + } + "u" => { + match p2.parse::() { + Ok(value) => Some(Setting::UInt(value)), + Err(_) => None + } + } + "s" => Some(Setting::String(p2.to_string())), + "m" => { + let mut values = Vec::new(); + for value in p2.split(',') { + values.push(value.to_string()); + } + Some(Setting::Map(values)) + }, + _ => None + } + } + + /// Converts a setting to a string representation + pub(crate) fn to_string(&self) -> String { + match self { + Setting::SInt(value) => format!("i:{}", value), + Setting::UInt(value) => format!("u:{}", value), + Setting::String(value) => format!("s:{}", value), + Setting::Bool(value) => format!("b:{}", value), + Setting::Map(values) => { + let mut result = String::new(); + for value in values { + result.push_str(value); + result.push(','); + } + result.pop(); + format!("m:{}", result) + } + } + } +} + +/// SettingInfo returns information about a given setting +#[derive(Clone, PartialEq, Debug)] +pub struct SettingInfo { + /// Name of the key (dot notation, (ie: dns.resolver.enabled + pub key: String, + /// Description of the setting + pub description: String, + /// Default setting if none has been specified + pub default: Setting, + /// Timestamp this setting is last accessed (useful for debugging old/obsolete settings) + pub last_accessed: u64 +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn setting() { + let s = Setting::from_string("b:true"); + assert_eq!(s, Some(Setting::Bool(true))); + + let s = Setting::from_string("i:-1"); + assert_eq!(s, Some(Setting::SInt(-1))); + + let s = Setting::from_string("i:1"); + assert_eq!(s, Some(Setting::SInt(1))); + + let s = Setting::from_string("s:hello world"); + assert_eq!(s, Some(Setting::String("hello world".into()))); + + let s = Setting::from_string("m:foo,bar,baz"); + assert_eq!(s, Some(Setting::Map(vec!["foo".into(), "bar".into(), "baz".into()]))); + + let s = Setting::from_string("notexist:true"); + assert_eq!(s, None); + + let s = Setting::from_string("b:foobar"); + assert_eq!(s, None); + + let s = Setting::from_string("i:foobar"); + assert_eq!(s, None); + + let s = Setting::from_string("u:-1"); + assert_eq!(s, None); + } +} diff --git a/src/config/storage.rs b/src/config/storage.rs new file mode 100644 index 000000000..e40088c06 --- /dev/null +++ b/src/config/storage.rs @@ -0,0 +1,3 @@ +pub mod sqlite_storage; +pub mod json_storage; +pub mod memory_storage; \ No newline at end of file diff --git a/src/config/storage/json_storage.rs b/src/config/storage/json_storage.rs new file mode 100644 index 000000000..a0bbe8dd9 --- /dev/null +++ b/src/config/storage/json_storage.rs @@ -0,0 +1,29 @@ +use std::collections::HashMap; +use crate::config::settings::Setting; +use crate::config::StorageAdapter; + +pub struct JsonStorageAdapter { + path: String +} + +impl JsonStorageAdapter { + fn new(path: String) -> Self { + JsonStorageAdapter { + path + } + } +} + +impl StorageAdapter for JsonStorageAdapter { + fn get_setting(&self, _key: &str) -> Option { + todo!() + } + + fn set_setting(&mut self, _key: &str, _value: Setting) { + todo!() + } + + fn get_all_settings(&self) -> HashMap { + todo!() + } +} diff --git a/src/config/storage/memory_storage.rs b/src/config/storage/memory_storage.rs new file mode 100644 index 000000000..d4c6b28c5 --- /dev/null +++ b/src/config/storage/memory_storage.rs @@ -0,0 +1,33 @@ +use std::collections::HashMap; +use crate::config::settings::Setting; +use crate::config::StorageAdapter; + +pub struct MemoryStorageAdapter { + store: HashMap +} + +impl MemoryStorageAdapter { + pub fn new() -> Self { + MemoryStorageAdapter { + store: HashMap::new() + } + } +} + +impl StorageAdapter for MemoryStorageAdapter { + fn get_setting(&self, key: &str) -> Option { + let v = self.store.get(key); + match v { + Some(v) => Some(v.clone()), + None => None + } + } + + fn set_setting(&mut self, key: &str, value: Setting) { + self.store.insert(key.to_string(), value); + } + + fn get_all_settings(&self) -> HashMap { + self.store.clone() + } +} diff --git a/src/config/storage/sqlite_storage.rs b/src/config/storage/sqlite_storage.rs new file mode 100644 index 000000000..17029b1c0 --- /dev/null +++ b/src/config/storage/sqlite_storage.rs @@ -0,0 +1,49 @@ +use std::collections::HashMap; +use crate::config::settings::Setting; +use crate::config::StorageAdapter; + +struct SqlStorageAdapter { + connection: sqlite::Connection +} + +impl SqlStorageAdapter { + fn new(path: String) -> Self { + let conn = sqlite::open(path).expect("cannot open db file"); + + let query = "CREATE TABLE IF NOT EXISTS settings ( + id INTEGER PRIMARY KEY, + key TEXT NOT NULL, + value TEXT NOT NULL, + )"; + conn.execute(query).unwrap(); + + SqlStorageAdapter { + connection: conn + } + + } +} + +impl StorageAdapter for SqlStorageAdapter +{ + fn get_setting(&self, key: &str) -> Option { + let query = "SELECT * FROM settings WHERE key = :key"; + let mut statement = self.connection.prepare(query).unwrap(); + statement.bind((":key", key)).unwrap(); + + Setting::from_string(key) + } + + fn set_setting(&mut self, key: &str, value: Setting) { + let query = "INSERT OR REPLACE INTO settings (key, value) VALUES (:key, :value)"; + let mut statement = self.connection.prepare(query).unwrap(); + statement.bind((":key", key)).unwrap(); + statement.bind((":value", value.to_string().as_str())).unwrap(); + + statement.next().unwrap(); + } + + fn get_all_settings(&self) -> HashMap { + todo!() + } +} diff --git a/src/lib.rs b/src/lib.rs index e47220fee..e81244136 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,3 +9,6 @@ pub mod css3; pub mod html5; pub mod testing; pub mod types; + +#[allow(dead_code)] +pub mod config; From bdcc6ca0efedb06035bfe66495703bf729a7b477 Mon Sep 17 00:00:00 2001 From: Joshua Thijssen Date: Fri, 10 Nov 2023 18:12:55 +0100 Subject: [PATCH 02/10] Added config-store binary and some adjustments on the adapters --- Cargo.lock | 178 ++++++++++++++++++++++++++- Cargo.toml | 3 + src/bin/config-store.rs | 115 +++++++++++++++++ src/config.rs | 25 ++-- src/config/settings.rs | 4 +- src/config/storage/json_storage.rs | 4 +- src/config/storage/sqlite_storage.rs | 25 ++-- 7 files changed, 329 insertions(+), 25 deletions(-) create mode 100644 src/bin/config-store.rs diff --git a/Cargo.lock b/Cargo.lock index 7d44953f0..f865a723e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,12 +23,54 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "anstream" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys", +] + [[package]] name = "anyhow" version = "1.0.75" @@ -115,28 +157,72 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.6" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" +checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] name = "clap_builder" -version = "4.4.6" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663" dependencies = [ + "anstream", "anstyle", "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.38", ] [[package]] name = "clap_lex" -version = "0.5.1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "cli-table" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adfbb116d9e2c4be7011360d0c0bee565712c11e969c9609b25b619366dc379d" +dependencies = [ + "cli-table-derive", + "csv", + "termcolor", + "unicode-width", +] + +[[package]] +name = "cli-table-derive" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +checksum = "2af3bfb9da627b0a6c467624fb7963921433774ed435493b5c08a3053e829ad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "colored" @@ -233,6 +319,27 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -308,6 +415,8 @@ name = "gosub-engine" version = "0.1.0" dependencies = [ "anyhow", + "clap", + "cli-table", "colored", "criterion", "derive_more", @@ -321,6 +430,7 @@ dependencies = [ "serde_json", "sqlite", "test-case", + "textwrap", "thiserror", "typed-arena", "ureq", @@ -335,6 +445,12 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.3.3" @@ -803,6 +919,12 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + [[package]] name = "spin" version = "0.5.2" @@ -839,6 +961,12 @@ dependencies = [ "sqlite3-src", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "1.0.109" @@ -861,6 +989,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcolor" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" +dependencies = [ + "winapi-util", +] + [[package]] name = "test-case" version = "3.2.1" @@ -896,6 +1033,17 @@ dependencies = [ "test-case-core", ] +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.50" @@ -959,6 +1107,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + [[package]] name = "unicode-normalization" version = "0.1.22" @@ -968,6 +1122,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "untrusted" version = "0.7.1" @@ -1001,6 +1161,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "uuid" version = "1.5.0" diff --git a/Cargo.toml b/Cargo.toml index 3f45ca04a..74b1745e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,9 @@ nom = "7.1.3" nom_locate = "4.2.0" sqlite = "0.32.0" wildmatch = "2.1.1" +clap = { version = "4.4.7", features = ["derive"] } +cli-table = "0.4.7" +textwrap = "0.16.0" [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } diff --git a/src/bin/config-store.rs b/src/bin/config-store.rs new file mode 100644 index 000000000..97b9b4b70 --- /dev/null +++ b/src/bin/config-store.rs @@ -0,0 +1,115 @@ +use clap::{Parser, Subcommand}; +use derive_more::Display; +use gosub_engine::config::{ConfigStore, StorageAdapter}; +use gosub_engine::config::settings::Setting; +use gosub_engine::config::storage::json_storage::JsonStorageAdapter; +use gosub_engine::config::storage::sqlite_storage::SqliteStorageAdapter; + +#[derive(Debug, Parser)] +#[clap(name = "Config-Store", version = "0.1.0", author = "Gosub")] +struct Cli { + #[clap(flatten)] + global_opts: GlobalOpts, + + #[clap(subcommand)] + command: Commands, +} + +#[derive(Debug, Subcommand)] +enum Commands { + #[clap(arg_required_else_help = true, about = "View a setting")] + View { + #[clap(required = true, short = 'k', long = "key")] + key: String, + }, + #[clap(about = "List all settings")] + List, + #[clap(arg_required_else_help = true, about = "Set a setting")] + Set { + #[clap(required = true, short = 'k', long = "key")] + key: String, + #[clap(required = true, short = 'v', long = "value")] + value: String, + }, + #[clap(arg_required_else_help = true, about = "Search for a setting")] + Search { + #[clap(required = true, short = 'k', long = "key")] + key: String, + }, +} + +#[derive(Debug, Clone, Copy, clap::ValueEnum, Display)] +enum Engine { + Sqlite, + Json, +} + +impl std::str::FromStr for Engine { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "sqlite" => Ok(Engine::Sqlite), + "json" => Ok(Engine::Json), + _ => Err(()), + } + } +} + + +#[derive(Debug, Parser)] +struct GlobalOpts { + #[clap(short = 'e', long = "engine", global = true, default_value = "sqlite")] + engine: Engine, + #[clap(short = 'p', long = "path", global = true, default_value = "settings.db")] + path: String, +} + +fn main() { + let args = Cli::parse(); + + let storage_box: Box; + match args.global_opts.engine { + Engine::Sqlite => { + storage_box = Box::new(SqliteStorageAdapter::new(args.global_opts.path.as_str())); + } + Engine::Json => { + storage_box = Box::new(JsonStorageAdapter::new(args.global_opts.path.as_str())); + } + } + + let mut store = ConfigStore::new(storage_box, true); + + + match args.command { + Commands::View { key } => { + if !store.has(key.as_str()) { + println!("Key not found"); + return; + } + + let info = store.get_info(key.as_str()).unwrap(); + let value = store.get(key.as_str(), None); + + println!("Key : {}", key); + println!("Current Value : {}", value.to_string()); + println!("Default Value : {}", info.default.to_string()); + println!("Description : {}", info.description); + } + Commands::List => { + for key in store.find("*".into()) { + let value = store.get(key.as_str(), None); + println!("{:40}: {}", key, value.to_string()); + } + } + Commands::Set { key, value } => { + store.set(key.as_str(), Setting::from_string(value.as_str()).expect("incorrect value")); + } + Commands::Search { key } => { + for key in store.find(key.as_str().into()) { + let value = store.get(key.as_str(), None); + println!("{:40}: {}", key, value.to_string()); + } + } + } +} diff --git a/src/config.rs b/src/config.rs index 2b75b7a94..900681763 100644 --- a/src/config.rs +++ b/src/config.rs @@ -18,7 +18,7 @@ struct JsonEntry { description: String, } -trait StorageAdapter { +pub trait StorageAdapter { /// Retrieves a setting from the storage fn get_setting(&self, key: &str) -> Option; /// Stores a given setting to the storage @@ -41,7 +41,7 @@ pub struct ConfigStore { impl ConfigStore { /// Creates a new store with the given storage adapter and preloads the store if needed - fn new(storage: Box, preload: bool) -> Self { + pub fn new(storage: Box, preload: bool) -> Self { let mut store = ConfigStore { settings: HashMap::new(), settings_info: HashMap::new(), @@ -63,8 +63,12 @@ impl ConfigStore { store } + pub fn has(&self, key: &str) -> bool { + self.settings.contains_key(key) + } + /// Returns a list of keys that mathces the given search string (can use *) - fn find_setting_keys(&self, search: &str) -> Vec { + pub fn find(&self, search: &str) -> Vec { let search = WildMatch::new(search); let mut keys = Vec::new(); @@ -78,9 +82,16 @@ impl ConfigStore { keys } + pub fn get_info(&self, key: &str) -> Option { + match self.settings_info.get(key) { + Some(info) => Some(info.clone()), + None => None + } + } + /// Returns the setting with the given key /// If the setting does not exist, it will try and load it from the storage adapter - fn get(&mut self, key: &str, default: Option) -> Setting { + pub fn get(&mut self, key: &str, default: Option) -> Setting { if let Some(setting) = self.settings.get(key) { return setting.clone(); } @@ -99,7 +110,7 @@ impl ConfigStore { default.unwrap() } - fn set(&mut self, key: &str, value: Setting) { + pub fn set(&mut self, key: &str, value: Setting) { self.settings.insert(key.to_string(), value.clone()); self.storage.set_setting(key, value); } @@ -137,12 +148,12 @@ impl ConfigStore { #[cfg(test)] mod test { - use crate::config::storage::memory_storage::MemoryStorageAdapter; + use crate::config::storage::sqlite_storage::SqliteStorageAdapter; use super::*; #[test] fn test_config_store() { - let mut store = ConfigStore::new(Box::new(MemoryStorageAdapter::new()), true); + let mut store = ConfigStore::new(Box::new(SqliteStorageAdapter::new("settings.db")), true); let setting = store.get("dns.local_resolver.enabled", None); assert_eq!(setting, Setting::Bool(false)); diff --git a/src/config/settings.rs b/src/config/settings.rs index 21f1518d1..634cab571 100644 --- a/src/config/settings.rs +++ b/src/config/settings.rs @@ -18,7 +18,7 @@ impl Setting { // m:foo,bar,baz /// Converts a string to a setting or None when the string is invalid - pub(crate) fn from_string(p0: &str) -> Option { + pub fn from_string(p0: &str) -> Option { let mut parts = p0.splitn(2, ':'); let p1 = parts.next().unwrap(); let p2 = parts.next().unwrap(); @@ -55,7 +55,7 @@ impl Setting { } /// Converts a setting to a string representation - pub(crate) fn to_string(&self) -> String { + pub fn to_string(&self) -> String { match self { Setting::SInt(value) => format!("i:{}", value), Setting::UInt(value) => format!("u:{}", value), diff --git a/src/config/storage/json_storage.rs b/src/config/storage/json_storage.rs index a0bbe8dd9..fe394742f 100644 --- a/src/config/storage/json_storage.rs +++ b/src/config/storage/json_storage.rs @@ -7,9 +7,9 @@ pub struct JsonStorageAdapter { } impl JsonStorageAdapter { - fn new(path: String) -> Self { + pub fn new(path: &str) -> Self { JsonStorageAdapter { - path + path: path.to_string() } } } diff --git a/src/config/storage/sqlite_storage.rs b/src/config/storage/sqlite_storage.rs index 17029b1c0..2d4c042fb 100644 --- a/src/config/storage/sqlite_storage.rs +++ b/src/config/storage/sqlite_storage.rs @@ -2,29 +2,28 @@ use std::collections::HashMap; use crate::config::settings::Setting; use crate::config::StorageAdapter; -struct SqlStorageAdapter { +pub struct SqliteStorageAdapter { connection: sqlite::Connection } -impl SqlStorageAdapter { - fn new(path: String) -> Self { +impl SqliteStorageAdapter { + pub fn new(path: &str) -> Self { let conn = sqlite::open(path).expect("cannot open db file"); let query = "CREATE TABLE IF NOT EXISTS settings ( id INTEGER PRIMARY KEY, key TEXT NOT NULL, - value TEXT NOT NULL, + value TEXT NOT NULL )"; conn.execute(query).unwrap(); - SqlStorageAdapter { + SqliteStorageAdapter { connection: conn } - } } -impl StorageAdapter for SqlStorageAdapter +impl StorageAdapter for SqliteStorageAdapter { fn get_setting(&self, key: &str) -> Option { let query = "SELECT * FROM settings WHERE key = :key"; @@ -44,6 +43,16 @@ impl StorageAdapter for SqlStorageAdapter } fn get_all_settings(&self) -> HashMap { - todo!() + let query = "SELECT * FROM settings"; + let mut statement = self.connection.prepare(query).unwrap(); + + let mut settings = HashMap::new(); + while let sqlite::State::Row = statement.next().unwrap() { + let key = statement.read::(1).unwrap(); + let value = statement.read::(2).unwrap(); + settings.insert(key, Setting::from_string(value.as_str()).unwrap()); + } + + settings } } From 66b0164d76ad5621323344c82c71c5bfb399df0f Mon Sep 17 00:00:00 2001 From: Joshua Thijssen Date: Fri, 10 Nov 2023 18:20:25 +0100 Subject: [PATCH 03/10] clippy / fmt --- src/bin/config-store.rs | 41 ++--- src/config.rs | 32 ++-- src/config/settings.rs | 244 +++++++++++++-------------- src/config/storage.rs | 6 +- src/config/storage/json_storage.rs | 58 +++---- src/config/storage/memory_storage.rs | 64 ++++--- src/config/storage/sqlite_storage.rs | 115 +++++++------ 7 files changed, 278 insertions(+), 282 deletions(-) diff --git a/src/bin/config-store.rs b/src/bin/config-store.rs index 97b9b4b70..b8aa65fa4 100644 --- a/src/bin/config-store.rs +++ b/src/bin/config-store.rs @@ -1,9 +1,9 @@ use clap::{Parser, Subcommand}; use derive_more::Display; -use gosub_engine::config::{ConfigStore, StorageAdapter}; use gosub_engine::config::settings::Setting; use gosub_engine::config::storage::json_storage::JsonStorageAdapter; use gosub_engine::config::storage::sqlite_storage::SqliteStorageAdapter; +use gosub_engine::config::{ConfigStore, StorageAdapter}; #[derive(Debug, Parser)] #[clap(name = "Config-Store", version = "0.1.0", author = "Gosub")] @@ -56,31 +56,29 @@ impl std::str::FromStr for Engine { } } - #[derive(Debug, Parser)] struct GlobalOpts { #[clap(short = 'e', long = "engine", global = true, default_value = "sqlite")] engine: Engine, - #[clap(short = 'p', long = "path", global = true, default_value = "settings.db")] + #[clap( + short = 'p', + long = "path", + global = true, + default_value = "settings.db" + )] path: String, } fn main() { let args = Cli::parse(); - let storage_box: Box; - match args.global_opts.engine { - Engine::Sqlite => { - storage_box = Box::new(SqliteStorageAdapter::new(args.global_opts.path.as_str())); - } - Engine::Json => { - storage_box = Box::new(JsonStorageAdapter::new(args.global_opts.path.as_str())); - } - } + let storage_box: Box = match args.global_opts.engine { + Engine::Sqlite => Box::new(SqliteStorageAdapter::new(args.global_opts.path.as_str())), + Engine::Json => Box::new(JsonStorageAdapter::new(args.global_opts.path.as_str())), + }; let mut store = ConfigStore::new(storage_box, true); - match args.command { Commands::View { key } => { if !store.has(key.as_str()) { @@ -92,23 +90,26 @@ fn main() { let value = store.get(key.as_str(), None); println!("Key : {}", key); - println!("Current Value : {}", value.to_string()); - println!("Default Value : {}", info.default.to_string()); + println!("Current Value : {}", value); + println!("Default Value : {}", info.default); println!("Description : {}", info.description); } Commands::List => { - for key in store.find("*".into()) { + for key in store.find("*") { let value = store.get(key.as_str(), None); - println!("{:40}: {}", key, value.to_string()); + println!("{:40}: {}", key, value); } } Commands::Set { key, value } => { - store.set(key.as_str(), Setting::from_string(value.as_str()).expect("incorrect value")); + store.set( + key.as_str(), + Setting::from_string(value.as_str()).expect("incorrect value"), + ); } Commands::Search { key } => { - for key in store.find(key.as_str().into()) { + for key in store.find(key.as_str()) { let value = store.get(key.as_str(), None); - println!("{:40}: {}", key, value.to_string()); + println!("{:40}: {}", key, value); } } } diff --git a/src/config.rs b/src/config.rs index 900681763..760b158bd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,11 +1,11 @@ -pub mod storage; pub mod settings; +pub mod storage; -use std::collections::HashMap; +use crate::config::settings::{Setting, SettingInfo}; use serde_derive::Deserialize; use serde_json::Value; +use std::collections::HashMap; use wildmatch::WildMatch; -use crate::config::settings::{Setting, SettingInfo}; const SETTINGS_JSON: &str = include_str!("./config/settings.json"); @@ -36,7 +36,7 @@ pub struct ConfigStore { /// Keys of all settings so we can iterate keys easily setting_keys: Vec, /// The storage adapter used for persisting and loading keys - storage: Box + storage: Box, } impl ConfigStore { @@ -46,9 +46,9 @@ impl ConfigStore { settings: HashMap::new(), settings_info: HashMap::new(), setting_keys: Vec::new(), - storage + storage, }; - + // Populate the settings from the json file store.populate_settings(); @@ -83,10 +83,7 @@ impl ConfigStore { } pub fn get_info(&self, key: &str) -> Option { - match self.settings_info.get(key) { - Some(info) => Some(info.clone()), - None => None - } + self.settings_info.get(key).cloned() } /// Returns the setting with the given key @@ -125,15 +122,18 @@ impl ConfigStore { let json_data = json_data.unwrap(); if let Value::Object(data) = json_data { for (section_prefix, section_entries) in data.iter() { - let section_entries: Vec = serde_json::from_value(section_entries.clone()).expect("Failed to parse settings.json"); + let section_entries: Vec = + serde_json::from_value(section_entries.clone()) + .expect("Failed to parse settings.json"); for entry in section_entries { let key = format!("{}.{}", section_prefix, entry.key); - let info = SettingInfo{ + let info = SettingInfo { key: key.clone(), description: entry.description, - default: Setting::from_string(entry.default.as_str()).expect("cannot parse default setting"), + default: Setting::from_string(entry.default.as_str()) + .expect("cannot parse default setting"), last_accessed: 0, }; @@ -148,12 +148,12 @@ impl ConfigStore { #[cfg(test)] mod test { - use crate::config::storage::sqlite_storage::SqliteStorageAdapter; use super::*; + use crate::config::storage::memory_storage::MemoryStorageAdapter; #[test] fn test_config_store() { - let mut store = ConfigStore::new(Box::new(SqliteStorageAdapter::new("settings.db")), true); + let mut store = ConfigStore::new(Box::new(MemoryStorageAdapter::new()), true); let setting = store.get("dns.local_resolver.enabled", None); assert_eq!(setting, Setting::Bool(false)); @@ -161,4 +161,4 @@ mod test { let setting = store.get("dns.local_resolver.enabled", None); assert_eq!(setting, Setting::Bool(true)); } -} \ No newline at end of file +} diff --git a/src/config/settings.rs b/src/config/settings.rs index 634cab571..e9dbace38 100644 --- a/src/config/settings.rs +++ b/src/config/settings.rs @@ -1,123 +1,121 @@ -/// A setting can be either a signed integer, unsigned integer, string, map or boolean. -/// Maps could be created by using comma separated strings maybe -#[derive(Clone, PartialEq, Debug)] -pub enum Setting { - SInt(isize), - UInt(usize), - String(String), - Bool(bool), - Map(Vec), -} - -impl Setting { - // first element is the type: - // b:true - // i:-123 - // u:234 - // s:hello world - // m:foo,bar,baz - - /// Converts a string to a setting or None when the string is invalid - pub fn from_string(p0: &str) -> Option { - let mut parts = p0.splitn(2, ':'); - let p1 = parts.next().unwrap(); - let p2 = parts.next().unwrap(); - - match p1 { - "b" => { - match p2.parse::() { - Ok(value) => Some(Setting::Bool(value)), - Err(_) => None - } - } - "i" => { - match p2.parse::() { - Ok(value) => Some(Setting::SInt(value)), - Err(_) => None - } - } - "u" => { - match p2.parse::() { - Ok(value) => Some(Setting::UInt(value)), - Err(_) => None - } - } - "s" => Some(Setting::String(p2.to_string())), - "m" => { - let mut values = Vec::new(); - for value in p2.split(',') { - values.push(value.to_string()); - } - Some(Setting::Map(values)) - }, - _ => None - } - } - - /// Converts a setting to a string representation - pub fn to_string(&self) -> String { - match self { - Setting::SInt(value) => format!("i:{}", value), - Setting::UInt(value) => format!("u:{}", value), - Setting::String(value) => format!("s:{}", value), - Setting::Bool(value) => format!("b:{}", value), - Setting::Map(values) => { - let mut result = String::new(); - for value in values { - result.push_str(value); - result.push(','); - } - result.pop(); - format!("m:{}", result) - } - } - } -} - -/// SettingInfo returns information about a given setting -#[derive(Clone, PartialEq, Debug)] -pub struct SettingInfo { - /// Name of the key (dot notation, (ie: dns.resolver.enabled - pub key: String, - /// Description of the setting - pub description: String, - /// Default setting if none has been specified - pub default: Setting, - /// Timestamp this setting is last accessed (useful for debugging old/obsolete settings) - pub last_accessed: u64 -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn setting() { - let s = Setting::from_string("b:true"); - assert_eq!(s, Some(Setting::Bool(true))); - - let s = Setting::from_string("i:-1"); - assert_eq!(s, Some(Setting::SInt(-1))); - - let s = Setting::from_string("i:1"); - assert_eq!(s, Some(Setting::SInt(1))); - - let s = Setting::from_string("s:hello world"); - assert_eq!(s, Some(Setting::String("hello world".into()))); - - let s = Setting::from_string("m:foo,bar,baz"); - assert_eq!(s, Some(Setting::Map(vec!["foo".into(), "bar".into(), "baz".into()]))); - - let s = Setting::from_string("notexist:true"); - assert_eq!(s, None); - - let s = Setting::from_string("b:foobar"); - assert_eq!(s, None); - - let s = Setting::from_string("i:foobar"); - assert_eq!(s, None); - - let s = Setting::from_string("u:-1"); - assert_eq!(s, None); - } -} +use core::fmt::Display; + +/// A setting can be either a signed integer, unsigned integer, string, map or boolean. +/// Maps could be created by using comma separated strings maybe +#[derive(Clone, PartialEq, Debug)] +pub enum Setting { + SInt(isize), + UInt(usize), + String(String), + Bool(bool), + Map(Vec), +} + +impl Display for Setting { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Setting::SInt(value) => write!(f, "{}", value), + Setting::UInt(value) => write!(f, "{}", value), + Setting::String(value) => write!(f, "{}", value), + Setting::Bool(value) => write!(f, "{}", value), + Setting::Map(values) => { + let mut result = String::new(); + for value in values { + result.push_str(value); + result.push(','); + } + result.pop(); + write!(f, "{}", result) + } + } + } +} + +impl Setting { + // first element is the type: + // b:true + // i:-123 + // u:234 + // s:hello world + // m:foo,bar,baz + + /// Converts a string to a setting or None when the string is invalid + pub fn from_string(p0: &str) -> Option { + let (p1, p2) = p0.split_once(':').unwrap(); + + match p1 { + "b" => match p2.parse::() { + Ok(value) => Some(Setting::Bool(value)), + Err(_) => None, + }, + "i" => match p2.parse::() { + Ok(value) => Some(Setting::SInt(value)), + Err(_) => None, + }, + "u" => match p2.parse::() { + Ok(value) => Some(Setting::UInt(value)), + Err(_) => None, + }, + "s" => Some(Setting::String(p2.to_string())), + "m" => { + let mut values = Vec::new(); + for value in p2.split(',') { + values.push(value.to_string()); + } + Some(Setting::Map(values)) + } + _ => None, + } + } +} + +/// SettingInfo returns information about a given setting +#[derive(Clone, PartialEq, Debug)] +pub struct SettingInfo { + /// Name of the key (dot notation, (ie: dns.resolver.enabled + pub key: String, + /// Description of the setting + pub description: String, + /// Default setting if none has been specified + pub default: Setting, + /// Timestamp this setting is last accessed (useful for debugging old/obsolete settings) + pub last_accessed: u64, +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn setting() { + let s = Setting::from_string("b:true"); + assert_eq!(s, Some(Setting::Bool(true))); + + let s = Setting::from_string("i:-1"); + assert_eq!(s, Some(Setting::SInt(-1))); + + let s = Setting::from_string("i:1"); + assert_eq!(s, Some(Setting::SInt(1))); + + let s = Setting::from_string("s:hello world"); + assert_eq!(s, Some(Setting::String("hello world".into()))); + + let s = Setting::from_string("m:foo,bar,baz"); + assert_eq!( + s, + Some(Setting::Map(vec!["foo".into(), "bar".into(), "baz".into()])) + ); + + let s = Setting::from_string("notexist:true"); + assert_eq!(s, None); + + let s = Setting::from_string("b:foobar"); + assert_eq!(s, None); + + let s = Setting::from_string("i:foobar"); + assert_eq!(s, None); + + let s = Setting::from_string("u:-1"); + assert_eq!(s, None); + } +} diff --git a/src/config/storage.rs b/src/config/storage.rs index e40088c06..7af2025e9 100644 --- a/src/config/storage.rs +++ b/src/config/storage.rs @@ -1,3 +1,3 @@ -pub mod sqlite_storage; -pub mod json_storage; -pub mod memory_storage; \ No newline at end of file +pub mod json_storage; +pub mod memory_storage; +pub mod sqlite_storage; diff --git a/src/config/storage/json_storage.rs b/src/config/storage/json_storage.rs index fe394742f..f3b8f9efc 100644 --- a/src/config/storage/json_storage.rs +++ b/src/config/storage/json_storage.rs @@ -1,29 +1,29 @@ -use std::collections::HashMap; -use crate::config::settings::Setting; -use crate::config::StorageAdapter; - -pub struct JsonStorageAdapter { - path: String -} - -impl JsonStorageAdapter { - pub fn new(path: &str) -> Self { - JsonStorageAdapter { - path: path.to_string() - } - } -} - -impl StorageAdapter for JsonStorageAdapter { - fn get_setting(&self, _key: &str) -> Option { - todo!() - } - - fn set_setting(&mut self, _key: &str, _value: Setting) { - todo!() - } - - fn get_all_settings(&self) -> HashMap { - todo!() - } -} +use crate::config::settings::Setting; +use crate::config::StorageAdapter; +use std::collections::HashMap; + +pub struct JsonStorageAdapter { + path: String, +} + +impl JsonStorageAdapter { + pub fn new(path: &str) -> Self { + JsonStorageAdapter { + path: path.to_string(), + } + } +} + +impl StorageAdapter for JsonStorageAdapter { + fn get_setting(&self, _key: &str) -> Option { + todo!() + } + + fn set_setting(&mut self, _key: &str, _value: Setting) { + todo!() + } + + fn get_all_settings(&self) -> HashMap { + todo!() + } +} diff --git a/src/config/storage/memory_storage.rs b/src/config/storage/memory_storage.rs index d4c6b28c5..b70421cf8 100644 --- a/src/config/storage/memory_storage.rs +++ b/src/config/storage/memory_storage.rs @@ -1,33 +1,31 @@ -use std::collections::HashMap; -use crate::config::settings::Setting; -use crate::config::StorageAdapter; - -pub struct MemoryStorageAdapter { - store: HashMap -} - -impl MemoryStorageAdapter { - pub fn new() -> Self { - MemoryStorageAdapter { - store: HashMap::new() - } - } -} - -impl StorageAdapter for MemoryStorageAdapter { - fn get_setting(&self, key: &str) -> Option { - let v = self.store.get(key); - match v { - Some(v) => Some(v.clone()), - None => None - } - } - - fn set_setting(&mut self, key: &str, value: Setting) { - self.store.insert(key.to_string(), value); - } - - fn get_all_settings(&self) -> HashMap { - self.store.clone() - } -} +use crate::config::settings::Setting; +use crate::config::StorageAdapter; +use std::collections::HashMap; + +#[derive(Default)] +pub struct MemoryStorageAdapter { + store: HashMap, +} + +impl MemoryStorageAdapter { + pub fn new() -> Self { + MemoryStorageAdapter { + store: HashMap::new(), + } + } +} + +impl StorageAdapter for MemoryStorageAdapter { + fn get_setting(&self, key: &str) -> Option { + let v = self.store.get(key); + v.cloned() + } + + fn set_setting(&mut self, key: &str, value: Setting) { + self.store.insert(key.to_string(), value); + } + + fn get_all_settings(&self) -> HashMap { + self.store.clone() + } +} diff --git a/src/config/storage/sqlite_storage.rs b/src/config/storage/sqlite_storage.rs index 2d4c042fb..20fb7cadd 100644 --- a/src/config/storage/sqlite_storage.rs +++ b/src/config/storage/sqlite_storage.rs @@ -1,58 +1,57 @@ -use std::collections::HashMap; -use crate::config::settings::Setting; -use crate::config::StorageAdapter; - -pub struct SqliteStorageAdapter { - connection: sqlite::Connection -} - -impl SqliteStorageAdapter { - pub fn new(path: &str) -> Self { - let conn = sqlite::open(path).expect("cannot open db file"); - - let query = "CREATE TABLE IF NOT EXISTS settings ( - id INTEGER PRIMARY KEY, - key TEXT NOT NULL, - value TEXT NOT NULL - )"; - conn.execute(query).unwrap(); - - SqliteStorageAdapter { - connection: conn - } - } -} - -impl StorageAdapter for SqliteStorageAdapter -{ - fn get_setting(&self, key: &str) -> Option { - let query = "SELECT * FROM settings WHERE key = :key"; - let mut statement = self.connection.prepare(query).unwrap(); - statement.bind((":key", key)).unwrap(); - - Setting::from_string(key) - } - - fn set_setting(&mut self, key: &str, value: Setting) { - let query = "INSERT OR REPLACE INTO settings (key, value) VALUES (:key, :value)"; - let mut statement = self.connection.prepare(query).unwrap(); - statement.bind((":key", key)).unwrap(); - statement.bind((":value", value.to_string().as_str())).unwrap(); - - statement.next().unwrap(); - } - - fn get_all_settings(&self) -> HashMap { - let query = "SELECT * FROM settings"; - let mut statement = self.connection.prepare(query).unwrap(); - - let mut settings = HashMap::new(); - while let sqlite::State::Row = statement.next().unwrap() { - let key = statement.read::(1).unwrap(); - let value = statement.read::(2).unwrap(); - settings.insert(key, Setting::from_string(value.as_str()).unwrap()); - } - - settings - } -} +use crate::config::settings::Setting; +use crate::config::StorageAdapter; +use std::collections::HashMap; + +pub struct SqliteStorageAdapter { + connection: sqlite::Connection, +} + +impl SqliteStorageAdapter { + pub fn new(path: &str) -> Self { + let conn = sqlite::open(path).expect("cannot open db file"); + + let query = "CREATE TABLE IF NOT EXISTS settings ( + id INTEGER PRIMARY KEY, + key TEXT NOT NULL, + value TEXT NOT NULL + )"; + conn.execute(query).unwrap(); + + SqliteStorageAdapter { connection: conn } + } +} + +impl StorageAdapter for SqliteStorageAdapter { + fn get_setting(&self, key: &str) -> Option { + let query = "SELECT * FROM settings WHERE key = :key"; + let mut statement = self.connection.prepare(query).unwrap(); + statement.bind((":key", key)).unwrap(); + + Setting::from_string(key) + } + + fn set_setting(&mut self, key: &str, value: Setting) { + let query = "INSERT OR REPLACE INTO settings (key, value) VALUES (:key, :value)"; + let mut statement = self.connection.prepare(query).unwrap(); + statement.bind((":key", key)).unwrap(); + statement + .bind((":value", value.to_string().as_str())) + .unwrap(); + + statement.next().unwrap(); + } + + fn get_all_settings(&self) -> HashMap { + let query = "SELECT * FROM settings"; + let mut statement = self.connection.prepare(query).unwrap(); + + let mut settings = HashMap::new(); + while let sqlite::State::Row = statement.next().unwrap() { + let key = statement.read::(1).unwrap(); + let value = statement.read::(2).unwrap(); + settings.insert(key, Setting::from_string(value.as_str()).unwrap()); + } + + settings + } +} From 312f270f714add83a2fd97fdd1ce4c5fe91161db Mon Sep 17 00:00:00 2001 From: Joshua Thijssen Date: Sat, 11 Nov 2023 13:48:53 +0100 Subject: [PATCH 04/10] Making json adapapter work --- src/bin/config-store.rs | 6 +-- src/config.rs | 58 ++++++++++++++------ src/config/settings.rs | 45 +++++++++++----- src/config/storage/json_storage.rs | 87 +++++++++++++++++++++++++++--- 4 files changed, 158 insertions(+), 38 deletions(-) diff --git a/src/bin/config-store.rs b/src/bin/config-store.rs index b8aa65fa4..69e1d0dd2 100644 --- a/src/bin/config-store.rs +++ b/src/bin/config-store.rs @@ -87,7 +87,7 @@ fn main() { } let info = store.get_info(key.as_str()).unwrap(); - let value = store.get(key.as_str(), None); + let value = store.get(key.as_str()); println!("Key : {}", key); println!("Current Value : {}", value); @@ -96,7 +96,7 @@ fn main() { } Commands::List => { for key in store.find("*") { - let value = store.get(key.as_str(), None); + let value = store.get(key.as_str()); println!("{:40}: {}", key, value); } } @@ -108,7 +108,7 @@ fn main() { } Commands::Search { key } => { for key in store.find(key.as_str()) { - let value = store.get(key.as_str(), None); + let value = store.get(key.as_str()); println!("{:40}: {}", key, value); } } diff --git a/src/config.rs b/src/config.rs index 760b158bd..fa7e2f2fd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ pub mod settings; pub mod storage; +use std::mem; use crate::config::settings::{Setting, SettingInfo}; use serde_derive::Deserialize; use serde_json::Value; @@ -18,12 +19,16 @@ struct JsonEntry { description: String, } +/// StorageAdapter is the interface for storing and retrieving settings +/// This can be used to store settings in a database, json file, etc pub trait StorageAdapter { /// Retrieves a setting from the storage fn get_setting(&self, key: &str) -> Option; /// Stores a given setting to the storage fn set_setting(&mut self, key: &str, value: Setting); - /// Retrieves all the settings in the storage + /// Retrieves all the settings in the storage in one go. This is used for preloading the settings + /// into the ConfigStore and is more performant normally than calling get_setting manually for each + /// setting. fn get_all_settings(&self) -> HashMap; } @@ -31,7 +36,7 @@ pub trait StorageAdapter { pub struct ConfigStore { /// A hashmap of all settings so we can search o(1) time settings: HashMap, - /// A hashmap of all setting descriptions + /// A hashmap of all setting descriptions, default values and type information settings_info: HashMap, /// Keys of all settings so we can iterate keys easily setting_keys: Vec, @@ -63,11 +68,13 @@ impl ConfigStore { store } + /// Returns true when the store knows about the given key pub fn has(&self, key: &str) -> bool { self.settings.contains_key(key) } - /// Returns a list of keys that mathces the given search string (can use *) + /// Returns a list of keys that matches the given search string (can use ? and *) for search + /// wildcards. pub fn find(&self, search: &str) -> Vec { let search = WildMatch::new(search); @@ -82,13 +89,20 @@ impl ConfigStore { keys } + /// Retrieves information about the given key, or returns None when key is unknown pub fn get_info(&self, key: &str) -> Option { self.settings_info.get(key).cloned() } - /// Returns the setting with the given key - /// If the setting does not exist, it will try and load it from the storage adapter - pub fn get(&mut self, key: &str, default: Option) -> Setting { + /// Returns the setting with the given key. If the setting is not found in the current + /// store, it will load the key from the storage. If the key is still not found, it will + /// return the default value for the given key. Note that if the key is not found and no + /// default value is specified, this function will panic. + pub fn get(&mut self, key: &str) -> Setting { + if ! self.has(key) { + panic!("Setting {} not found", key); + } + if let Some(setting) = self.settings.get(key) { return setting.clone(); } @@ -99,15 +113,22 @@ impl ConfigStore { return setting.clone(); } - // Panic if we can't find the setting, and we don't have a default - if default.is_none() { - panic!("Setting {} not found", key); - } - - default.unwrap() + // Return the default value for the setting when nothing is found + let info = self.settings_info.get(key).unwrap(); + info.default.clone() } + /// Sets the given setting to the given value. Will persist the setting to the + /// storage. pub fn set(&mut self, key: &str, value: Setting) { + if ! self.has(key) { + panic!("key not found"); + } + let info = self.settings_info.get(key).unwrap(); + if mem::discriminant(&info.default) != mem::discriminant(&value) { + panic!("value is of different type than setting expects") + } + self.settings.insert(key.to_string(), value.clone()); self.storage.set_setting(key, value); } @@ -152,13 +173,20 @@ mod test { use crate::config::storage::memory_storage::MemoryStorageAdapter; #[test] - fn test_config_store() { + fn config_store() { let mut store = ConfigStore::new(Box::new(MemoryStorageAdapter::new()), true); - let setting = store.get("dns.local_resolver.enabled", None); + let setting = store.get("dns.local_resolver.enabled"); assert_eq!(setting, Setting::Bool(false)); store.set("dns.local_resolver.enabled", Setting::Bool(true)); - let setting = store.get("dns.local_resolver.enabled", None); + let setting = store.get("dns.local_resolver.enabled"); assert_eq!(setting, Setting::Bool(true)); } + + #[test] + #[should_panic] + fn invalid_setting() { + let mut store = ConfigStore::new(Box::new(MemoryStorageAdapter::new()), true); + store.set("dns.local_resolver.enabled", Setting::String("wont accept strings".into())); + } } diff --git a/src/config/settings.rs b/src/config/settings.rs index e9dbace38..50ad8fbff 100644 --- a/src/config/settings.rs +++ b/src/config/settings.rs @@ -1,4 +1,5 @@ use core::fmt::Display; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// A setting can be either a signed integer, unsigned integer, string, map or boolean. /// Maps could be created by using comma separated strings maybe @@ -11,13 +12,31 @@ pub enum Setting { Map(Vec), } +impl Serialize for Setting { + fn serialize(&self, serializer: S) -> Result where S: Serializer { + let s= self.to_string(); + serializer.collect_str(&s) + } +} + +impl<'de> Deserialize<'de> for Setting { + fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { + let value = String::deserialize(deserializer)?; + + match Setting::from_string(value.as_str()) { + None => Err(serde::de::Error::custom("Cannot deserialize")), + Some(setting) => Ok(setting) + } + } +} + impl Display for Setting { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Setting::SInt(value) => write!(f, "{}", value), - Setting::UInt(value) => write!(f, "{}", value), - Setting::String(value) => write!(f, "{}", value), - Setting::Bool(value) => write!(f, "{}", value), + Setting::SInt(value) => write!(f, "i:{}", value), + Setting::UInt(value) => write!(f, "u:{}", value), + Setting::String(value) => write!(f, "s:{}", value), + Setting::Bool(value) => write!(f, "b:{}", value), Setting::Map(values) => { let mut result = String::new(); for value in values { @@ -25,7 +44,7 @@ impl Display for Setting { result.push(','); } result.pop(); - write!(f, "{}", result) + write!(f, "m: {}", result) } } } @@ -40,26 +59,26 @@ impl Setting { // m:foo,bar,baz /// Converts a string to a setting or None when the string is invalid - pub fn from_string(p0: &str) -> Option { - let (p1, p2) = p0.split_once(':').unwrap(); + pub fn from_string(key: &str) -> Option { + let (key_type, key_value) = key.split_once(':').unwrap(); - match p1 { - "b" => match p2.parse::() { + match key_type { + "b" => match key_value.parse::() { Ok(value) => Some(Setting::Bool(value)), Err(_) => None, }, - "i" => match p2.parse::() { + "i" => match key_value.parse::() { Ok(value) => Some(Setting::SInt(value)), Err(_) => None, }, - "u" => match p2.parse::() { + "u" => match key_value.parse::() { Ok(value) => Some(Setting::UInt(value)), Err(_) => None, }, - "s" => Some(Setting::String(p2.to_string())), + "s" => Some(Setting::String(key_value.to_string())), "m" => { let mut values = Vec::new(); - for value in p2.split(',') { + for value in key_value.split(',') { values.push(value.to_string()); } Some(Setting::Map(values)) diff --git a/src/config/storage/json_storage.rs b/src/config/storage/json_storage.rs index f3b8f9efc..54e33427d 100644 --- a/src/config/storage/json_storage.rs +++ b/src/config/storage/json_storage.rs @@ -1,29 +1,102 @@ use crate::config::settings::Setting; use crate::config::StorageAdapter; use std::collections::HashMap; +use std::fs; +use std::fs::File; +use std::io::{Read, Seek, Write}; +use std::sync::{Arc, Mutex}; +use serde_json::Value; pub struct JsonStorageAdapter { path: String, + file_mutex: Arc>, + elements: HashMap } impl JsonStorageAdapter { pub fn new(path: &str) -> Self { - JsonStorageAdapter { + let file = match fs::metadata(path) { + Ok(metadata) => { + if ! metadata.is_file() { + panic!("json file is not a regular file") + } + + File::options() + .read(true) + .write(true) + .open(path) + .expect("failed to open json file") + } + Err(_) => { + let json = "{}"; + + let mut file = File::create(path).expect("cannot create json file"); + file.write_all(json.as_bytes()).expect("failed to write to file"); + + file + } + }; + + let mut adapter = JsonStorageAdapter { path: path.to_string(), - } + file_mutex: Arc::new(Mutex::new(file)), + elements: HashMap::new(), + }; + + adapter.read_file(); + + adapter } } impl StorageAdapter for JsonStorageAdapter { - fn get_setting(&self, _key: &str) -> Option { - todo!() + fn get_setting(&self, key: &str) -> Option { + match self.elements.get(key) { + None => None, + Some(setting) => Some(setting.clone()), + } } - fn set_setting(&mut self, _key: &str, _value: Setting) { - todo!() + fn set_setting(&mut self, key: &str, value: Setting) { + self.elements.insert(key.to_string(), value); + + self.write_file() } fn get_all_settings(&self) -> HashMap { - todo!() + self.elements.clone() + } +} + +impl JsonStorageAdapter { + /// Read whole json file and stores the data into self.elements + fn read_file(&mut self) { + let mut file = self.file_mutex.lock().expect("Mutex lock failed"); + + let mut buf= String::new(); + _ = file.read_to_string(&mut buf); + + let parsed_json: Value = serde_json::from_str(&buf).expect("Failed to parse json"); + + if let Value::Object(settings ) = parsed_json { + self.elements = HashMap::new(); + for (key, value) in settings.iter() { + if let Ok(setting) = serde_json::from_value(value.clone()) { + self.elements.insert(key.clone(), setting); + } + } + } + } + + /// Write the self.elements hashmap back to the file by truncating the file and writing the + /// data again. + fn write_file(&mut self) { + let mut file = self.file_mutex.lock().expect("Mutex lock failed"); + + let json = serde_json::to_string_pretty(&self.elements).expect("failed to serialize"); + + file.set_len(0).expect("failed to truncate file"); + file.seek(std::io::SeekFrom::Start(0)).expect("failed to seek"); + file.write_all(json.as_bytes()).expect("failed to write file"); } } From 26fd2518a71bb2596caed5149574b0fee37f61d4 Mon Sep 17 00:00:00 2001 From: Joshua Thijssen Date: Sat, 11 Nov 2023 15:23:41 +0100 Subject: [PATCH 05/10] clippy/fmt fix --- src/config.rs | 11 +++++++---- src/config/settings.rs | 14 ++++++++++---- src/config/storage/json_storage.rs | 24 ++++++++++++------------ 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/config.rs b/src/config.rs index fa7e2f2fd..d63bd7a8b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,11 +1,11 @@ pub mod settings; pub mod storage; -use std::mem; use crate::config::settings::{Setting, SettingInfo}; use serde_derive::Deserialize; use serde_json::Value; use std::collections::HashMap; +use std::mem; use wildmatch::WildMatch; const SETTINGS_JSON: &str = include_str!("./config/settings.json"); @@ -99,7 +99,7 @@ impl ConfigStore { /// return the default value for the given key. Note that if the key is not found and no /// default value is specified, this function will panic. pub fn get(&mut self, key: &str) -> Setting { - if ! self.has(key) { + if !self.has(key) { panic!("Setting {} not found", key); } @@ -121,7 +121,7 @@ impl ConfigStore { /// Sets the given setting to the given value. Will persist the setting to the /// storage. pub fn set(&mut self, key: &str, value: Setting) { - if ! self.has(key) { + if !self.has(key) { panic!("key not found"); } let info = self.settings_info.get(key).unwrap(); @@ -187,6 +187,9 @@ mod test { #[should_panic] fn invalid_setting() { let mut store = ConfigStore::new(Box::new(MemoryStorageAdapter::new()), true); - store.set("dns.local_resolver.enabled", Setting::String("wont accept strings".into())); + store.set( + "dns.local_resolver.enabled", + Setting::String("wont accept strings".into()), + ); } } diff --git a/src/config/settings.rs b/src/config/settings.rs index 50ad8fbff..ae8a42904 100644 --- a/src/config/settings.rs +++ b/src/config/settings.rs @@ -13,19 +13,25 @@ pub enum Setting { } impl Serialize for Setting { - fn serialize(&self, serializer: S) -> Result where S: Serializer { - let s= self.to_string(); + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = self.to_string(); serializer.collect_str(&s) } } impl<'de> Deserialize<'de> for Setting { - fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { let value = String::deserialize(deserializer)?; match Setting::from_string(value.as_str()) { None => Err(serde::de::Error::custom("Cannot deserialize")), - Some(setting) => Ok(setting) + Some(setting) => Ok(setting), } } } diff --git a/src/config/storage/json_storage.rs b/src/config/storage/json_storage.rs index 54e33427d..211ac1b34 100644 --- a/src/config/storage/json_storage.rs +++ b/src/config/storage/json_storage.rs @@ -1,23 +1,23 @@ use crate::config::settings::Setting; use crate::config::StorageAdapter; +use serde_json::Value; use std::collections::HashMap; use std::fs; use std::fs::File; use std::io::{Read, Seek, Write}; use std::sync::{Arc, Mutex}; -use serde_json::Value; pub struct JsonStorageAdapter { path: String, file_mutex: Arc>, - elements: HashMap + elements: HashMap, } impl JsonStorageAdapter { pub fn new(path: &str) -> Self { let file = match fs::metadata(path) { Ok(metadata) => { - if ! metadata.is_file() { + if !metadata.is_file() { panic!("json file is not a regular file") } @@ -31,7 +31,8 @@ impl JsonStorageAdapter { let json = "{}"; let mut file = File::create(path).expect("cannot create json file"); - file.write_all(json.as_bytes()).expect("failed to write to file"); + file.write_all(json.as_bytes()) + .expect("failed to write to file"); file } @@ -51,10 +52,7 @@ impl JsonStorageAdapter { impl StorageAdapter for JsonStorageAdapter { fn get_setting(&self, key: &str) -> Option { - match self.elements.get(key) { - None => None, - Some(setting) => Some(setting.clone()), - } + self.elements.get(key).cloned() } fn set_setting(&mut self, key: &str, value: Setting) { @@ -73,12 +71,12 @@ impl JsonStorageAdapter { fn read_file(&mut self) { let mut file = self.file_mutex.lock().expect("Mutex lock failed"); - let mut buf= String::new(); + let mut buf = String::new(); _ = file.read_to_string(&mut buf); let parsed_json: Value = serde_json::from_str(&buf).expect("Failed to parse json"); - if let Value::Object(settings ) = parsed_json { + if let Value::Object(settings) = parsed_json { self.elements = HashMap::new(); for (key, value) in settings.iter() { if let Ok(setting) = serde_json::from_value(value.clone()) { @@ -96,7 +94,9 @@ impl JsonStorageAdapter { let json = serde_json::to_string_pretty(&self.elements).expect("failed to serialize"); file.set_len(0).expect("failed to truncate file"); - file.seek(std::io::SeekFrom::Start(0)).expect("failed to seek"); - file.write_all(json.as_bytes()).expect("failed to write file"); + file.seek(std::io::SeekFrom::Start(0)) + .expect("failed to seek"); + file.write_all(json.as_bytes()) + .expect("failed to write file"); } } From b58cdd079c4daa022da3582495530389df811823 Mon Sep 17 00:00:00 2001 From: Eric Walker Date: Sat, 11 Nov 2023 11:23:29 -0700 Subject: [PATCH 06/10] Replace Box with a struct parameter --- .gitignore | 3 ++- src/bin/config-store.rs | 60 ++++++++++++++++++++++++++++++++++++----- src/config.rs | 32 +++++++++++----------- 3 files changed, 72 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index 5ce6f87ef..0a3c97f28 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ target/ *.swp* # Local testing -local/ \ No newline at end of file +local/ +settings.db diff --git a/src/bin/config-store.rs b/src/bin/config-store.rs index 69e1d0dd2..65c054a70 100644 --- a/src/bin/config-store.rs +++ b/src/bin/config-store.rs @@ -1,9 +1,9 @@ use clap::{Parser, Subcommand}; use derive_more::Display; -use gosub_engine::config::settings::Setting; +use gosub_engine::config::settings::{Setting, SettingInfo}; use gosub_engine::config::storage::json_storage::JsonStorageAdapter; use gosub_engine::config::storage::sqlite_storage::SqliteStorageAdapter; -use gosub_engine::config::{ConfigStore, StorageAdapter}; +use gosub_engine::config::ConfigStore; #[derive(Debug, Parser)] #[clap(name = "Config-Store", version = "0.1.0", author = "Gosub")] @@ -69,16 +69,62 @@ struct GlobalOpts { path: String, } +enum Config { + Sqlite(ConfigStore), + Json(ConfigStore), +} + +impl Config { + fn find(&self, search: &str) -> Vec { + match self { + Self::Sqlite(store) => store.find(search), + Self::Json(store) => store.find(search), + } + } + + fn has(&self, key: &str) -> bool { + match self { + Self::Sqlite(store) => store.has(key), + Self::Json(store) => store.has(key), + } + } + + fn get(&mut self, key: &str) -> Setting { + match self { + Self::Sqlite(store) => store.get(key), + Self::Json(store) => store.get(key), + } + } + + fn get_info(&self, key: &str) -> Option { + match self { + Self::Sqlite(store) => store.get_info(key), + Self::Json(store) => store.get_info(key), + } + } + + fn set(&mut self, key: &str, value: Setting) { + match self { + Self::Sqlite(store) => store.set(key, value), + Self::Json(store) => store.set(key, value), + } + } +} + fn main() { let args = Cli::parse(); - let storage_box: Box = match args.global_opts.engine { - Engine::Sqlite => Box::new(SqliteStorageAdapter::new(args.global_opts.path.as_str())), - Engine::Json => Box::new(JsonStorageAdapter::new(args.global_opts.path.as_str())), + let mut store: Config = match args.global_opts.engine { + Engine::Sqlite => { + let store = SqliteStorageAdapter::new(args.global_opts.path.as_str()); + Config::Sqlite(ConfigStore::new(store, true)) + } + Engine::Json => { + let store = JsonStorageAdapter::new(args.global_opts.path.as_str()); + Config::Json(ConfigStore::new(store, true)) + } }; - let mut store = ConfigStore::new(storage_box, true); - match args.command { Commands::View { key } => { if !store.has(key.as_str()) { diff --git a/src/config.rs b/src/config.rs index d63bd7a8b..7619af7ef 100644 --- a/src/config.rs +++ b/src/config.rs @@ -33,7 +33,7 @@ pub trait StorageAdapter { } /// Configuration store is the place where the gosub engine can find all configurable options -pub struct ConfigStore { +pub struct ConfigStore { /// A hashmap of all settings so we can search o(1) time settings: HashMap, /// A hashmap of all setting descriptions, default values and type information @@ -41,14 +41,24 @@ pub struct ConfigStore { /// Keys of all settings so we can iterate keys easily setting_keys: Vec, /// The storage adapter used for persisting and loading keys - storage: Box, + storage: S, } -impl ConfigStore { +impl ConfigStore { /// Creates a new store with the given storage adapter and preloads the store if needed - pub fn new(storage: Box, preload: bool) -> Self { + pub fn new(storage: S, preload: bool) -> Self { + // preload the settings if requested + let mut settings = HashMap::new(); + + if preload { + let all_settings = storage.get_all_settings(); + for (key, value) in all_settings { + settings.insert(key, value); + } + } + let mut store = ConfigStore { - settings: HashMap::new(), + settings, settings_info: HashMap::new(), setting_keys: Vec::new(), storage, @@ -57,14 +67,6 @@ impl ConfigStore { // Populate the settings from the json file store.populate_settings(); - // preload the settings if requested - if preload { - let all_settings = store.storage.get_all_settings(); - for (key, value) in all_settings { - store.settings.insert(key, value); - } - } - store } @@ -174,7 +176,7 @@ mod test { #[test] fn config_store() { - let mut store = ConfigStore::new(Box::new(MemoryStorageAdapter::new()), true); + let mut store = ConfigStore::new(MemoryStorageAdapter::new(), true); let setting = store.get("dns.local_resolver.enabled"); assert_eq!(setting, Setting::Bool(false)); @@ -186,7 +188,7 @@ mod test { #[test] #[should_panic] fn invalid_setting() { - let mut store = ConfigStore::new(Box::new(MemoryStorageAdapter::new()), true); + let mut store = ConfigStore::new(MemoryStorageAdapter::new(), true); store.set( "dns.local_resolver.enabled", Setting::String("wont accept strings".into()), From 371256beb50288d76219ceaca71483022a9f4651 Mon Sep 17 00:00:00 2001 From: Joshua Thijssen Date: Sat, 11 Nov 2023 18:44:16 +0000 Subject: [PATCH 07/10] Multiple suggestions implemented Co-authored-by: Eric Walker --- src/bin/config-store.rs | 7 +++---- src/config.rs | 11 ++++------- src/config/settings.rs | 10 +++++----- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/bin/config-store.rs b/src/bin/config-store.rs index 65c054a70..bda0ba367 100644 --- a/src/bin/config-store.rs +++ b/src/bin/config-store.rs @@ -127,7 +127,7 @@ fn main() { match args.command { Commands::View { key } => { - if !store.has(key.as_str()) { + if !store.has(&key) { println!("Key not found"); return; } @@ -148,13 +148,12 @@ fn main() { } Commands::Set { key, value } => { store.set( - key.as_str(), - Setting::from_string(value.as_str()).expect("incorrect value"), + &key, Setting::from_string(&value).expect("incorrect value"), ); } Commands::Search { key } => { for key in store.find(key.as_str()) { - let value = store.get(key.as_str()); + let value = store.get(key); println!("{:40}: {}", key, value); } } diff --git a/src/config.rs b/src/config.rs index 7619af7ef..51d8dc75f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -21,7 +21,7 @@ struct JsonEntry { /// StorageAdapter is the interface for storing and retrieving settings /// This can be used to store settings in a database, json file, etc -pub trait StorageAdapter { +pub trait Store { /// Retrieves a setting from the storage fn get_setting(&self, key: &str) -> Option; /// Stores a given setting to the storage @@ -82,7 +82,7 @@ impl ConfigStore { let mut keys = Vec::new(); for key in &self.setting_keys { - if search.matches(key.as_str()) { + if search.matches(key) { let key = key.clone(); keys.push(key); } @@ -137,12 +137,9 @@ impl ConfigStore { /// Populates the settings in the store from the settings.json file fn populate_settings(&mut self) { - let json_data = serde_json::from_str(SETTINGS_JSON); - if json_data.is_err() { - panic!("Failed to parse settings.json"); - } + let json_data: Value = + serde_json::from_str(SETTINGS_JSON).expect("Failed to parse settings.json"); - let json_data = json_data.unwrap(); if let Value::Object(data) = json_data { for (section_prefix, section_entries) in data.iter() { let section_entries: Vec = diff --git a/src/config/settings.rs b/src/config/settings.rs index ae8a42904..69c779e2d 100644 --- a/src/config/settings.rs +++ b/src/config/settings.rs @@ -39,10 +39,10 @@ impl<'de> Deserialize<'de> for Setting { impl Display for Setting { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Setting::SInt(value) => write!(f, "i:{}", value), - Setting::UInt(value) => write!(f, "u:{}", value), - Setting::String(value) => write!(f, "s:{}", value), - Setting::Bool(value) => write!(f, "b:{}", value), + Setting::SInt(value) => write!(f, "i:{value}"), + Setting::UInt(value) => write!(f, "u:{value}"), + Setting::String(value) => write!(f, "s:{value}"), + Setting::Bool(value) => write!(f, "b:{value}"), Setting::Map(values) => { let mut result = String::new(); for value in values { @@ -65,7 +65,7 @@ impl Setting { // m:foo,bar,baz /// Converts a string to a setting or None when the string is invalid - pub fn from_string(key: &str) -> Option { + pub fn from_str(key: &str) -> Option { let (key_type, key_value) = key.split_once(':').unwrap(); match key_type { From 1580d32458d7072974198a3cab4be0dcd4696fa1 Mon Sep 17 00:00:00 2001 From: Joshua Thijssen Date: Mon, 13 Nov 2023 09:40:20 +0100 Subject: [PATCH 08/10] Revert "Replace Box with a struct parameter" This reverts commit b58cdd079c4daa022da3582495530389df811823. --- .gitignore | 3 +-- src/bin/config-store.rs | 60 +++++------------------------------------ src/config.rs | 32 +++++++++++----------- 3 files changed, 23 insertions(+), 72 deletions(-) diff --git a/.gitignore b/.gitignore index 0a3c97f28..5ce6f87ef 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,4 @@ target/ *.swp* # Local testing -local/ -settings.db +local/ \ No newline at end of file diff --git a/src/bin/config-store.rs b/src/bin/config-store.rs index bda0ba367..34f8c477c 100644 --- a/src/bin/config-store.rs +++ b/src/bin/config-store.rs @@ -1,9 +1,9 @@ use clap::{Parser, Subcommand}; use derive_more::Display; -use gosub_engine::config::settings::{Setting, SettingInfo}; +use gosub_engine::config::settings::Setting; use gosub_engine::config::storage::json_storage::JsonStorageAdapter; use gosub_engine::config::storage::sqlite_storage::SqliteStorageAdapter; -use gosub_engine::config::ConfigStore; +use gosub_engine::config::{ConfigStore, StorageAdapter}; #[derive(Debug, Parser)] #[clap(name = "Config-Store", version = "0.1.0", author = "Gosub")] @@ -69,62 +69,16 @@ struct GlobalOpts { path: String, } -enum Config { - Sqlite(ConfigStore), - Json(ConfigStore), -} - -impl Config { - fn find(&self, search: &str) -> Vec { - match self { - Self::Sqlite(store) => store.find(search), - Self::Json(store) => store.find(search), - } - } - - fn has(&self, key: &str) -> bool { - match self { - Self::Sqlite(store) => store.has(key), - Self::Json(store) => store.has(key), - } - } - - fn get(&mut self, key: &str) -> Setting { - match self { - Self::Sqlite(store) => store.get(key), - Self::Json(store) => store.get(key), - } - } - - fn get_info(&self, key: &str) -> Option { - match self { - Self::Sqlite(store) => store.get_info(key), - Self::Json(store) => store.get_info(key), - } - } - - fn set(&mut self, key: &str, value: Setting) { - match self { - Self::Sqlite(store) => store.set(key, value), - Self::Json(store) => store.set(key, value), - } - } -} - fn main() { let args = Cli::parse(); - let mut store: Config = match args.global_opts.engine { - Engine::Sqlite => { - let store = SqliteStorageAdapter::new(args.global_opts.path.as_str()); - Config::Sqlite(ConfigStore::new(store, true)) - } - Engine::Json => { - let store = JsonStorageAdapter::new(args.global_opts.path.as_str()); - Config::Json(ConfigStore::new(store, true)) - } + let storage_box: Box = match args.global_opts.engine { + Engine::Sqlite => Box::new(SqliteStorageAdapter::new(args.global_opts.path.as_str())), + Engine::Json => Box::new(JsonStorageAdapter::new(args.global_opts.path.as_str())), }; + let mut store = ConfigStore::new(storage_box, true); + match args.command { Commands::View { key } => { if !store.has(&key) { diff --git a/src/config.rs b/src/config.rs index 51d8dc75f..4f31c84e6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -33,7 +33,7 @@ pub trait Store { } /// Configuration store is the place where the gosub engine can find all configurable options -pub struct ConfigStore { +pub struct ConfigStore { /// A hashmap of all settings so we can search o(1) time settings: HashMap, /// A hashmap of all setting descriptions, default values and type information @@ -41,24 +41,14 @@ pub struct ConfigStore { /// Keys of all settings so we can iterate keys easily setting_keys: Vec, /// The storage adapter used for persisting and loading keys - storage: S, + storage: Box, } -impl ConfigStore { +impl ConfigStore { /// Creates a new store with the given storage adapter and preloads the store if needed - pub fn new(storage: S, preload: bool) -> Self { - // preload the settings if requested - let mut settings = HashMap::new(); - - if preload { - let all_settings = storage.get_all_settings(); - for (key, value) in all_settings { - settings.insert(key, value); - } - } - + pub fn new(storage: Box, preload: bool) -> Self { let mut store = ConfigStore { - settings, + settings: HashMap::new(), settings_info: HashMap::new(), setting_keys: Vec::new(), storage, @@ -67,6 +57,14 @@ impl ConfigStore { // Populate the settings from the json file store.populate_settings(); + // preload the settings if requested + if preload { + let all_settings = store.storage.get_all_settings(); + for (key, value) in all_settings { + store.settings.insert(key, value); + } + } + store } @@ -173,7 +171,7 @@ mod test { #[test] fn config_store() { - let mut store = ConfigStore::new(MemoryStorageAdapter::new(), true); + let mut store = ConfigStore::new(Box::new(MemoryStorageAdapter::new()), true); let setting = store.get("dns.local_resolver.enabled"); assert_eq!(setting, Setting::Bool(false)); @@ -185,7 +183,7 @@ mod test { #[test] #[should_panic] fn invalid_setting() { - let mut store = ConfigStore::new(MemoryStorageAdapter::new(), true); + let mut store = ConfigStore::new(Box::new(MemoryStorageAdapter::new()), true); store.set( "dns.local_resolver.enabled", Setting::String("wont accept strings".into()), From 385a6a1257c7cb2f8285c1b28b0e5ec429c1f27b Mon Sep 17 00:00:00 2001 From: Joshua Thijssen Date: Mon, 13 Nov 2023 09:45:38 +0100 Subject: [PATCH 09/10] fixed refactoring --- src/bin/config-store.rs | 4 +--- src/config.rs | 6 +++--- src/config/settings.rs | 20 ++++++++++---------- src/config/storage/json_storage.rs | 4 ++-- src/config/storage/memory_storage.rs | 4 ++-- src/config/storage/sqlite_storage.rs | 8 ++++---- 6 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/bin/config-store.rs b/src/bin/config-store.rs index 34f8c477c..b03e4c689 100644 --- a/src/bin/config-store.rs +++ b/src/bin/config-store.rs @@ -101,9 +101,7 @@ fn main() { } } Commands::Set { key, value } => { - store.set( - &key, Setting::from_string(&value).expect("incorrect value"), - ); + store.set(&key, Setting::from_str(&value).expect("incorrect value")); } Commands::Search { key } => { for key in store.find(key.as_str()) { diff --git a/src/config.rs b/src/config.rs index 4f31c84e6..1b5d1d1a4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -41,12 +41,12 @@ pub struct ConfigStore { /// Keys of all settings so we can iterate keys easily setting_keys: Vec, /// The storage adapter used for persisting and loading keys - storage: Box, + storage: Box, } impl ConfigStore { /// Creates a new store with the given storage adapter and preloads the store if needed - pub fn new(storage: Box, preload: bool) -> Self { + pub fn new(storage: Box, preload: bool) -> Self { let mut store = ConfigStore { settings: HashMap::new(), settings_info: HashMap::new(), @@ -150,7 +150,7 @@ impl ConfigStore { let info = SettingInfo { key: key.clone(), description: entry.description, - default: Setting::from_string(entry.default.as_str()) + default: Setting::from_str(entry.default.as_str()) .expect("cannot parse default setting"), last_accessed: 0, }; diff --git a/src/config/settings.rs b/src/config/settings.rs index 69c779e2d..e58cdaa9d 100644 --- a/src/config/settings.rs +++ b/src/config/settings.rs @@ -29,7 +29,7 @@ impl<'de> Deserialize<'de> for Setting { { let value = String::deserialize(deserializer)?; - match Setting::from_string(value.as_str()) { + match Setting::from_str(value.as_str()) { None => Err(serde::de::Error::custom("Cannot deserialize")), Some(setting) => Ok(setting), } @@ -113,34 +113,34 @@ mod test { #[test] fn setting() { - let s = Setting::from_string("b:true"); + let s = Setting::from_str("b:true"); assert_eq!(s, Some(Setting::Bool(true))); - let s = Setting::from_string("i:-1"); + let s = Setting::from_str("i:-1"); assert_eq!(s, Some(Setting::SInt(-1))); - let s = Setting::from_string("i:1"); + let s = Setting::from_str("i:1"); assert_eq!(s, Some(Setting::SInt(1))); - let s = Setting::from_string("s:hello world"); + let s = Setting::from_str("s:hello world"); assert_eq!(s, Some(Setting::String("hello world".into()))); - let s = Setting::from_string("m:foo,bar,baz"); + let s = Setting::from_str("m:foo,bar,baz"); assert_eq!( s, Some(Setting::Map(vec!["foo".into(), "bar".into(), "baz".into()])) ); - let s = Setting::from_string("notexist:true"); + let s = Setting::from_str("notexist:true"); assert_eq!(s, None); - let s = Setting::from_string("b:foobar"); + let s = Setting::from_str("b:foobar"); assert_eq!(s, None); - let s = Setting::from_string("i:foobar"); + let s = Setting::from_str("i:foobar"); assert_eq!(s, None); - let s = Setting::from_string("u:-1"); + let s = Setting::from_str("u:-1"); assert_eq!(s, None); } } diff --git a/src/config/storage/json_storage.rs b/src/config/storage/json_storage.rs index 211ac1b34..85c7f3b36 100644 --- a/src/config/storage/json_storage.rs +++ b/src/config/storage/json_storage.rs @@ -1,5 +1,5 @@ use crate::config::settings::Setting; -use crate::config::StorageAdapter; +use crate::config::Store; use serde_json::Value; use std::collections::HashMap; use std::fs; @@ -50,7 +50,7 @@ impl JsonStorageAdapter { } } -impl StorageAdapter for JsonStorageAdapter { +impl Store for JsonStorageAdapter { fn get_setting(&self, key: &str) -> Option { self.elements.get(key).cloned() } diff --git a/src/config/storage/memory_storage.rs b/src/config/storage/memory_storage.rs index b70421cf8..a7988f7f4 100644 --- a/src/config/storage/memory_storage.rs +++ b/src/config/storage/memory_storage.rs @@ -1,5 +1,5 @@ use crate::config::settings::Setting; -use crate::config::StorageAdapter; +use crate::config::Store; use std::collections::HashMap; #[derive(Default)] @@ -15,7 +15,7 @@ impl MemoryStorageAdapter { } } -impl StorageAdapter for MemoryStorageAdapter { +impl Store for MemoryStorageAdapter { fn get_setting(&self, key: &str) -> Option { let v = self.store.get(key); v.cloned() diff --git a/src/config/storage/sqlite_storage.rs b/src/config/storage/sqlite_storage.rs index 20fb7cadd..2dc3e94e7 100644 --- a/src/config/storage/sqlite_storage.rs +++ b/src/config/storage/sqlite_storage.rs @@ -1,5 +1,5 @@ use crate::config::settings::Setting; -use crate::config::StorageAdapter; +use crate::config::Store; use std::collections::HashMap; pub struct SqliteStorageAdapter { @@ -21,13 +21,13 @@ impl SqliteStorageAdapter { } } -impl StorageAdapter for SqliteStorageAdapter { +impl Store for SqliteStorageAdapter { fn get_setting(&self, key: &str) -> Option { let query = "SELECT * FROM settings WHERE key = :key"; let mut statement = self.connection.prepare(query).unwrap(); statement.bind((":key", key)).unwrap(); - Setting::from_string(key) + Setting::from_str(key) } fn set_setting(&mut self, key: &str, value: Setting) { @@ -49,7 +49,7 @@ impl StorageAdapter for SqliteStorageAdapter { while let sqlite::State::Row = statement.next().unwrap() { let key = statement.read::(1).unwrap(); let value = statement.read::(2).unwrap(); - settings.insert(key, Setting::from_string(value.as_str()).unwrap()); + settings.insert(key, Setting::from_str(value.as_str()).unwrap()); } settings From 701149a0fcad8160e32bd65cd22467ae6d7ced68 Mon Sep 17 00:00:00 2001 From: Joshua Thijssen Date: Mon, 13 Nov 2023 09:53:57 +0100 Subject: [PATCH 10/10] Fixed fmt issues --- src/bin/config-store.rs | 8 ++++---- src/config.rs | 2 +- src/config/settings.rs | 22 +++++++++++----------- src/config/storage/sqlite_storage.rs | 4 ++-- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/bin/config-store.rs b/src/bin/config-store.rs index b03e4c689..6a6d2a1c5 100644 --- a/src/bin/config-store.rs +++ b/src/bin/config-store.rs @@ -3,7 +3,7 @@ use derive_more::Display; use gosub_engine::config::settings::Setting; use gosub_engine::config::storage::json_storage::JsonStorageAdapter; use gosub_engine::config::storage::sqlite_storage::SqliteStorageAdapter; -use gosub_engine::config::{ConfigStore, StorageAdapter}; +use gosub_engine::config::{ConfigStore, Store}; #[derive(Debug, Parser)] #[clap(name = "Config-Store", version = "0.1.0", author = "Gosub")] @@ -72,7 +72,7 @@ struct GlobalOpts { fn main() { let args = Cli::parse(); - let storage_box: Box = match args.global_opts.engine { + let storage_box: Box = match args.global_opts.engine { Engine::Sqlite => Box::new(SqliteStorageAdapter::new(args.global_opts.path.as_str())), Engine::Json => Box::new(JsonStorageAdapter::new(args.global_opts.path.as_str())), }; @@ -101,11 +101,11 @@ fn main() { } } Commands::Set { key, value } => { - store.set(&key, Setting::from_str(&value).expect("incorrect value")); + store.set(&key, Setting::from_string(&value).expect("incorrect value")); } Commands::Search { key } => { for key in store.find(key.as_str()) { - let value = store.get(key); + let value = store.get(&key); println!("{:40}: {}", key, value); } } diff --git a/src/config.rs b/src/config.rs index 1b5d1d1a4..31d8c702d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -150,7 +150,7 @@ impl ConfigStore { let info = SettingInfo { key: key.clone(), description: entry.description, - default: Setting::from_str(entry.default.as_str()) + default: Setting::from_string(entry.default.as_str()) .expect("cannot parse default setting"), last_accessed: 0, }; diff --git a/src/config/settings.rs b/src/config/settings.rs index e58cdaa9d..a04dd5a58 100644 --- a/src/config/settings.rs +++ b/src/config/settings.rs @@ -29,7 +29,7 @@ impl<'de> Deserialize<'de> for Setting { { let value = String::deserialize(deserializer)?; - match Setting::from_str(value.as_str()) { + match Setting::from_string(value.as_str()) { None => Err(serde::de::Error::custom("Cannot deserialize")), Some(setting) => Ok(setting), } @@ -65,7 +65,7 @@ impl Setting { // m:foo,bar,baz /// Converts a string to a setting or None when the string is invalid - pub fn from_str(key: &str) -> Option { + pub fn from_string(key: &str) -> Option { let (key_type, key_value) = key.split_once(':').unwrap(); match key_type { @@ -113,34 +113,34 @@ mod test { #[test] fn setting() { - let s = Setting::from_str("b:true"); + let s = Setting::from_string("b:true"); assert_eq!(s, Some(Setting::Bool(true))); - let s = Setting::from_str("i:-1"); + let s = Setting::from_string("i:-1"); assert_eq!(s, Some(Setting::SInt(-1))); - let s = Setting::from_str("i:1"); + let s = Setting::from_string("i:1"); assert_eq!(s, Some(Setting::SInt(1))); - let s = Setting::from_str("s:hello world"); + let s = Setting::from_string("s:hello world"); assert_eq!(s, Some(Setting::String("hello world".into()))); - let s = Setting::from_str("m:foo,bar,baz"); + let s = Setting::from_string("m:foo,bar,baz"); assert_eq!( s, Some(Setting::Map(vec!["foo".into(), "bar".into(), "baz".into()])) ); - let s = Setting::from_str("notexist:true"); + let s = Setting::from_string("notexist:true"); assert_eq!(s, None); - let s = Setting::from_str("b:foobar"); + let s = Setting::from_string("b:foobar"); assert_eq!(s, None); - let s = Setting::from_str("i:foobar"); + let s = Setting::from_string("i:foobar"); assert_eq!(s, None); - let s = Setting::from_str("u:-1"); + let s = Setting::from_string("u:-1"); assert_eq!(s, None); } } diff --git a/src/config/storage/sqlite_storage.rs b/src/config/storage/sqlite_storage.rs index 2dc3e94e7..9477e604b 100644 --- a/src/config/storage/sqlite_storage.rs +++ b/src/config/storage/sqlite_storage.rs @@ -27,7 +27,7 @@ impl Store for SqliteStorageAdapter { let mut statement = self.connection.prepare(query).unwrap(); statement.bind((":key", key)).unwrap(); - Setting::from_str(key) + Setting::from_string(key) } fn set_setting(&mut self, key: &str, value: Setting) { @@ -49,7 +49,7 @@ impl Store for SqliteStorageAdapter { while let sqlite::State::Row = statement.next().unwrap() { let key = statement.read::(1).unwrap(); let value = statement.read::(2).unwrap(); - settings.insert(key, Setting::from_str(value.as_str()).unwrap()); + settings.insert(key, Setting::from_string(value.as_str()).unwrap()); } settings