Skip to content

Commit

Permalink
fix: rewrote settings logic to mimic aw-server-python behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
ErikBjare committed Oct 19, 2023
1 parent f962ccd commit 2fb9a80
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 172 deletions.
37 changes: 19 additions & 18 deletions aw-datastore/src/datastore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -914,10 +914,6 @@ impl DatastoreInstance {
Ok(KeyValue {
key: row.get(0)?,
value: row.get(1)?,
timestamp: Some(DateTime::from_utc(
NaiveDateTime::from_timestamp_opt(row.get(2)?, 0).unwrap(),
Utc,
)),
})
}) {
Ok(result) => Ok(result),
Expand All @@ -932,12 +928,12 @@ impl DatastoreInstance {
}
}

pub fn get_keys_starting(
pub fn get_key_values(
&self,
conn: &Connection,
pattern: &str,
) -> Result<Vec<String>, DatastoreError> {
let mut stmt = match conn.prepare("SELECT key FROM key_value WHERE key LIKE ?") {
) -> Result<HashMap<String, String>, DatastoreError> {
let mut stmt = match conn.prepare("SELECT key, value FROM key_value WHERE key LIKE ?") {
Ok(stmt) => stmt,
Err(err) => {
return Err(DatastoreError::InternalError(format!(
Expand All @@ -946,25 +942,30 @@ impl DatastoreInstance {
}
};

let mut output = Vec::<String>::new();
let mut output = HashMap::<String, String>::new();
// Rusqlite's get wants index and item type as parameters.
let result = stmt.query_map([pattern], |row| row.get::<usize, String>(0));
let result = stmt.query_map([pattern], |row| {
Ok((row.get::<usize, String>(0)?, row.get::<usize, String>(1)?))
});
match result {
Ok(keys) => {
for row in keys {
Ok(settings) => {
for row in settings {
// Unwrap to String or panic on SQL row if type is invalid. Can't happen with a
// properly initialized table.
output.push(row.unwrap());
let (key, value) = row.unwrap();
// Only return keys starting with "settings.".
if !key.starts_with("settings.") {
continue;
}
output.insert(key, value);
}
Ok(output)
}
Err(err) => match err {
rusqlite::Error::QueryReturnedNoRows => {
Err(DatastoreError::NoSuchKey(pattern.to_string()))
}
_ => Err(DatastoreError::InternalError(format!(
"Failed to get key_value rows starting with pattern {pattern}"
))),
rusqlite::Error::QueryReturnedNoRows => Ok(output),
_ => Err(DatastoreError::InternalError(
"Failed to get settings".to_string(),
)),
},
}
}
Expand Down
56 changes: 28 additions & 28 deletions aw-datastore/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ pub enum Response {
EventList(Vec<Event>),
Count(i64),
KeyValue(KeyValue),
StringVec(Vec<String>),
KeyValues(HashMap<String, String>)
}

#[allow(clippy::large_enum_variant)]
Expand All @@ -74,9 +74,9 @@ pub enum Command {
GetEventCount(String, Option<DateTime<Utc>>, Option<DateTime<Utc>>),
DeleteEventsById(String, Vec<i64>),
ForceCommit(),
InsertKeyValue(String, String),
GetKeyValues(String),
GetKeyValue(String),
GetKeysStarting(String),
SetKeyValue(String, String),
DeleteKeyValue(String),
Close(),
}
Expand Down Expand Up @@ -275,18 +275,18 @@ impl DatastoreWorker {
self.commit = true;
Ok(Response::Empty())
}
Command::InsertKeyValue(key, data) => match ds.insert_key_value(tx, &key, &data) {
Command::GetKeyValues(pattern) => match ds.get_key_values(tx, pattern.as_str()) {
Ok(result) => Ok(Response::KeyValues(result)),
Err(e) => Err(e),
}
Command::SetKeyValue(key, data) => match ds.insert_key_value(tx, &key, &data) {
Ok(()) => Ok(Response::Empty()),
Err(e) => Err(e),
},
Command::GetKeyValue(key) => match ds.get_key_value(tx, &key) {
Ok(result) => Ok(Response::KeyValue(result)),
Err(e) => Err(e),
},
Command::GetKeysStarting(pattern) => match ds.get_keys_starting(tx, &pattern) {
Ok(result) => Ok(Response::StringVec(result)),
Err(e) => Err(e),
},
Command::DeleteKeyValue(key) => match ds.delete_key_value(tx, &key) {
Ok(()) => Ok(Response::Empty()),
Err(e) => Err(e),
Expand Down Expand Up @@ -475,46 +475,46 @@ impl Datastore {
}
}

pub fn insert_key_value(&self, key: &str, data: &str) -> Result<(), DatastoreError> {
let cmd = Command::InsertKeyValue(key.to_string(), data.to_string());
let receiver = self.requester.request(cmd).unwrap();

_unwrap_response(receiver)
}

pub fn delete_key_value(&self, key: &str) -> Result<(), DatastoreError> {
let cmd = Command::DeleteKeyValue(key.to_string());
let receiver = self.requester.request(cmd).unwrap();

_unwrap_response(receiver)
}

pub fn get_key_value(&self, key: &str) -> Result<KeyValue, DatastoreError> {
let cmd = Command::GetKeyValue(key.to_string());
pub fn get_key_values(&self, pattern: &str) -> Result<HashMap<String, String>, DatastoreError> {
let cmd = Command::GetKeyValues(pattern.to_string());
let receiver = self.requester.request(cmd).unwrap();

match receiver.collect().unwrap() {
Ok(r) => match r {
Response::KeyValue(value) => Ok(value),
Response::KeyValues(value) => Ok(value),
_ => panic!("Invalid response"),
},
Err(e) => Err(e),
}
}

pub fn get_keys_starting(&self, pattern: &str) -> Result<Vec<String>, DatastoreError> {
let cmd = Command::GetKeysStarting(pattern.to_string());
pub fn get_key_value(&self, key: &str) -> Result<KeyValue, DatastoreError> {
let cmd = Command::GetKeyValue(key.to_string());
let receiver = self.requester.request(cmd).unwrap();

match receiver.collect().unwrap() {
Ok(r) => match r {
Response::StringVec(value) => Ok(value),
Response::KeyValue(kv) => Ok(kv),
_ => panic!("Invalid response"),
},
Err(e) => Err(e),
}
}

pub fn set_key_value(&self, key: &str, data: &str) -> Result<(), DatastoreError> {
let cmd = Command::SetKeyValue(key.to_string(), data.to_string());
let receiver = self.requester.request(cmd).unwrap();

_unwrap_response(receiver)
}

pub fn delete_key_value(&self, key: &str) -> Result<(), DatastoreError> {
let cmd = Command::DeleteKeyValue(key.to_string());
let receiver = self.requester.request(cmd).unwrap();

_unwrap_response(receiver)
}

// Should block until worker has stopped
pub fn close(&self) {
info!("Sending close request to database");
Expand Down
2 changes: 0 additions & 2 deletions aw-models/src/key_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ pub struct Key {
pub struct KeyValue {
pub key: String,
pub value: Value,
pub timestamp: Option<DateTime<Utc>>,
}

impl KeyValue {
Expand All @@ -24,7 +23,6 @@ impl KeyValue {
KeyValue {
key: key.into(),
value: value.into(),
timestamp: Some(timestamp),
}
}
}
4 changes: 2 additions & 2 deletions aw-server/src/endpoints/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,9 @@ pub fn build_rocket(server_state: ServerState, config: AWConfig) -> rocket::Rock
"/api/0/settings",
routes![
settings::setting_get,
settings::settings_list_get,
settings::setting_set,
settings::setting_delete
settings::setting_delete,
settings::settings_get,
],
)
.mount("/", rocket_cors::catch_all_options_routes());
Expand Down
73 changes: 44 additions & 29 deletions aw-server/src/endpoints/settings.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use crate::endpoints::ServerState;
use aw_query::query;
use rocket::http::Status;
use rocket::serde::json::Json;
use rocket::State;
use std::collections::HashMap;
use std::sync::MutexGuard;

use aw_datastore::Datastore;
use aw_models::{Key, KeyValue};

use crate::endpoints::HttpErrorJson;

Expand All @@ -21,51 +22,65 @@ fn parse_key(key: String) -> Result<String, HttpErrorJson> {
}
}

#[post("/", data = "<message>", format = "application/json")]
pub fn setting_set(
state: &State<ServerState>,
message: Json<KeyValue>,
) -> Result<Status, HttpErrorJson> {
let data = message.into_inner();

let setting_key = parse_key(data.key)?;

let datastore: MutexGuard<'_, Datastore> = endpoints_get_lock!(state.datastore);
let result = datastore.insert_key_value(&setting_key, &data.value.to_string());

match result {
Ok(_) => Ok(Status::Created),
Err(err) => Err(err.into()),
}
}

#[get("/")]
pub fn settings_list_get(state: &State<ServerState>) -> Result<Json<Vec<Key>>, HttpErrorJson> {
pub fn settings_get(
state: &State<ServerState>,
) -> Result<Json<HashMap<String, serde_json::Value>>, HttpErrorJson> {
let datastore = endpoints_get_lock!(state.datastore);
let queryresults = match datastore.get_keys_starting("settings.%") {
let queryresults = match datastore.get_key_values("settings.%") {
Ok(result) => Ok(result),
Err(err) => Err(err.into()),
};

let mut output = Vec::<Key>::new();
for i in queryresults? {
output.push(Key { key: i });
match queryresults {
Ok(settings) => {
// strip 'settings.' prefix from keys
let mut map: HashMap<String, serde_json::Value> = HashMap::new();
for (key, value) in settings.iter() {
map.insert(key.strip_prefix("settings.").unwrap_or(key).to_string(), serde_json::from_str(value.clone().as_str()).unwrap());
}
Ok(Json(map))
},
Err(err) => Err(err),
}

Ok(Json(output))
}

#[get("/<key>")]
pub fn setting_get(
state: &State<ServerState>,
key: String,
) -> Result<Json<KeyValue>, HttpErrorJson> {
) -> Result<Json<serde_json::Value>, HttpErrorJson> {
let setting_key = parse_key(key)?;

let datastore = endpoints_get_lock!(state.datastore);

match datastore.get_key_value(&setting_key) {
Ok(result) => Ok(Json(result)),
Ok(kv) => Ok(Json(kv.value)),
Err(err) => Err(err.into()),
}
}

#[post("/<key>", data = "<value>", format = "application/json")]
pub fn setting_set(
state: &State<ServerState>,
key: String,
value: Json<serde_json::Value>,
) -> Result<Status, HttpErrorJson> {
let setting_key = parse_key(key)?;
let value_str = match serde_json::to_string(&value.0) {
Ok(value) => value,
Err(err) => {
return Err(HttpErrorJson::new(
Status::BadRequest,
format!("Invalid JSON: {}", err),
))
}
};

let datastore: MutexGuard<'_, Datastore> = endpoints_get_lock!(state.datastore);
let result = datastore.set_key_value(&setting_key, &value_str);

match result {
Ok(_) => Ok(Status::Created),
Err(err) => Err(err.into()),
}
}
Expand Down
Loading

0 comments on commit 2fb9a80

Please sign in to comment.