Files
payphone/src/utils.rs
T
2025-08-25 20:03:21 -03:00

136 lines
3.9 KiB
Rust

//! A set of generic utilities used across the codebase.
//!
//! It currently includes [`ToTitleCase`], [`get_random_u64`], and [`Bye`].
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
/// Converts a string to title case.
///
/// It removes all duplicate whitespaces and capitalizes the first letter of every word.
pub trait ToTitleCase {
fn to_title_case(&self) -> String;
}
impl<T> ToTitleCase for T
where
T: ToString,
{
/// Removes duplicated whitespaces and capitalizes the first letter of each word.
///
/// Returns:
/// [`String`] - The title-cased version of the string.
fn to_title_case(&self) -> String {
self.to_string()
.split_whitespace()
.map(|word| {
let mut chars = word.chars();
match chars.next() {
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
None => String::new(),
}
})
.collect::<Vec<_>>()
.join(" ")
}
}
/// Returns a random `u64` generated using a cryptographically secure random number generator.
///
/// Returns:
/// [`u64`] - A randomly generated 64-bit unsigned integer.
pub fn get_random_u64() -> u64 {
let mut rng = ChaCha20Rng::from_os_rng();
rng.random()
}
/// Consumes any value. Used to normalize return values when the output of a function doesn't
/// matter yet all functions being called return different types.
pub trait Bye: Sized {
/// Whatever the value is, it consumes it and returns a unit value. Used to remove warnings
/// about unused results.
fn bye(self) {}
}
impl<T> Bye for T {}
/// Trims a string to a maximum length, adding an ellipsis if it exceeds the limit.
///
/// Arguments:
/// * `string` - The string to trim with ellipse.
/// * `max_length` - The max length the string may have.
///
/// Returns:
/// [`String`] - The trimmed string with an ellipsis if it exceeded the limit.
pub fn ellipse(string: String, max_length: usize) -> String {
if string.len() <= max_length {
string
} else {
let len = max_length.saturating_sub(3);
format!("{}...", &string[..len])
}
}
/// Copies the bytes from [`i64`] to [`u64`].
///
/// WARNING: This does NOT keep the same "represented" value. This only keeps the bytes intact,
/// e.g. 0b0101 will still be 0b0101, but the value represented will change since the type changes.
///
/// Arguments:
/// * `i` - The [`i64`] to convert.
///
/// Returns:
/// [`u64`] - The resulting [`u64`].
pub fn i64_to_u64(i: i64) -> u64 {
u64::from_ne_bytes(i.to_ne_bytes())
}
/// Copies the bytes from [`u64`] to [`i64`].
///
/// WARNING: This does NOT keep the same "represented" value. This only keeps the bytes intact,
/// e.g. 0b0101 will still be 0b0101, but the value represented will change since the type changes.
///
/// Arguments:
/// * `u` - The [`u64`] to convert.
///
/// Returns:
/// [`i64`] - The resulting [`i64`].
pub fn u64_to_i64(u: u64) -> i64 {
i64::from_ne_bytes(u.to_ne_bytes())
}
/// Splits at the `n`th occurrence of `pattern` counting from the end of the string.
///
/// Arguments:
/// * `s` - The string to split.
/// * `pattern` - The pattern to split at.
/// * `n` - The occurrence to split at (counting from the end).
///
/// Returns:
/// [`Option<(&str, &str)>`] - The splitted parts of the string, or [`None`] if no occurrences of
/// `pattern` were found in `s`.
pub fn split_at_nth_from_end<'a, 'b>(
s: &'a str,
pattern: &'b str,
n: usize,
) -> Option<(&'a str, &'a str)> {
let mut end = s.len();
let mut count = 0;
while let Some(pos) = s[..end].rfind(pattern) {
count += 1;
if count == n {
let before = &s[..pos];
let after = &s[pos + pattern.len()..];
return Some((before, after));
}
end = pos; // move left and keep searching
}
None
}