use thiserror::Error;
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum Error {
#[error("Integer overflow: {0}")]
Overflow(String),
#[error("Division by zero")]
DivisionByZero,
#[error("Value {value} out of bounds [{min}, {max}]")]
OutOfBounds {
value: i64,
min: i64,
max: i64,
},
#[error("Invalid UTF-8 encoding: {0}")]
InvalidUtf8(String),
#[error("Invalid format: {0}")]
InvalidFormat(String),
#[error("Parse error: {0}")]
ParseError(String),
#[error("Validation error: {0}")]
ValidationError(String),
#[error("Empty input not allowed")]
EmptyInput,
#[error("Path traversal detected: {0}")]
PathTraversal(String),
#[error("Injection attempt detected: {0}")]
InjectionDetected(String),
#[error("Input too long: {0}")]
TooLong(String),
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NonEmpty<T> {
head: T,
tail: Vec<T>,
}
impl<T> NonEmpty<T> {
pub fn singleton(value: T) -> Self {
NonEmpty {
head: value,
tail: Vec::new(),
}
}
pub fn new(head: T, tail: Vec<T>) -> Self {
NonEmpty { head, tail }
}
pub fn from_vec(mut vec: Vec<T>) -> Option<Self> {
if vec.is_empty() {
None
} else {
let head = vec.remove(0);
Some(NonEmpty { head, tail: vec })
}
}
pub fn head(&self) -> &T {
&self.head
}
pub fn tail(&self) -> &[T] {
&self.tail
}
pub fn len(&self) -> usize {
1 + self.tail.len()
}
pub fn is_singleton(&self) -> bool {
self.tail.is_empty()
}
pub fn to_vec(self) -> Vec<T> {
let mut v = vec![self.head];
v.extend(self.tail);
v
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Bounded<const MIN: i64, const MAX: i64> {
value: i64,
}
impl<const MIN: i64, const MAX: i64> Bounded<MIN, MAX> {
pub fn new(value: i64) -> Result<Self> {
if value >= MIN && value <= MAX {
Ok(Bounded { value })
} else {
Err(Error::OutOfBounds {
value,
min: MIN,
max: MAX,
})
}
}
pub fn get(&self) -> i64 {
self.value
}
pub const fn min() -> i64 {
MIN
}
pub const fn max() -> i64 {
MAX
}
}
pub type Percentage = Bounded<0, 100>;
pub type Port = Bounded<0, 65535>;
pub type Byte = Bounded<0, 255>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_non_empty() {
let ne = NonEmpty::singleton(42);
assert_eq!(ne.len(), 1);
assert_eq!(*ne.head(), 42);
assert!(ne.is_singleton());
}
#[test]
fn test_bounded() {
let pct: Result<Percentage> = Bounded::new(50);
assert!(pct.is_ok());
assert_eq!(pct.unwrap().get(), 50);
let invalid: Result<Percentage> = Bounded::new(101);
assert!(invalid.is_err());
}
}