use std::collections::{HashMap, HashSet};
use serde::{Deserialize, Serialize};
use super::option::Option;
use super::param_error::ParamError;
pub fn problem_validate(
primary_items: &Vec<&str>,
secondary_items: &Vec<&str>,
options: &Vec<Option>,
) -> Result<(), ParamError> {
let mut unique_items: HashSet<&str> = HashSet::new();
for item in primary_items {
if !unique_items.insert(item) {
let message = format!("Duplicate primary item {}", &item);
return Err(ParamError::new(message));
}
}
unique_items.clear();
for item in secondary_items {
if !unique_items.insert(item) {
let message = format!("Duplicate secondary item {}", &item);
return Err(ParamError::new(message));
}
if primary_items.contains(&item) {
let message = format!("Item {} both primary and secondary", &item);
return Err(ParamError::new(message));
}
}
let mut opt_count = HashMap::new();
for o in options {
let count = opt_count.entry(&o.label).or_insert(0);
*count += 1;
}
for o in options {
if opt_count.get(&o.label).unwrap() > &1 {
let message = format!("Multiple options with label {}", &o.label);
return Err(ParamError::new(message));
}
}
Ok(())
}
pub fn new_item_validate(items: &Vec<&str>, new: &str) -> Result<(), ParamError> {
if items.contains(&new) {
let message = format!("Item {} overlap with existing item", new);
return Err(ParamError::new(message));
}
Ok(())
}
pub fn new_option_validate(options: &Vec<Option>, new: &Option) -> Result<(), ParamError> {
if options
.iter()
.map(|o| &o.label)
.collect::<Vec<&String>>()
.contains(&&new.label)
{
let message = format!("Option label {} overlap with existing option", &new.label);
return Err(ParamError::new(message));
}
Ok(())
}
#[derive(Debug, Eq, PartialEq, Clone, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
pub struct Problem {
pub primary_items: Vec<String>,
pub secondary_items: Vec<String>,
pub options: Vec<Option>,
}
impl Problem {
pub fn new(
primary_items: &Vec<&str>,
secondary_items: &Vec<&str>,
options: &Vec<Option>,
) -> Result<Self, ParamError> {
problem_validate(&primary_items, &secondary_items, &options)?;
Ok(Self {
primary_items: primary_items.into_iter().map(|s| s.to_string()).collect(),
secondary_items: secondary_items.into_iter().map(|s| s.to_string()).collect(),
options: options.into_iter().map(|o| o.clone()).collect(),
})
}
pub fn new_validated(
primary_items: &Vec<&str>,
secondary_items: &Vec<&str>,
options: &Vec<Option>,
) -> Self {
Self {
primary_items: primary_items.into_iter().map(|s| s.to_string()).collect(),
secondary_items: secondary_items.into_iter().map(|s| s.to_string()).collect(),
options: options.into_iter().map(|o| o.clone()).collect(),
}
}
pub fn add_primary_item(&mut self, primary_item: &str) -> Result<(), ParamError> {
new_item_validate(
&self.primary_items.iter().map(|s| s.as_str()).collect(),
&primary_item,
)?;
self.primary_items.push(primary_item.to_string());
Ok(())
}
pub fn add_primary_valid(&mut self, primary_item: &str) {
self.primary_items.push(primary_item.to_string());
}
pub fn add_secondary_item(&mut self, secondary_item: &str) -> Result<(), ParamError> {
new_item_validate(
&self.secondary_items.iter().map(|s| s.as_str()).collect(),
&secondary_item,
)?;
self.secondary_items.push(secondary_item.to_string());
Ok(())
}
pub fn add_secondary_valid(&mut self, secondary_item: &str) {
self.secondary_items.push(secondary_item.to_string());
}
pub fn add_option(&mut self, option: &Option) -> Result<(), ParamError> {
new_option_validate(&self.options, &option)?;
self.options.push(option.clone());
Ok(())
}
pub fn add_option_valid(&mut self, option: &Option) {
self.options.push(option.clone());
}
}
#[cfg(test)]
mod tests {
use super::*;
fn s(char: &str) -> String {
char.to_string()
}
#[test]
fn test_new_problem() {
let mut p1 = Problem::new(
&vec!["p1", "p2"],
&vec!["s1", "s2"],
&vec![Option::new("o1", &vec!["p1", "p2"], &vec![("s1", "")]).unwrap()],
)
.unwrap();
assert_eq!(p1.primary_items, ["p1", "p2"]);
assert_eq!(p1.secondary_items, ["s1", "s2"]);
assert_eq!(p1.options.len(), 1);
assert_eq!(p1.options[0].label, "o1");
assert_eq!(p1.options[0].primary_items, ["p1", "p2"]);
assert_eq!(p1.options[0].secondary_items, [(s("s1"), s(""))]);
p1.add_primary_item("p3").unwrap();
assert_eq!(p1.primary_items, ["p1", "p2", "p3"]);
p1.add_secondary_item("s3").unwrap();
assert_eq!(p1.secondary_items, ["s1", "s2", "s3"]);
p1.add_option(&Option::new("o2", &vec!["p3"], &vec![]).unwrap())
.unwrap();
assert_eq!(p1.options.len(), 2);
assert_eq!(p1.options[1].label, "o2");
assert_eq!(p1.options[1].primary_items, ["p3"]);
assert_eq!(p1.options[1].secondary_items, []);
let result = serde_json::to_string(&p1);
assert!(result.is_ok());
let json = result.unwrap();
assert_eq!(
json,
r#"{"primary_items":["p1","p2","p3"],"secondary_items":["s1","s2","s3"],"options":[{"label":"o1","primary_items":["p1","p2"],"secondary_items":[["s1",""]]},{"label":"o2","primary_items":["p3"],"secondary_items":[]}]}"#
);
}
#[test]
fn test_new_problem_from_json() {
let valid_json = r#"{"primary_items":["p1","p2","p3"],"secondary_items":["s1","s2","s3"],"options":[{"label":"o1","primary_items":["p1","p2"],"secondary_items":[["s1",""]]},{"label":"o2","primary_items":["p3"],"secondary_items":[]}]}"#;
let result = serde_json::from_str::<Problem>(valid_json);
assert!(result.is_ok());
let problem = result.unwrap();
assert_eq!(problem.primary_items, ["p1", "p2", "p3"]);
let invalid_json = r#"{"unknown_items":["p1","p2","p3"],"secondary_items":["s1","s2","s3"],"options":[{"label":"o1","primary_items":["p1","p2"],"secondary_items":[["s1",""]]},{"label":"o2","primary_items":["p3"],"secondary_items":[]}]}"#;
let result = serde_json::from_str::<Problem>(invalid_json);
assert!(result.is_err());
}
#[test]
fn test_new_problem_items_overlap() {
let result = Problem::new(
&vec!["p1", "p2"],
&vec!["s1", "p1"],
&vec![Option::new("o1", &vec!["p1", "p2"], &vec![("s1", "")]).unwrap()],
);
assert!(result.is_err());
let error = result.unwrap_err();
assert_eq!(
error,
ParamError::new("Item p1 both primary and secondary".to_string())
);
}
#[test]
fn test_new_problem_duplicate_primary() {
let result = Problem::new(
&vec!["p1", "p1"],
&vec!["s1", "s2"],
&vec![Option::new("o1", &vec!["p1", "p2"], &vec![("s1", "")]).unwrap()],
);
assert!(result.is_err());
let error = result.unwrap_err();
assert_eq!(
error,
ParamError::new("Duplicate primary item p1".to_string())
);
}
#[test]
fn test_new_problem_duplicate_secondary() {
let result = Problem::new(
&vec!["p1", "p2"],
&vec!["s1", "s1"],
&vec![Option::new("o1", &vec!["p1", "p2"], &vec![("s1", "")]).unwrap()],
);
assert!(result.is_err());
let error = result.unwrap_err();
assert_eq!(
error,
ParamError::new("Duplicate secondary item s1".to_string())
);
}
#[test]
fn test_new_problem_options_overlap() {
let result = Problem::new(
&vec!["p1", "p2"],
&vec!["s1", "s2"],
&vec![
Option::new("o1", &vec!["p1", "p2"], &vec![("s1", "")]).unwrap(),
Option::new("o1", &vec!["p1", "p2"], &vec![("s1", "")]).unwrap(),
],
);
assert!(result.is_err());
let error = result.unwrap_err();
assert_eq!(
error,
ParamError::new("Multiple options with label o1".to_string())
);
}
#[test]
fn test_add_primary_overlap() {
let mut problem = Problem::new(
&vec!["p1", "p2"],
&vec!["s1", "s2"],
&vec![Option::new("o1", &vec!["p1", "p2"], &vec![("s1", "")]).unwrap()],
)
.unwrap();
let result = problem.add_primary_item("p1");
assert!(result.is_err());
let error = result.unwrap_err();
assert_eq!(
error,
ParamError::new("Item p1 overlap with existing item".to_string())
);
}
#[test]
fn test_add_secondary_overlap() {
let mut problem = Problem::new(
&vec!["p1", "p2"],
&vec!["s1", "s2"],
&vec![Option::new("o1", &vec!["p1", "p2"], &vec![("s1", "")]).unwrap()],
)
.unwrap();
let result = problem.add_secondary_item("s1");
assert!(result.is_err());
let error = result.unwrap_err();
assert_eq!(
error,
ParamError::new("Item s1 overlap with existing item".to_string())
);
}
#[test]
fn test_add_option_overlap() {
let mut problem = Problem::new(
&vec!["p1", "p2"],
&vec!["s1", "s2"],
&vec![Option::new("o1", &vec!["p1", "p2"], &vec![("s1", "")]).unwrap()],
)
.unwrap();
let result =
problem.add_option(&Option::new("o1", &vec!["p1", "p2"], &vec![("s1", "")]).unwrap());
assert!(result.is_err());
let error = result.unwrap_err();
assert_eq!(
error,
ParamError::new("Option label o1 overlap with existing option".to_string())
);
}
}