Files
komodo/lib/helpers/src/lib.rs
2023-03-05 09:24:59 +00:00

155 lines
4.9 KiB
Rust

use std::{borrow::Borrow, fs::File, io::Read, net::SocketAddr, str::FromStr};
use anyhow::{anyhow, Context};
use axum::http::StatusCode;
use rand::{distributions::Alphanumeric, Rng};
use serde::de::DeserializeOwned;
use serde_json::{Map, Value};
use types::Log;
pub fn parse_config_files<'a, T: DeserializeOwned>(
paths: impl IntoIterator<Item = impl Borrow<String>>,
merge_nested: bool,
extend_array: bool,
) -> anyhow::Result<T> {
let mut target = Map::new();
for path in paths {
target = merge_objects(
target,
parse_config_file(path.borrow())?,
merge_nested,
extend_array,
)?;
}
serde_json::from_str(&serde_json::to_string(&target)?)
.context("failed to parse final config into expected type")
}
pub fn parse_config_file<T: DeserializeOwned>(path: &str) -> anyhow::Result<T> {
let mut file = File::open(&path).expect(&format!("failed to find config at {path}"));
let config = if path.ends_with("toml") {
let mut contents = String::new();
file.read_to_string(&mut contents)
.context(format!("failed to read toml at {path}"))?;
toml::from_str(&contents).context(format!("failed to parse toml at {path}"))?
} else if path.ends_with("json") {
serde_json::from_reader(file).context(format!("failed to parse json at {path}"))?
} else {
panic!("unsupported config file type: {}", path)
};
Ok(config)
}
/// object is serde_json::Map<String, serde_json::Value>
/// source will overide target
/// will recurse when field is object if merge_object = true, otherwise object will be replaced
/// will extend when field is array if extend_array = true, otherwise array will be replaced
/// will return error when types on source and target fields do not match
fn merge_objects(
mut target: Map<String, Value>,
source: Map<String, Value>,
merge_nested: bool,
extend_array: bool,
) -> anyhow::Result<Map<String, Value>> {
for (key, value) in source {
let curr = target.remove(&key);
if curr.is_none() {
target.insert(key, value);
continue;
}
let curr = curr.unwrap();
match curr {
Value::Object(target_obj) => {
if !merge_nested {
target.insert(key, value);
continue;
}
match value {
Value::Object(source_obj) => {
target.insert(
key,
Value::Object(merge_objects(
target_obj,
source_obj,
merge_nested,
extend_array,
)?),
);
}
_ => {
return Err(anyhow!(
"types on field {key} do not match. got {value:?}, expected object"
))
}
}
}
Value::Array(mut target_arr) => {
if !extend_array {
target.insert(key, value);
continue;
}
match value {
Value::Array(source_arr) => {
target_arr.extend(source_arr);
target.insert(key, Value::Array(target_arr));
}
_ => {
return Err(anyhow!(
"types on field {key} do not match. got {value:?}, expected array"
))
}
}
}
_ => {
target.insert(key, value);
}
}
}
Ok(target)
}
pub fn parse_comma_seperated_list<T: FromStr>(
comma_sep_list: impl Borrow<str>,
) -> anyhow::Result<Vec<T>> {
comma_sep_list
.borrow()
.split(",")
.filter(|item| item.len() > 0)
.map(|item| {
let item = item
.parse()
.map_err(|_| anyhow!("error parsing string {item} into type T"))?;
Ok::<T, anyhow::Error>(item)
})
.collect()
}
pub fn get_socket_addr(port: u16) -> SocketAddr {
SocketAddr::from_str(&format!("0.0.0.0:{}", port)).expect("failed to parse socket addr")
}
pub fn to_monitor_name(name: &str) -> String {
name.to_lowercase().replace(" ", "_")
}
pub fn handle_anyhow_error(err: anyhow::Error) -> (StatusCode, String) {
(StatusCode::INTERNAL_SERVER_ERROR, format!("{err:#?}"))
}
pub fn generate_secret(length: usize) -> String {
rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(length)
.map(char::from)
.collect()
}
pub fn all_logs_success(logs: &Vec<Log>) -> bool {
for log in logs {
if !log.success {
return false;
}
}
true
}