use std::{collections::HashMap, fmt, str::FromStr};
use bytes::{BufMut, BytesMut};
use super::validators::{validate_param_key, validate_param_value};
use crate::err::{Error, ParamError};
#[repr(transparent)]
#[derive(Debug, Clone, Default)]
pub struct Params(HashMap<String, String>);
impl Params {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn clear(&mut self) {
self.0.clear();
}
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[must_use]
pub const fn inner(&self) -> &HashMap<String, String> {
&self.0
}
pub fn add(&mut self, key: String, value: String) -> Result<(), Error> {
validate_param_key(&key)?;
validate_param_value(&value)?;
self.0.insert(key, value);
Ok(())
}
#[inline]
#[allow(clippy::needless_pass_by_value)]
pub fn add_param(
&mut self,
key: impl ToString,
value: impl ToString
) -> Result<(), Error> {
let key = key.to_string();
let value = value.to_string();
validate_param_key(&key)?;
validate_param_value(&value)?;
self.0.insert(key, value);
Ok(())
}
#[inline]
#[allow(clippy::needless_pass_by_value)]
pub fn add_str(
&mut self,
key: impl ToString,
value: impl Into<String>
) -> Result<(), Error> {
let key = key.to_string();
let value = value.into();
validate_param_key(&key)?;
validate_param_value(&value)?;
self.0.insert(key, value);
Ok(())
}
#[inline]
#[allow(clippy::needless_pass_by_value)]
pub fn add_bool(
&mut self,
key: impl ToString,
value: bool
) -> Result<(), Error> {
let key = key.to_string();
let v = if value { "true" } else { "false" };
validate_param_key(&key)?;
self.add_str(key, v)
}
#[must_use]
pub fn contains(&self, key: &str) -> bool {
self.0.contains_key(key)
}
pub fn get_fromstr<T, E>(&self, key: &str) -> Result<Option<T>, Error>
where
T: FromStr<Err = E>,
E: std::fmt::Display
{
self.get_str(key).map_or_else(
|| Ok(None),
|val| {
T::from_str(val).map_or_else(
|e| Err(Error::Param(ParamError::Value(e.to_string()))),
|v| Ok(Some(v))
)
}
)
}
#[must_use]
pub fn get_str(&self, key: &str) -> Option<&str> {
let kv = self.0.get_key_value(key);
if let Some((_k, v)) = kv {
return Some(v);
}
None
}
pub fn get_bool(&self, key: &str) -> Result<Option<bool>, Error> {
let Some(v) = self.get_str(key) else {
return Ok(None);
};
let v = v.to_ascii_lowercase();
match v.as_ref() {
"y" | "yes" | "t" | "true" | "1" | "on" => Ok(Some(true)),
"n" | "no" | "f" | "false" | "0" | "off" => Ok(Some(false)),
_ => Err(Error::Param(ParamError::Value("not boolean".into())))
}
}
#[must_use]
pub fn into_inner(self) -> HashMap<String, String> {
self.0
}
#[must_use]
pub fn calc_buf_size(&self) -> usize {
let mut size = 0;
for (key, value) in &self.0 {
size += key.len() + 1; size += value.len() + 1; }
size + 1 }
#[must_use]
pub fn serialize(&self) -> Vec<u8> {
let mut buf = Vec::new();
for (key, value) in &self.0 {
let k = key.as_bytes();
let v = value.as_bytes();
for a in k {
buf.push(*a);
}
buf.push(b' ');
for a in v {
buf.push(*a);
}
buf.push(b'\n');
}
buf.push(b'\n');
buf
}
pub fn encoder_write(&self, buf: &mut BytesMut) {
let size = self.calc_buf_size();
buf.reserve(size);
for (key, value) in &self.0 {
buf.put(key.as_bytes());
buf.put_u8(b' ');
buf.put(value.as_bytes());
buf.put_u8(b'\n');
}
buf.put_u8(b'\n');
}
}
impl Params {
#[cfg(feature = "bin")]
#[cfg_attr(docsrs, doc(cfg(feature = "bin")))]
pub fn encode_buf(
&mut self,
key: impl Into<String>,
buf: &[u8]
) -> Result<(), Error> {
self.add(key.into(), base85::encode(buf))?;
Ok(())
}
#[cfg(feature = "bin")]
#[cfg_attr(docsrs, doc(cfg(feature = "bin")))]
pub fn decode_buf(&self, key: &str) -> Result<Option<Vec<u8>>, Error> {
let Some(b85) = self.get_str(key) else {
return Ok(None);
};
let buf = base85::decode(b85)
.map_err(|_| Error::Param(ParamError::Value("invalid base85".into())))?;
Ok(Some(buf))
}
#[cfg(feature = "bincode")]
#[cfg_attr(docsrs, doc(cfg(feature = "bincode")))]
pub fn encode<E>(
&mut self,
key: impl Into<String>,
data: E
) -> Result<(), Error>
where
E: bincode::Encode
{
let conf = bincode::config::standard();
let buf = bincode::encode_to_vec(data, conf)
.map_err(|e| Error::SerializeError(e.to_string()))?;
self.encode_buf(key, &buf)?;
Ok(())
}
#[cfg(feature = "bincode")]
#[cfg_attr(docsrs, doc(cfg(feature = "bincode")))]
pub fn decode<D>(&self, key: &str) -> Result<Option<D>, Error>
where
D: bincode::Decode<()>
{
let Some(buf) = self.decode_buf(key)? else {
return Ok(None);
};
let conf = bincode::config::standard();
let (data, _) = bincode::decode_from_slice(&buf, conf)
.map_err(|e| Error::Param(ParamError::Value(e.to_string())))?;
Ok(Some(data))
}
}
#[cfg(test)]
#[cfg(feature = "bin")]
mod buf_tests {
use super::Params;
#[test]
fn enc_dec() {
let buf = orphanage::buf::random(2048);
let mut params = Params::new();
params.encode_buf("Bytes", &buf).unwrap();
let buf2 = params.decode_buf("Bytes").unwrap().unwrap();
assert_eq!(buf, buf2);
}
}
impl Params {
pub fn encode_anon_list<T, I, F, E>(&mut self, it: I, f: F) -> Result<(), E>
where
I: IntoIterator<Item = T>,
F: Fn(T, &mut Vec<(String, String)>) -> Result<(), E>,
E: From<Error>
{
let mut count = 0;
let mut recs = 0;
for (idx, rec) in it.into_iter().enumerate() {
let mut rec_kv = Vec::with_capacity(recs);
f(rec, &mut rec_kv)?;
recs = std::cmp::max(recs, rec_kv.len());
for (k, v) in rec_kv {
let key = format!("{k}.{idx}");
self.add(key, v)?;
}
count += 1;
}
self.add_param("Count", count)?;
Ok(())
}
pub fn decode_anon_list<T, F, E>(
&self,
keys: &[&str],
f: F
) -> Result<Vec<T>, E>
where
F: Fn(&HashMap<&str, &str>) -> Result<T, E>,
E: From<Error>
{
let mut ret = Vec::new();
let mut recmap: HashMap<&str, &str> = HashMap::new();
let count = self.get_fromstr::<usize, _>("Count")?.ok_or_else(|| {
Error::Param(ParamError::Key("Missing 'Count'".into()))
})?;
for idx in 0..count {
recmap.clear();
for (k, ki) in keys.iter().map(|k| (k, format!("{k}.{idx}",))) {
let Some(v) = self.0.get(&ki) else {
continue;
};
recmap.insert(k, v);
}
let vecrec = f(&recmap)?;
ret.push(vecrec);
}
Ok(ret)
}
pub fn encode_named_list<T, I, F, E>(
&mut self,
name: &str,
it: I,
f: F
) -> Result<(), E>
where
I: IntoIterator<Item = T>,
F: Fn(T, &mut Vec<(String, String)>) -> Result<(), E>,
E: From<Error>
{
let mut count = 0;
let mut recs = 0;
for (idx, rec) in it.into_iter().enumerate() {
let mut rec_kv = Vec::with_capacity(recs);
f(rec, &mut rec_kv)?;
recs = std::cmp::max(recs, rec_kv.len());
for (k, v) in rec_kv {
let key = format!("{name}.{k}.{idx}");
self.add(key, v)?;
}
count += 1;
}
let key = format!("{name}.Count");
self.add_param(key, count)?;
Ok(())
}
pub fn decode_named_list<T, F, E>(
&self,
name: &str,
keys: &[&str],
f: F
) -> Result<Vec<T>, E>
where
F: Fn(&HashMap<&str, &str>) -> Result<T, E>,
E: From<Error>
{
let mut ret = Vec::new();
let mut recmap: HashMap<&str, &str> = HashMap::new();
let key = format!("{name}.Count");
let count = self.get_fromstr::<usize, _>(&key)?.ok_or_else(|| {
Error::Param(ParamError::Key("Missing 'Count'".into()))
})?;
for idx in 0..count {
recmap.clear();
for (k, ki) in keys.iter().map(|k| (k, format!("{name}.{k}.{idx}",))) {
let Some(v) = self.0.get(&ki) else {
continue;
};
recmap.insert(k, v);
}
let vecrec = f(&recmap)?;
ret.push(vecrec);
}
Ok(ret)
}
}
impl TryFrom<HashMap<String, String>> for Params {
type Error = Error;
fn try_from(hm: HashMap<String, String>) -> Result<Self, Self::Error> {
for (k, v) in &hm {
validate_param_key(k)?;
validate_param_value(v)?;
}
Ok(Self(hm))
}
}
impl fmt::Display for Params {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut kvlist = Vec::new();
for (key, value) in &self.0 {
kvlist.push(format!("{key}={value}"));
}
write!(f, "{{{}}}", kvlist.join(","))
}
}
#[cfg(test)]
mod tests {
use super::Params;
#[test]
fn string() {
let mut msg = Params::new();
msg.add_str("Foo", "bar").unwrap();
assert_eq!(msg.get_str("Foo").unwrap(), "bar");
assert_eq!(msg.get_str("Moo"), None);
}
#[test]
fn exists() {
let mut params = Params::new();
params.add_str("foo", "bar").unwrap();
assert!(params.contains("foo"));
assert!(!params.contains("nonexistent"));
}
#[test]
fn empty_value() {
let mut params = Params::new();
params.add_str("foo", "").unwrap();
assert_eq!(params.get_str("foo"), Some(""));
}
#[test]
fn integer() {
let mut msg = Params::new();
msg.add_str("Num", "64").unwrap();
assert_eq!(msg.get_fromstr::<u16, _>("Num").unwrap().unwrap(), 64);
}
#[test]
fn size() {
let mut msg = Params::new();
msg.add_param("Num", 7_usize).unwrap();
assert_eq!(msg.get_fromstr::<usize, _>("Num").unwrap().unwrap(), 7);
}
#[test]
fn intoparams() {
let mut msg = Params::new();
msg.add_str("Foo", "bar").unwrap();
assert_eq!(msg.get_str("Foo").unwrap(), "bar");
assert_eq!(msg.get_str("Moo"), None);
let hm = msg.into_inner();
let kv = hm.get_key_value("Foo");
if let Some((_k, v)) = kv {
assert_eq!(v, "bar");
}
}
#[test]
fn display() {
let mut params = Params::new();
params.add_str("foo", "bar").unwrap();
let s = format!("{params}");
assert_eq!(s, "{foo=bar}");
}
#[test]
fn ser_size() {
let mut params = Params::new();
params.add_str("foo", "bar").unwrap();
params.add_str("moo", "cow").unwrap();
let sz = params.calc_buf_size();
assert_eq!(sz, 8 + 8 + 1);
}
#[test]
#[should_panic(expected = "Param(Key(\"invalid character\"))")]
fn key_invalid_char() {
let mut param = Params::new();
param.add_str("hell o", "world").unwrap();
}
#[test]
#[should_panic(expected = "Param(Key(\"empty\"))")]
fn empty_key() {
let mut param = Params::new();
param.add_str("", "world").unwrap();
}
#[test]
#[should_panic(expected = "Param(Value(\"contains newline\"))")]
fn value_newline() {
let mut param = Params::new();
param.add_str("greeting", "hello\nworld").unwrap();
}
#[test]
fn boolvals() {
let mut params = Params::new();
params.add_bool("abool", true).unwrap();
params.add_bool("abooltoo", false).unwrap();
let Ok(Some(true)) = params.get_bool("abool") else {
panic!("Unexpectedly not Ok(true)");
};
let Ok(Some(false)) = params.get_bool("abooltoo") else {
panic!("Unexpectedly not Ok(false)");
};
}
#[test]
#[should_panic(expected = "Param(Value(\"not boolean\"))")]
fn bad_bool() {
let mut params = Params::new();
params.add_str("fool", "uncertain").unwrap();
params.get_bool("fool").unwrap();
}
}
#[cfg(test)]
mod recvec_tests {
use super::{Error, Params};
struct Agent {
name: String,
age: u8
}
impl Agent {
fn new(name: impl Into<String>, age: u8) -> Self {
Self {
name: name.into(),
age
}
}
}
#[test]
fn encode_anon() {
let mut params = Params::new();
let agents = vec![Agent::new("frank", 42), Agent::new("anon", 32)];
params
.encode_anon_list::<_, _, _, Error>(agents, |agent, v| {
v.push(("Name".to_string(), agent.name));
v.push(("Age".to_string(), agent.age.to_string()));
Ok(())
})
.unwrap();
assert_eq!(params.get_fromstr::<usize, _>("Count").unwrap().unwrap(), 2);
assert_eq!(params.get_str("Name.0"), Some("frank"));
assert_eq!(params.get_str("Age.0"), Some("42"));
assert_eq!(params.get_str("Name.1"), Some("anon"));
assert_eq!(params.get_str("Age.1"), Some("32"));
}
#[test]
fn decode_anon() {
let mut params = Params::new();
params.add_param("Name.0", "frank").unwrap();
params.add_param("Age.0", "42").unwrap();
params.add_param("Name.1", "anon").unwrap();
params.add_param("Age.1", "32").unwrap();
params.add_param("Count", "2").unwrap();
let v = params
.decode_anon_list::<_, _, Error>(&["Name", "Age"], |recmap| {
let name = (*recmap.get("Name").unwrap()).to_string();
let age = recmap.get("Age").unwrap();
let age = age.parse::<u8>().unwrap();
Ok(Agent { name, age })
})
.unwrap();
assert_eq!(v.len(), 2);
assert_eq!(&v[0].name, "frank");
assert_eq!(v[0].age, 42);
assert_eq!(&v[1].name, "anon");
assert_eq!(v[1].age, 32);
}
#[test]
#[should_panic(expected = "Param(Key(\"Missing 'Count'\"))")]
fn decode_no_count() {
let params = Params::new();
let _v = params
.decode_anon_list::<_, _, Error>(&["Name", "Age"], |recmap| {
let name = (*recmap.get("Name").unwrap()).to_string();
let age = recmap.get("Age").unwrap();
let age = age.parse::<u8>().unwrap();
Ok(Agent { name, age })
})
.unwrap();
}
#[test]
#[should_panic(expected = "Param(Value(\"invalid digit found in string\"))")]
fn decode_bad_count() {
let mut params = Params::new();
params.add_param("Count", "moo").unwrap();
let _v = params
.decode_anon_list::<_, _, Error>(&["Name", "Age"], |recmap| {
let name = (*recmap.get("Name").unwrap()).to_string();
let age = recmap.get("Age").unwrap();
let age = age.parse::<u8>().unwrap();
Ok(Agent { name, age })
})
.unwrap();
}
#[test]
fn encode_named() {
let mut params = Params::new();
let agents = vec![Agent::new("frank", 42), Agent::new("anon", 32)];
params
.encode_named_list::<_, _, _, Error>("Agents", agents, |agent, v| {
v.push(("Name".to_string(), agent.name));
v.push(("Age".to_string(), agent.age.to_string()));
Ok(())
})
.unwrap();
assert_eq!(
params
.get_fromstr::<usize, _>("Agents.Count")
.unwrap()
.unwrap(),
2
);
assert_eq!(params.get_str("Agents.Name.0"), Some("frank"));
assert_eq!(params.get_str("Agents.Age.0"), Some("42"));
assert_eq!(params.get_str("Agents.Name.1"), Some("anon"));
assert_eq!(params.get_str("Agents.Age.1"), Some("32"));
}
#[test]
fn decode_named() {
let mut params = Params::new();
params.add_param("Agents.Name.0", "frank").unwrap();
params.add_param("Agents.Age.0", "42").unwrap();
params.add_param("Agents.Name.1", "anon").unwrap();
params.add_param("Agents.Age.1", "32").unwrap();
params.add_param("Agents.Count", "2").unwrap();
let v = params
.decode_named_list::<_, _, Error>("Agents", &["Name", "Age"], |recmap| {
let name = (*recmap.get("Name").unwrap()).to_string();
let age = recmap.get("Age").unwrap();
let age = age.parse::<u8>().unwrap();
Ok(Agent { name, age })
})
.unwrap();
assert_eq!(v.len(), 2);
assert_eq!(&v[0].name, "frank");
assert_eq!(v[0].age, 42);
assert_eq!(&v[1].name, "anon");
assert_eq!(v[1].age, 32);
}
}