use gxhash::{GxBuildHasher, HashMap};
use serde::{Serialize, de::DeserializeOwned};
pub trait IRespCommandData: Sized {
fn name(&self) -> &str;
}
pub fn get_resp_commands_data_provider() -> DefaultRespCommandsDataProvider {
DefaultRespCommandsDataProvider
}
pub struct DefaultRespCommandsDataProvider;
impl DefaultRespCommandsDataProvider {
pub fn try_import_resp_commands_data<T: IRespCommandData + DeserializeOwned>(
&self,
json: &str,
) -> Option<Vec<T>> {
let entries: Vec<T> = sonic_rs::from_str(json).ok()?;
let mut seen: HashMap<String, ()> =
HashMap::with_capacity_and_hasher(entries.len(), GxBuildHasher::default());
for entry in &entries {
let name = entry.name();
if name.trim().is_empty() {
return None;
}
if seen.insert(name.to_lowercase(), ()).is_some() {
return None;
}
}
Some(entries)
}
pub fn try_export_resp_commands_data<T: IRespCommandData + Serialize>(
&self,
commands_data: &[T],
) -> Option<String> {
let mut sorted: Vec<&T> = commands_data.iter().collect();
sorted.sort_by_key(|entry| entry.name().to_lowercase());
sonic_rs::to_string_pretty(&sorted).ok()
}
}
#[cfg(test)]
mod tests {
use super::{IRespCommandData, get_resp_commands_data_provider};
#[derive(serde::Deserialize, serde::Serialize)]
struct ProbeOut {
#[serde(rename = "Name")]
name: String,
}
#[derive(serde::Deserialize)]
struct Probe {
#[serde(rename = "Name")]
name: String,
}
impl IRespCommandData for Probe {
fn name(&self) -> &str {
&self.name
}
}
impl IRespCommandData for ProbeOut {
fn name(&self) -> &str {
&self.name
}
}
#[test]
fn import_validates_names() {
let provider = get_resp_commands_data_provider();
let ok = provider.try_import_resp_commands_data::<Probe>(r#"[{"Name":"SET"},{"Name":"GET"}]"#);
assert_eq!(ok.unwrap().len(), 2);
assert!(
provider
.try_import_resp_commands_data::<Probe>(r#"[{"Name":""}]"#)
.is_none()
);
assert!(
provider
.try_import_resp_commands_data::<Probe>(r#"[{"Name":"SET"},{"Name":"set"}]"#)
.is_none()
);
assert!(
provider
.try_import_resp_commands_data::<Probe>("n/a")
.is_none()
);
}
#[test]
fn export_sorts_by_name() {
let provider = get_resp_commands_data_provider();
let data = vec![ProbeOut { name: "b".into() }, ProbeOut { name: "a".into() }];
let json = provider.try_export_resp_commands_data(&data).unwrap();
assert!(json.find("\"a\"").unwrap() < json.find("\"b\"").unwrap());
}
}