use std::str::FromStr;
const MAX_BACKEND_NAME_LEN: usize = 255;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Backend {
name: String,
}
impl Backend {
pub fn from_name(s: &str) -> Result<Self, BackendError> {
s.parse()
}
pub fn name(&self) -> &str {
self.name.as_str()
}
pub fn into_string(self) -> String {
self.name
}
}
#[derive(Copy, Clone, Debug, thiserror::Error, PartialEq, Eq)]
pub enum BackendError {
#[error("an empty string is not a valid backend")]
EmptyName,
#[error("backend names must be <= 255 characters")]
TooLong,
#[error("backend names must only contain visible ASCII characters or spaces")]
InvalidName,
}
impl FromStr for Backend {
type Err = BackendError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
validate_backend(s)?;
Ok(Self { name: s.to_owned() })
}
}
impl std::fmt::Display for Backend {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.name.as_str())
}
}
pub fn validate_backend(backend: &str) -> Result<(), BackendError> {
if backend.is_empty() {
Err(BackendError::EmptyName)
} else if backend.len() > MAX_BACKEND_NAME_LEN {
Err(BackendError::TooLong)
} else if backend.chars().any(is_invalid_char) {
Err(BackendError::InvalidName)
} else {
Ok(())
}
}
#[inline]
fn is_invalid_char(c: char) -> bool {
c != ' ' && !c.is_ascii_graphic()
}
#[cfg(test)]
mod validate_backend_tests {
use super::*;
#[test]
fn valid_backend_names_are_accepted() {
let valid_backend_names = [
"valid_backend_1",
"1_backend_with_leading_integer",
"backend-with-kebab-case",
"backend with spaces",
"backend.with.periods",
"123_456_789_000",
"123.456.789.000",
"tilde~backend",
"(parens-backend)",
];
for backend in valid_backend_names.iter() {
match validate_backend(backend) {
Ok(_) => {}
x => panic!(
"backend string \"{}\" yielded unexpected result: {:?}",
backend, x
),
}
}
}
#[test]
fn empty_str_is_not_accepted() {
let invalid_backend = "";
match validate_backend(invalid_backend) {
Err(BackendError::EmptyName) => {}
x => panic!("unexpected result: {:?}", x),
}
}
#[test]
fn name_equal_to_character_limit_is_accepted() {
use std::iter::FromIterator;
let invalid_backend: String = String::from_iter(vec!['a'; 255]);
match validate_backend(&invalid_backend) {
Ok(_) => {}
x => panic!("unexpected result: {:?}", x),
}
}
#[test]
fn name_longer_than_character_limit_are_not_accepted() {
use std::iter::FromIterator;
let invalid_backend: String = String::from_iter(vec!['a'; 256]);
match validate_backend(&invalid_backend) {
Err(BackendError::TooLong) => {}
x => panic!("unexpected result: {:?}", x),
}
}
#[test]
fn unprintable_characters_are_not_accepted() {
let invalid_backend = "\n";
match validate_backend(invalid_backend) {
Err(BackendError::InvalidName) => {}
x => panic!("unexpected result: {:?}", x),
}
}
#[test]
fn unicode_is_not_accepted() {
let invalid_backend = "♓";
match validate_backend(invalid_backend) {
Err(BackendError::InvalidName) => {}
x => panic!("unexpected result: {:?}", x),
}
}
}