use thiserror::Error;
#[derive(Error, Debug)]
pub enum StrError {
#[error("String cannot be empty")]
Empty,
#[error("Invalid format: {0}")]
InvalidFormat(String),
}
#[derive(PartialEq)]
pub enum CamelFormat {
Upper,
Lower,
}
pub fn split_camel_case(s: &str, format: CamelFormat) -> Result<Vec<String>, StrError> {
if s.is_empty() {
return Err(StrError::Empty);
}
let chars: Vec<char> = s.chars().collect();
if CamelFormat::Upper == format && !chars[0].is_ascii_uppercase() {
return Err(StrError::InvalidFormat(
"First character must be uppercase".to_string(),
));
} else if CamelFormat::Lower == format && !chars[0].is_ascii_lowercase() {
return Err(StrError::InvalidFormat(
"First character must be lowercase".to_string(),
));
}
let mut words = Vec::new();
let mut current_word = String::new();
for (i, ch) in chars.iter().enumerate() {
if !ch.is_ascii_alphanumeric() {
return Err(StrError::InvalidFormat(format!(
"Invalid character '{ch}' at position {i}"
)));
}
if ch.is_ascii_uppercase() {
if i > 0 {
let prev_ch = chars[i - 1];
if prev_ch.is_ascii_lowercase() || prev_ch.is_ascii_digit() {
if !current_word.is_empty() {
words.push(current_word.clone());
current_word.clear();
}
} else if prev_ch.is_ascii_uppercase() {
if i + 1 < chars.len() {
let next_ch = chars[i + 1];
if next_ch.is_ascii_lowercase() && current_word.len() > 1 {
let last_char = current_word.pop().unwrap();
words.push(current_word.clone());
current_word.clear();
current_word.push(last_char);
}
}
}
}
}
current_word.push(*ch);
}
if !current_word.is_empty() {
words.push(current_word);
}
Ok(words)
}
pub fn snake_to_pascal(s: &str) -> String {
s.split('_')
.filter(|seg| !seg.is_empty())
.map(|seg| {
let mut c = seg.chars();
match c.next() {
None => String::new(),
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
}
})
.collect()
}