use std::collections::HashMap;
const ATTR_ARRAY_SEPARATOR_COMMA: &str = ",";
const ATTR_KEY_VALUE_EQUAL_SIGN: &str = "=";
const ATTR_ADD_PLUS_SIGN: &str = "+";
const ATTR_DELETE_MINUS_SIGN: &str = "-";
#[derive(Debug)]
pub struct AttributeParser;
impl AttributeParser {
pub fn parse_to_map(attributes_modification: &str) -> Result<HashMap<String, String>, String> {
if attributes_modification.is_empty() {
return Ok(HashMap::new());
}
let mut attributes = HashMap::new();
let kvs: Vec<&str> = attributes_modification.split(ATTR_ARRAY_SEPARATOR_COMMA).collect();
for kv in kvs {
let mut key = String::new();
let mut value = String::new();
if kv.contains(ATTR_KEY_VALUE_EQUAL_SIGN) {
let splits: Vec<&str> = kv.split(ATTR_KEY_VALUE_EQUAL_SIGN).collect();
key.push_str(splits[0]);
value.push_str(splits[1]);
if !key.contains(ATTR_ADD_PLUS_SIGN) {
return Err(format!("add/alter attribute format is wrong: {key}"));
}
} else {
key.push_str(kv);
if !key.contains(ATTR_DELETE_MINUS_SIGN) {
return Err(format!("delete attribute format is wrong: {key}",));
}
}
if attributes.insert(key.clone(), value).is_some() {
return Err(format!("key duplication: {key}",));
}
}
Ok(attributes)
}
pub fn parse_to_string(attributes: &HashMap<String, String>) -> String {
if attributes.is_empty() {
return String::new();
}
let mut kvs: Vec<String> = Vec::new();
for (key, value) in attributes {
if value.is_empty() {
kvs.push(key.clone());
} else {
kvs.push(format!("{key}{ATTR_KEY_VALUE_EQUAL_SIGN}{value}"));
}
}
kvs.join(ATTR_ARRAY_SEPARATOR_COMMA)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_to_map_empty_string() {
let result = AttributeParser::parse_to_map("");
assert_eq!(result.unwrap(), HashMap::new());
}
#[test]
fn test_parse_to_map_valid_attributes() {
let input = "+key1=value1,+key2=value2,-key3";
let mut expected = HashMap::new();
expected.insert("+key1".to_string(), "value1".to_string());
expected.insert("+key2".to_string(), "value2".to_string());
expected.insert("-key3".to_string(), "".to_string());
let result = AttributeParser::parse_to_map(input);
assert_eq!(result.unwrap(), expected);
}
#[test]
fn test_parse_to_map_add_attribute_format_error() {
let input = "key1=value1,+key2=value2";
let result = AttributeParser::parse_to_map(input);
assert_eq!(
result.unwrap_err(),
"add/alter attribute format is wrong: key1".to_string()
);
}
#[test]
fn test_parse_to_map_delete_attribute_format_error() {
let input = "+key1=value1,key2";
let result = AttributeParser::parse_to_map(input);
assert_eq!(
result.unwrap_err(),
"delete attribute format is wrong: key2".to_string()
);
}
#[test]
fn test_parse_to_map_key_duplication_error() {
let input = "+key1=value1,+key1=value2";
let result = AttributeParser::parse_to_map(input);
assert_eq!(result.unwrap_err(), "key duplication: +key1".to_string());
}
#[test]
fn test_parse_to_string_empty_map() {
let attributes = HashMap::new();
let result = AttributeParser::parse_to_string(&attributes);
assert_eq!(result, "");
}
#[test]
fn test_parse_to_string_nonempty_operations_round_trip() {
for (operations, mut expected) in [
(
vec![("+key1", "hello 世界"), ("+key2", "value2"), ("-key3", "")],
vec!["+key1=hello 世界", "+key2=value2", "-key3"],
),
(vec![("+key1", "hello 世界")], vec!["+key1=hello 世界"]),
(vec![("-key3", "")], vec!["-key3"]),
] {
let attributes = operations
.iter()
.map(|(key, value)| (key.to_string(), value.to_string()))
.collect();
let result = AttributeParser::parse_to_string(&attributes);
let mut tokens: Vec<_> = result.split(',').collect();
tokens.sort_unstable();
expected.sort_unstable();
assert_eq!(tokens, expected);
assert_eq!(AttributeParser::parse_to_map(&result).unwrap(), attributes);
}
}
}