Skip to content

Commit

Permalink
Add basic error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
emwalker committed Nov 14, 2023
1 parent 3811255 commit faaff8f
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 69 deletions.
23 changes: 13 additions & 10 deletions src/bin/config-store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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, Store};
use std::str::FromStr;

#[derive(Debug, Parser)]
#[clap(name = "Config-Store", version = "0.1.0", author = "Gosub")]
Expand Down Expand Up @@ -69,25 +70,25 @@ struct GlobalOpts {
path: String,
}

fn main() {
fn main() -> anyhow::Result<()> {
let args = Cli::parse();

let storage_box: Box<dyn Store> = 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())),
Engine::Sqlite => Box::new(SqliteStorageAdapter::new(&args.global_opts.path)),
Engine::Json => Box::new(JsonStorageAdapter::new(&args.global_opts.path)),
};

let mut store = ConfigStore::new(storage_box, true);
let mut store = ConfigStore::from(storage_box, true)?;

match args.command {
Commands::View { key } => {
if !store.has(&key) {
println!("Key not found");
return;
return Ok(());
}

let info = store.get_info(key.as_str()).unwrap();
let value = store.get(key.as_str());
let info = store.get_info(&key).unwrap();
let value = store.get(&key);

println!("Key : {}", key);
println!("Current Value : {}", value);
Expand All @@ -96,18 +97,20 @@ fn main() {
}
Commands::List => {
for key in store.find("*") {
let value = store.get(key.as_str());
let value = store.get(&key);
println!("{:40}: {}", key, value);
}
}
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()) {
for key in store.find(&key) {
let value = store.get(&key);
println!("{:40}: {}", key, value);
}
}
}

Ok(())
}
16 changes: 9 additions & 7 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ pub mod settings;
pub mod storage;

use crate::config::settings::{Setting, SettingInfo};
use crate::types::Result;
use serde_derive::Deserialize;
use serde_json::Value;
use std::collections::HashMap;
use std::mem;
use std::str::FromStr;
use wildmatch::WildMatch;

const SETTINGS_JSON: &str = include_str!("./config/settings.json");
Expand All @@ -29,7 +31,7 @@ pub trait Store {
/// 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<String, Setting>;
fn get_all_settings(&self) -> Result<HashMap<String, Setting>>;
}

/// Configuration store is the place where the gosub engine can find all configurable options
Expand All @@ -46,7 +48,7 @@ pub struct ConfigStore {

impl ConfigStore {
/// Creates a new store with the given storage adapter and preloads the store if needed
pub fn new(storage: Box<dyn Store>, preload: bool) -> Self {
pub fn from(storage: Box<dyn Store>, preload: bool) -> Result<Self> {
let mut store = ConfigStore {
settings: HashMap::new(),
settings_info: HashMap::new(),
Expand All @@ -59,13 +61,13 @@ impl ConfigStore {

// preload the settings if requested
if preload {
let all_settings = store.storage.get_all_settings();
let all_settings = store.storage.get_all_settings()?;
for (key, value) in all_settings {
store.settings.insert(key, value);
}
}

store
Ok(store)
}

/// Returns true when the store knows about the given key
Expand Down Expand Up @@ -150,7 +152,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)
.expect("cannot parse default setting"),
last_accessed: 0,
};
Expand All @@ -171,7 +173,7 @@ mod test {

#[test]
fn config_store() {
let mut store = ConfigStore::new(Box::new(MemoryStorageAdapter::new()), true);
let mut store = ConfigStore::from(Box::new(MemoryStorageAdapter::new()), true).unwrap();
let setting = store.get("dns.local_resolver.enabled");
assert_eq!(setting, Setting::Bool(false));

Expand All @@ -183,7 +185,7 @@ mod test {
#[test]
#[should_panic]
fn invalid_setting() {
let mut store = ConfigStore::new(Box::new(MemoryStorageAdapter::new()), true);
let mut store = ConfigStore::from(Box::new(MemoryStorageAdapter::new()), true).unwrap();
store.set(
"dns.local_resolver.enabled",
Setting::String("wont accept strings".into()),
Expand Down
95 changes: 51 additions & 44 deletions src/config/settings.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::types::{Error, Result};
use core::fmt::Display;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::str::FromStr;

/// A setting can be either a signed integer, unsigned integer, string, map or boolean.
/// Maps could be created by using comma separated strings maybe
Expand All @@ -13,7 +15,7 @@ pub enum Setting {
}

impl Serialize for Setting {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
Expand All @@ -23,16 +25,13 @@ impl Serialize for Setting {
}

impl<'de> Deserialize<'de> for Setting {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
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),
}
Setting::from_str(&value)
.map_err(|err| serde::de::Error::custom(format!("cannot deserialize: {err}")))
}
}

Expand All @@ -56,7 +55,9 @@ impl Display for Setting {
}
}

impl Setting {
impl FromStr for Setting {
type Err = Error;

// first element is the type:
// b:true
// i:-123
Expand All @@ -65,32 +66,38 @@ 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<Setting> {
fn from_str(key: &str) -> Result<Setting> {
let (key_type, key_value) = key.split_once(':').unwrap();

match key_type {
"b" => match key_value.parse::<bool>() {
Ok(value) => Some(Setting::Bool(value)),
Err(_) => None,
},
"i" => match key_value.parse::<isize>() {
Ok(value) => Some(Setting::SInt(value)),
Err(_) => None,
},
"u" => match key_value.parse::<usize>() {
Ok(value) => Some(Setting::UInt(value)),
Err(_) => None,
},
"s" => Some(Setting::String(key_value.to_string())),
let setting = match key_type {
"b" => Setting::Bool(
key_value
.parse::<bool>()
.map_err(|err| Error::Config(format!("error parsing {key_value}: {err}")))?,
),
"i" => Setting::SInt(
key_value
.parse::<isize>()
.map_err(|err| Error::Config(format!("error parsing {key_value}: {err}")))?,
),
"u" => Setting::UInt(
key_value
.parse::<usize>()
.map_err(|err| Error::Config(format!("error parsing {key_value}: {err}")))?,
),
"s" => Setting::String(key_value.to_string()),

"m" => {
let mut values = Vec::new();
for value in key_value.split(',') {
values.push(value.to_string());
}
Some(Setting::Map(values))
Setting::Map(values)
}
_ => None,
}
_ => return Err(Error::Config(format!("unknown setting: {key_value}"))),
};

Ok(setting)
}
}

Expand All @@ -113,34 +120,34 @@ mod test {

#[test]
fn setting() {
let s = Setting::from_string("b:true");
assert_eq!(s, Some(Setting::Bool(true)));
let s = Setting::from_str("b:true").unwrap();
assert_eq!(s, Setting::Bool(true));

let s = Setting::from_string("i:-1");
assert_eq!(s, Some(Setting::SInt(-1)));
let s = Setting::from_str("i:-1").unwrap();
assert_eq!(s, Setting::SInt(-1));

let s = Setting::from_string("i:1");
assert_eq!(s, Some(Setting::SInt(1)));
let s = Setting::from_str("i:1").unwrap();
assert_eq!(s, Setting::SInt(1));

let s = Setting::from_string("s:hello world");
assert_eq!(s, Some(Setting::String("hello world".into())));
let s = Setting::from_str("s:hello world").unwrap();
assert_eq!(s, Setting::String("hello world".into()));

let s = Setting::from_string("m:foo,bar,baz");
let s = Setting::from_str("m:foo,bar,baz").unwrap();
assert_eq!(
s,
Some(Setting::Map(vec!["foo".into(), "bar".into(), "baz".into()]))
Setting::Map(vec!["foo".into(), "bar".into(), "baz".into()])
);

let s = Setting::from_string("notexist:true");
assert_eq!(s, None);
let s = Setting::from_str("notexist:true");
assert!(matches!(s, Err(Error::Config(_))));

let s = Setting::from_string("b:foobar");
assert_eq!(s, None);
let s = Setting::from_str("b:foobar");
assert!(matches!(s, Err(Error::Config(_))));

let s = Setting::from_string("i:foobar");
assert_eq!(s, None);
let s = Setting::from_str("i:foobar");
assert!(matches!(s, Err(Error::Config(_))));

let s = Setting::from_string("u:-1");
assert_eq!(s, None);
let s = Setting::from_str("u:-1");
assert!(matches!(s, Err(Error::Config(_))));
}
}
5 changes: 3 additions & 2 deletions src/config/storage/json_storage.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::config::settings::Setting;
use crate::config::Store;
use crate::types::Result;
use serde_json::Value;
use std::collections::HashMap;
use std::fs;
Expand Down Expand Up @@ -61,8 +62,8 @@ impl Store for JsonStorageAdapter {
self.write_file()
}

fn get_all_settings(&self) -> HashMap<String, Setting> {
self.elements.clone()
fn get_all_settings(&self) -> Result<HashMap<String, Setting>> {
Ok(self.elements.clone())
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/config/storage/memory_storage.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::config::settings::Setting;
use crate::config::Store;
use crate::types::Result;
use std::collections::HashMap;

#[derive(Default)]
Expand All @@ -25,7 +26,7 @@ impl Store for MemoryStorageAdapter {
self.store.insert(key.to_string(), value);
}

fn get_all_settings(&self) -> HashMap<String, Setting> {
self.store.clone()
fn get_all_settings(&self) -> Result<HashMap<String, Setting>> {
Ok(self.store.clone())
}
}
10 changes: 6 additions & 4 deletions src/config/storage/sqlite_storage.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::config::settings::Setting;
use crate::config::Store;
use crate::types::Result;
use std::collections::HashMap;
use std::str::FromStr;

pub struct SqliteStorageAdapter {
connection: sqlite::Connection,
Expand All @@ -27,7 +29,7 @@ impl Store for SqliteStorageAdapter {
let mut statement = self.connection.prepare(query).unwrap();
statement.bind((":key", key)).unwrap();

Setting::from_string(key)
Setting::from_str(key).ok()
}

fn set_setting(&mut self, key: &str, value: Setting) {
Expand All @@ -41,17 +43,17 @@ impl Store for SqliteStorageAdapter {
statement.next().unwrap();
}

fn get_all_settings(&self) -> HashMap<String, Setting> {
fn get_all_settings(&self) -> Result<HashMap<String, Setting>> {
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::<String, _>(1).unwrap();
let value = statement.read::<String, _>(2).unwrap();
settings.insert(key, Setting::from_string(value.as_str()).unwrap());
settings.insert(key, Setting::from_str(&value)?);
}

settings
Ok(settings)
}
}
3 changes: 3 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ pub struct ParseError {
/// Serious errors and errors from third-party libraries
#[derive(Error, Debug)]
pub enum Error {
#[error("config error: {0}")]
Config(String),

#[error("ureq error")]
Request(#[from] Box<ureq::Error>),

Expand Down

0 comments on commit faaff8f

Please sign in to comment.