use std::str;
use crate::objects::{
sortedset::sorted_set_object::{ExpireOption, SortedSetAddOption},
sortedsetgeo::geo_hash::{GeoDistanceUnitType, GeoHash},
};
#[inline]
pub fn equals_ignore_case(a: &[u8], b: &[u8]) -> bool {
a.eq_ignore_ascii_case(b)
}
#[inline]
pub fn try_parse_with_infinity(v: &[u8]) -> Option<f64> {
match v.len() {
3 if equals_ignore_case(v, b"inf") => return Some(f64::INFINITY),
4 if equals_ignore_case(v, b"+inf") => return Some(f64::INFINITY),
4 if equals_ignore_case(v, b"-inf") => return Some(f64::NEG_INFINITY),
_ => {}
}
let d = str::from_utf8(v).ok()?.parse::<f64>().ok()?;
if d.is_nan() || (d.is_infinite() && !v.iter().any(u8::is_ascii_digit)) {
return None;
}
Some(d)
}
#[inline]
pub fn try_get_long(v: &[u8]) -> Option<i64> {
let s = str::from_utf8(v).ok()?;
let digits = match s.as_bytes().first() {
Some(b'+') | Some(b'-') => &s[1..],
_ => s,
};
if digits.len() > 1 && digits.starts_with('0') {
return None;
}
if digits.is_empty() || !digits.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
s.parse().ok()
}
#[inline]
pub fn try_get_int(v: &[u8]) -> Option<i32> {
try_get_long(v).and_then(|v| i32::try_from(v).ok())
}
#[inline]
pub fn try_get_sorted_set_add_option(v: &[u8]) -> Option<SortedSetAddOption> {
let opt = if equals_ignore_case(v, b"XX") {
SortedSetAddOption::XX
} else if equals_ignore_case(v, b"NX") {
SortedSetAddOption::NX
} else if equals_ignore_case(v, b"LT") {
SortedSetAddOption::LT
} else if equals_ignore_case(v, b"GT") {
SortedSetAddOption::GT
} else if equals_ignore_case(v, b"CH") {
SortedSetAddOption::CH
} else if equals_ignore_case(v, b"INCR") {
SortedSetAddOption::INCR
} else {
return None;
};
Some(opt)
}
#[inline]
pub fn try_get_expire_option(v: &[u8]) -> Option<ExpireOption> {
let opt = if equals_ignore_case(v, b"NX") {
ExpireOption::NX
} else if equals_ignore_case(v, b"XX") {
ExpireOption::XX
} else if equals_ignore_case(v, b"GT") {
ExpireOption::GT
} else if equals_ignore_case(v, b"LT") {
ExpireOption::LT
} else {
return None;
};
Some(opt)
}
#[inline]
pub fn try_get_geo_distance_unit(v: &[u8]) -> Option<GeoDistanceUnitType> {
let unit = if equals_ignore_case(v, b"m") {
GeoDistanceUnitType::M
} else if equals_ignore_case(v, b"km") {
GeoDistanceUnitType::Km
} else if equals_ignore_case(v, b"mi") {
GeoDistanceUnitType::Mi
} else if equals_ignore_case(v, b"ft") {
GeoDistanceUnitType::Ft
} else {
return None;
};
Some(unit)
}
#[inline]
pub fn try_get_geo_lon_lat(lon: &[u8], lat: &[u8]) -> Option<(f64, f64)> {
let longitude = try_parse_with_infinity(lon)?;
let latitude = try_parse_with_infinity(lat)?;
if !(geo_longitude_in_range(longitude) && geo_latitude_in_range(latitude)) {
return None;
}
Some((longitude, latitude))
}
#[inline]
fn geo_longitude_in_range(lon: f64) -> bool {
(GeoHash::LONGITUDE_MIN..=GeoHash::LONGITUDE_MAX).contains(&lon)
}
#[inline]
fn geo_latitude_in_range(lat: f64) -> bool {
(GeoHash::LATITUDE_MIN..=GeoHash::LATITUDE_MAX).contains(&lat)
}
#[inline]
pub fn now_ticks() -> i64 {
let now_ms = coarsetime::Clock::now_since_epoch().as_millis() as i64;
(now_ms + 62_135_596_800_000) * 10_000
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn infinity_forms() {
assert_eq!(try_parse_with_infinity(b"inf"), Some(f64::INFINITY));
assert_eq!(try_parse_with_infinity(b"INF"), Some(f64::INFINITY));
assert_eq!(try_parse_with_infinity(b"+inf"), Some(f64::INFINITY));
assert_eq!(try_parse_with_infinity(b"-INF"), Some(f64::NEG_INFINITY));
assert_eq!(try_parse_with_infinity(b"+Inf"), Some(f64::INFINITY));
assert_eq!(try_parse_with_infinity(b"-inf"), Some(f64::NEG_INFINITY));
assert_eq!(try_parse_with_infinity(b"nan"), None);
assert_eq!(try_parse_with_infinity(b"NaN"), None);
assert_eq!(try_parse_with_infinity(b"Infinity"), None);
assert_eq!(try_parse_with_infinity(b"abc"), None);
assert_eq!(try_parse_with_infinity(b"1.5"), Some(1.5));
assert_eq!(try_parse_with_infinity(b"1e3"), Some(1000.0));
assert_eq!(try_parse_with_infinity(b"1.5x"), None);
}
#[test]
fn ints_strict() {
assert_eq!(try_get_long(b"42"), Some(42));
assert_eq!(try_get_long(b"-1"), Some(-1));
assert_eq!(try_get_long(b"+7"), Some(7));
assert_eq!(try_get_long(b"0"), Some(0));
assert_eq!(try_get_long(b"-0"), Some(0));
assert_eq!(try_get_long(b"007"), None);
assert_eq!(try_get_long(b"-007"), None);
assert_eq!(try_get_long(b"1.5"), None);
assert_eq!(try_get_long(b""), None);
assert_eq!(try_get_long(b"+"), None);
assert_eq!(try_get_int(b"2147483647"), Some(i32::MAX));
assert_eq!(try_get_int(b"2147483648"), None);
}
#[test]
fn options_tokens() {
assert_eq!(
try_get_sorted_set_add_option(b"incr"),
Some(SortedSetAddOption::INCR)
);
assert_eq!(try_get_sorted_set_add_option(b"zz"), None);
assert_eq!(try_get_expire_option(b"gt"), Some(ExpireOption::GT));
assert_eq!(try_get_expire_option(b"nx"), Some(ExpireOption::NX));
assert_eq!(try_get_expire_option(b""), None);
assert_eq!(
try_get_geo_distance_unit(b"KM"),
Some(GeoDistanceUnitType::Km)
);
assert_eq!(try_get_geo_distance_unit(b"parsecs"), None);
}
#[test]
fn geo_lon_lat_ranges() {
let parsed = try_get_geo_lon_lat(b"2.3522", b"48.8566");
assert_eq!(parsed, Some((2.3522, 48.8566)));
assert_eq!(try_get_geo_lon_lat(b"181", b"0"), None);
assert_eq!(try_get_geo_lon_lat(b"0", b"91"), None);
assert_eq!(try_get_geo_lon_lat(b"abc", b"0"), None);
}
#[test]
fn ticks_monotonic_and_large() {
let t = now_ticks();
assert!(t > 638_000_000_000_000_000);
assert!(now_ticks() >= t);
}
}