use std::fmt;
use crate::deploy::outcomes::actions::keywords::problem::{BadValue, Problem, plain};
use crate::utils::time::Stamp;
pub const MAX_SHORT_LEN: usize = 10;
pub const HORIZON_MS: u64 = 366 * 24 * 60 * 60 * 1000;
fn is_uint(text: &str) -> bool {
let digits = !text.is_empty() && text.bytes().all(|byte| byte.is_ascii_digit());
digits && (text == "0" || !text.starts_with('0'))
}
fn is_udecimal(text: &str) -> bool {
let (whole, fraction) = match text.split_once('.') {
Some((whole, fraction)) => (whole, Some(fraction)),
None => (text, None),
};
is_uint(whole)
&& fraction.is_none_or(|fraction| {
!fraction.is_empty() && fraction.bytes().all(|byte| byte.is_ascii_digit()) && !fraction.ends_with('0')
})
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Hint {
DateTime,
Date,
ShortString,
HlPerp,
UInt,
UDecimal,
String,
Other(String),
}
impl Hint {
#[must_use]
pub fn parse(name: &str) -> Hint {
match name {
"dateTime" => Hint::DateTime,
"date" => Hint::Date,
"shortString" => Hint::ShortString,
"hlPerp" => Hint::HlPerp,
"uInt" => Hint::UInt,
"uDecimal" => Hint::UDecimal,
"string" => Hint::String,
other => Hint::Other(other.to_owned()),
}
}
#[must_use]
pub fn name(&self) -> &str {
match self {
Hint::DateTime => "dateTime",
Hint::Date => "date",
Hint::ShortString => "shortString",
Hint::HlPerp => "hlPerp",
Hint::UInt => "uInt",
Hint::UDecimal => "uDecimal",
Hint::String => "string",
Hint::Other(name) => name,
}
}
pub fn check(&self, keyword: &str, value: &str) -> Result<(), BadValue> {
plain(keyword, value)?;
let problem = match self {
Hint::DateTime if Stamp::exact(value).is_none() => Problem::DateTime,
Hint::Date if Stamp::day(value).is_none() => Problem::Date,
Hint::ShortString if value.len() > MAX_SHORT_LEN => Problem::ShortString,
Hint::UInt if !is_uint(value) => Problem::UInt,
Hint::UInt if value.parse::<u64>().is_err() => Problem::TooLarge,
Hint::UDecimal if !is_udecimal(value) => Problem::UDecimal,
_ => return Ok(()),
};
Err(BadValue::new(keyword, problem))
}
pub fn check_at(&self, keyword: &str, value: &str, now_ms: u64) -> Result<(), BadValue> {
self.check(keyword, value)?;
if *self != Hint::DateTime {
return Ok(());
}
let Some(stamp) = Stamp::exact(value) else {
return Err(BadValue::new(keyword, Problem::DateTime));
};
let at = i128::from(stamp.ms());
let now = i128::from(now_ms);
let problem = if at <= now {
Problem::Passed
} else if at > now + i128::from(HORIZON_MS) {
Problem::TooFar
} else {
return Ok(());
};
Err(BadValue::new(keyword, problem))
}
}
impl fmt::Display for Hint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[cfg(test)]
#[path = "tests/hint.rs"]
mod tests;