use crate::rule::rule_result::RuleResult;
use crate::rule::{PasswordData, Rule};
use std::collections::HashMap;
const ERROR_CODE: &str = "ILLEGAL_REPEATED_CHARS";
const DEFAULT_SEQUENCE_LENGTH: usize = 5;
const DEFAULT_SEQUENCE_COUNT: usize = 1;
pub struct RepeatCharactersRule {
sequence_length: usize,
sequence_count: usize,
}
impl RepeatCharactersRule {
pub fn new(sequence_length: usize, sequence_count: usize) -> Result<Self, String> {
if sequence_count < 1 {
return Err("sequence count must be > 0".into());
}
if sequence_length < 2 {
return Err("sequence length must be > 2".into());
}
Ok(Self {
sequence_length,
sequence_count,
})
}
pub fn with_sequence_length(sequence_length: usize) -> Result<Self, String> {
Self::new(sequence_length, DEFAULT_SEQUENCE_COUNT)
}
fn create_rule_result_detail_parameters(&self, matches: &[String]) -> HashMap<String, String> {
let mut map = HashMap::with_capacity(4);
map.insert(
"sequenceLength".to_string(),
self.sequence_length.to_string(),
);
map.insert("sequenceCount".to_string(), self.sequence_count.to_string());
map.insert("matchesCount".to_string(), matches.len().to_string());
map.insert("matches".to_string(), matches.join(","));
map
}
}
impl Default for RepeatCharactersRule {
fn default() -> Self {
Self::new(DEFAULT_SEQUENCE_LENGTH, DEFAULT_SEQUENCE_COUNT).unwrap()
}
}
impl Rule for RepeatCharactersRule {
fn validate(&self, password_data: &PasswordData) -> RuleResult {
let mut result = RuleResult::default();
let mut matches = vec![];
let password = format!("{}{}", password_data.password(), '\u{ffff}');
let mut count = 0;
let mut repeat = 1;
let mut prev: Option<char> = None;
let chars: Vec<char> = password.chars().collect();
let max = chars.len() - 1;
for i in 0..=max {
let c = chars[i];
if prev.is_some() && c == prev.unwrap() {
repeat += 1;
} else {
if repeat >= self.sequence_length {
let m: String = chars[i - repeat..i].iter().collect();
matches.push(m);
count += 1;
}
repeat = 1;
}
prev = Some(c);
}
if count >= self.sequence_count {
result.add_error(
ERROR_CODE,
Some(self.create_rule_result_detail_parameters(&matches)),
);
}
result
}
}
#[cfg(test)]
mod tests {
use crate::rule::PasswordData;
use crate::rule::repeat_characters::{ERROR_CODE, RepeatCharactersRule};
use crate::test::{RulePasswordTestItem, check_messages, check_passwords};
#[test]
fn test_passwords() {
let test_cases: Vec<RulePasswordTestItem> = vec![
RulePasswordTestItem(
Box::new(RepeatCharactersRule::default()),
PasswordData::with_password("p4zRcv8#n65".to_string()),
vec![],
),
RulePasswordTestItem(
Box::new(RepeatCharactersRule::default()),
PasswordData::with_password("p4&&&&&#n65".to_string()),
vec![ERROR_CODE],
),
RulePasswordTestItem(
Box::new(RepeatCharactersRule::default()),
PasswordData::with_password("p4vvvvvvv#n65".to_string()),
vec![ERROR_CODE],
),
RulePasswordTestItem(
Box::new(RepeatCharactersRule::with_sequence_length(7).unwrap()),
PasswordData::with_password("p4zRcv8#n65".to_string()),
vec![],
),
RulePasswordTestItem(
Box::new(RepeatCharactersRule::with_sequence_length(7).unwrap()),
PasswordData::with_password("p4&&&&&#n65".to_string()),
vec![],
),
RulePasswordTestItem(
Box::new(RepeatCharactersRule::with_sequence_length(7).unwrap()),
PasswordData::with_password("p4vvvvvvv#n65".to_string()),
vec![ERROR_CODE],
),
RulePasswordTestItem(
Box::new(RepeatCharactersRule::default()),
PasswordData::with_password("p4&&&&&#n65FFFFF".to_string()),
vec![ERROR_CODE],
),
RulePasswordTestItem(
Box::new(RepeatCharactersRule::new(5, 3).unwrap()),
PasswordData::with_password("p4&&&&&#n65FFFFF".to_string()),
vec![],
),
RulePasswordTestItem(
Box::new(RepeatCharactersRule::new(5, 2).unwrap()),
PasswordData::with_password("p4&&&&&#n65FFFF".to_string()),
vec![],
),
RulePasswordTestItem(
Box::new(RepeatCharactersRule::new(5, 2).unwrap()),
PasswordData::with_password("p4&&&&&#n65FFFFF".to_string()),
vec![ERROR_CODE],
),
RulePasswordTestItem(
Box::new(RepeatCharactersRule::new(5, 3).unwrap()),
PasswordData::with_password("p4&&&&&#n65FFFFF".to_string()),
vec![],
),
];
check_passwords(test_cases);
}
#[test]
fn test_messages() {
let test_cases: Vec<RulePasswordTestItem> = vec![RulePasswordTestItem(
Box::new(RepeatCharactersRule::new(2, 2).unwrap()),
PasswordData::with_password("paaxvbbdkccx".to_string()),
vec!["ILLEGAL_REPEATED_CHARS,3,2,2,aa,bb,cc"],
)];
check_messages(test_cases);
}
}