use std::fmt;
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be converted from a field's text",
note = "implement `FieldValue` for it, or derive it for an enumeration"
)]
pub trait FieldValue: Sized + 'static {
fn from_field_text(text: &str) -> Result<Self, FieldValueError>;
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct FieldValueError;
impl fmt::Display for FieldValueError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("invalid field value")
}
}
impl std::error::Error for FieldValueError {}
impl FieldValue for String {
fn from_field_text(text: &str) -> Result<Self, FieldValueError> {
Ok(text.to_owned())
}
}
impl FieldValue for bool {
fn from_field_text(text: &str) -> Result<Self, FieldValueError> {
match text {
"true" => Ok(true),
"false" => Ok(false),
_ => Err(FieldValueError),
}
}
}
fn integer_digits(text: &str) -> Option<(bool, &str)> {
let (negative, digits) = match text.as_bytes().first() {
Some(b'-') => (true, &text[1..]),
Some(b'+') => (false, &text[1..]),
_ => (false, text),
};
(!digits.is_empty() && digits.bytes().all(|byte| byte.is_ascii_digit()))
.then_some((negative, digits))
}
macro_rules! signed_field_values {
($($type:ty),*) => {$(
impl FieldValue for $type {
fn from_field_text(text: &str) -> Result<Self, FieldValueError> {
integer_digits(text).ok_or(FieldValueError)?;
text.parse().map_err(|_| FieldValueError)
}
}
)*};
}
macro_rules! unsigned_field_values {
($($type:ty),*) => {$(
impl FieldValue for $type {
fn from_field_text(text: &str) -> Result<Self, FieldValueError> {
let (negative, digits) = integer_digits(text).ok_or(FieldValueError)?;
if negative {
return if digits.bytes().all(|byte| byte == b'0') { Ok(0) } else { Err(FieldValueError) };
}
digits.parse().map_err(|_| FieldValueError)
}
}
)*};
}
signed_field_values!(i8, i16, i32, i64, i128, isize);
unsigned_field_values!(u8, u16, u32, u64, u128, usize);
fn is_decimal(text: &str) -> bool {
let bytes = text.as_bytes();
let mut index = usize::from(matches!(bytes.first(), Some(b'+' | b'-')));
let digits = |index: &mut usize| {
let start = *index;
while bytes.get(*index).is_some_and(u8::is_ascii_digit) {
*index += 1;
}
*index - start
};
let mut mantissa = digits(&mut index);
if bytes.get(index) == Some(&b'.') {
index += 1;
mantissa += digits(&mut index);
}
if mantissa == 0 {
return false;
}
if matches!(bytes.get(index), Some(b'e' | b'E')) {
index += 1;
index += usize::from(matches!(bytes.get(index), Some(b'+' | b'-')));
if digits(&mut index) == 0 {
return false;
}
}
index == bytes.len()
}
macro_rules! float_field_values {
($($type:ty),*) => {$(
impl FieldValue for $type {
fn from_field_text(text: &str) -> Result<Self, FieldValueError> {
if !is_decimal(text) {
return Err(FieldValueError);
}
let value: $type = text.parse().expect("a decimal number parses");
if value.is_finite() { Ok(value) } else { Err(FieldValueError) }
}
}
)*};
}
float_field_values!(f32, f64);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Charset {
Unspecified,
Utf8,
UsAscii,
Unsupported,
}
impl Charset {
pub(crate) fn from_parameter(charset: Option<&str>) -> Self {
match charset {
None => Self::Unspecified,
Some(name) if name.eq_ignore_ascii_case("utf-8") => Self::Utf8,
Some(name) if name.eq_ignore_ascii_case("us-ascii") => Self::UsAscii,
Some(_) => Self::Unsupported,
}
}
pub(crate) fn decode(self, bytes: Vec<u8>) -> Option<String> {
match self {
Self::Unsupported => None,
Self::UsAscii if !bytes.is_ascii() => None,
_ => String::from_utf8(bytes).ok(),
}
}
}