use std::any::type_name;
use std::collections::HashMap;
use serde::de::DeserializeOwned;
use crate::RedfishError;
pub trait JsonMap {
fn get_value(&self, key: &str) -> Option<&serde_json::Value>;
fn remove_value(&mut self, key: &str) -> Option<serde_json::Value>;
}
impl JsonMap for serde_json::Map<String, serde_json::Value> {
fn get_value(&self, key: &str) -> Option<&serde_json::Value> {
self.get(key)
}
fn remove_value(&mut self, key: &str) -> Option<serde_json::Value> {
self.remove(key)
}
}
impl JsonMap for HashMap<String, serde_json::Value> {
fn get_value(&self, key: &str) -> Option<&serde_json::Value> {
self.get(key)
}
fn remove_value(&mut self, key: &str) -> Option<serde_json::Value> {
self.remove(key)
}
}
fn missing_key_error(key: &str, url: &str) -> RedfishError {
RedfishError::MissingKey {
key: key.to_string(),
url: url.to_string(),
}
}
fn invalid_type_error(key: &str, expected_type: &str, url: &str) -> RedfishError {
RedfishError::InvalidKeyType {
key: key.to_string(),
expected_type: expected_type.to_string(),
url: url.to_string(),
}
}
pub fn get_value<'a, M: JsonMap>(
map: &'a M,
key: &str,
url: &str,
) -> Result<&'a serde_json::Value, RedfishError> {
map.get_value(key)
.ok_or_else(|| missing_key_error(key, url))
}
pub fn get_str<'a, M: JsonMap>(map: &'a M, key: &str, url: &str) -> Result<&'a str, RedfishError> {
get_value(map, key, url)?
.as_str()
.ok_or_else(|| invalid_type_error(key, "string", url))
}
pub fn get_object<'a, M: JsonMap>(
map: &'a M,
key: &str,
url: &str,
) -> Result<&'a serde_json::Map<String, serde_json::Value>, RedfishError> {
get_value(map, key, url)?
.as_object()
.ok_or_else(|| invalid_type_error(key, "object", url))
}
pub fn get_bool<M: JsonMap>(map: &M, key: &str, url: &str) -> Result<bool, RedfishError> {
get_value(map, key, url)?
.as_bool()
.ok_or_else(|| invalid_type_error(key, "boolean", url))
}
#[allow(dead_code)]
pub fn get_i64<M: JsonMap>(map: &M, key: &str, url: &str) -> Result<i64, RedfishError> {
get_value(map, key, url)?
.as_i64()
.ok_or_else(|| invalid_type_error(key, "integer", url))
}
#[allow(dead_code)]
pub fn get_f64<M: JsonMap>(map: &M, key: &str, url: &str) -> Result<f64, RedfishError> {
get_value(map, key, url)?
.as_f64()
.ok_or_else(|| invalid_type_error(key, "number", url))
}
pub fn extract<T, M: JsonMap>(map: &mut M, key: &str, url: &str) -> Result<T, RedfishError>
where
T: DeserializeOwned,
{
let json = map
.remove_value(key)
.ok_or_else(|| missing_key_error(key, url))?;
serde_json::from_value::<T>(json).map_err(|_| invalid_type_error(key, type_name::<T>(), url))
}
pub fn extract_object<M: JsonMap>(
map: &mut M,
key: &str,
url: &str,
) -> Result<serde_json::Map<String, serde_json::Value>, RedfishError> {
extract(map, key, url).map_err(|e| match e {
RedfishError::InvalidKeyType { key, url, .. } => invalid_type_error(&key, "object", &url),
e => e,
})
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_get_str_success() {
let value = json!({
"Name": "TestName",
"Id": "123"
});
let map = value.as_object().unwrap();
let result = get_str(map, "Name", "http://test/url");
assert_eq!(result.unwrap(), "TestName");
}
#[test]
fn test_get_str_with_hashmap() {
let mut map: HashMap<String, serde_json::Value> = HashMap::new();
map.insert("Name".to_string(), json!("TestName"));
let result = get_str(&map, "Name", "http://test/url");
assert_eq!(result.unwrap(), "TestName");
}
#[test]
fn test_get_str_missing_key() {
let value = json!({
"Name": "TestName"
});
let map = value.as_object().unwrap();
let result = get_str(map, "Missing", "http://test/url");
assert!(matches!(result, Err(RedfishError::MissingKey { .. })));
}
#[test]
fn test_get_str_wrong_type() {
let value = json!({
"Count": 42
});
let map = value.as_object().unwrap();
let result = get_str(map, "Count", "http://test/url");
assert!(matches!(result, Err(RedfishError::InvalidKeyType { .. })));
}
#[test]
fn test_get_object_success() {
let value = json!({
"Nested": {
"Inner": "value"
}
});
let map = value.as_object().unwrap();
let result = get_object(map, "Nested", "http://test/url");
assert!(result.is_ok());
assert_eq!(
result.unwrap().get("Inner").unwrap().as_str().unwrap(),
"value"
);
}
#[test]
fn test_get_bool_success() {
let value = json!({
"Enabled": true,
"Disabled": false
});
let map = value.as_object().unwrap();
assert_eq!(get_bool(map, "Enabled", "http://test/url").unwrap(), true);
assert_eq!(get_bool(map, "Disabled", "http://test/url").unwrap(), false);
}
#[test]
fn test_get_i64_success() {
let value = json!({
"Count": 42
});
let map = value.as_object().unwrap();
assert_eq!(get_i64(map, "Count", "http://test/url").unwrap(), 42);
}
#[test]
fn test_extract_success() {
let mut map: HashMap<String, serde_json::Value> = HashMap::new();
map.insert("Name".to_string(), json!("TestName"));
let result: Result<String, _> = extract(&mut map, "Name", "http://test/url");
assert_eq!(result.unwrap(), "TestName");
assert!(map.is_empty());
}
#[test]
fn test_extract_object_success() {
let mut map: HashMap<String, serde_json::Value> = HashMap::new();
map.insert("Nested".to_string(), json!({"Inner": "value"}));
let result = extract_object(&mut map, "Nested", "http://test/url");
assert!(result.is_ok());
assert_eq!(
result.unwrap().get("Inner").unwrap().as_str().unwrap(),
"value"
);
assert!(map.is_empty());
}
}