#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PgNumeric(pub String);
impl std::fmt::Display for PgNumeric {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl From<String> for PgNumeric {
fn from(s: String) -> Self {
Self(s)
}
}
impl PgNumeric {
pub fn new_unchecked(s: &str) -> Self {
Self(s.to_string())
}
}
impl TryFrom<&str> for PgNumeric {
type Error = String;
fn try_from(s: &str) -> Result<Self, Self::Error> {
let s = s.trim();
if s.is_empty() {
return Err("empty string is not a valid numeric".into());
}
if s == "NaN" || s == "Infinity" || s == "-Infinity" {
return Ok(Self(s.to_string()));
}
let bytes = s.as_bytes();
let mut i = 0;
if i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') {
i += 1;
}
let start = i;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
if i < bytes.len() && bytes[i] == b'.' {
i += 1;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
}
if i == start || (i == start + 1 && bytes[start] == b'.') {
return Err(format!("invalid numeric: {s:?}"));
}
if i < bytes.len() && (bytes[i] == b'e' || bytes[i] == b'E') {
i += 1;
if i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') {
i += 1;
}
let exp_start = i;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
if i == exp_start {
return Err(format!("invalid numeric exponent: {s:?}"));
}
}
if i != bytes.len() {
return Err(format!(
"invalid numeric: unexpected character at position {i} in {s:?}"
));
}
Ok(Self(s.to_string()))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PgInet(pub String);
impl std::fmt::Display for PgInet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl From<String> for PgInet {
fn from(s: String) -> Self {
Self(s)
}
}
impl PgInet {
pub fn new_unchecked(s: &str) -> Self {
Self(s.to_string())
}
}
impl TryFrom<&str> for PgInet {
type Error = String;
fn try_from(s: &str) -> Result<Self, Self::Error> {
let s = s.trim();
if s.is_empty() {
return Err("empty string is not a valid inet address".into());
}
let (addr_part, prefix_part) = if let Some((addr, prefix)) = s.rsplit_once('/') {
let prefix_len: u8 = prefix
.parse()
.map_err(|_| format!("invalid CIDR prefix length: {prefix:?}"))?;
if prefix_len > 128 {
return Err(format!(
"CIDR prefix length {prefix_len} exceeds maximum (128)"
));
}
(addr, Some(prefix_len))
} else {
(s, None)
};
if addr_part.parse::<std::net::IpAddr>().is_err() {
return Err(format!("invalid IP address: {addr_part:?}"));
}
if let Some(prefix_len) = prefix_part {
if addr_part.contains('.') && prefix_len > 32 {
return Err(format!(
"IPv4 CIDR prefix length {prefix_len} exceeds maximum (32)"
));
}
}
Ok(Self(s.to_string()))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum PgTimestamp {
Value(i64),
Infinity,
NegInfinity,
}
impl std::fmt::Display for PgTimestamp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Value(us) => write!(f, "{us}"),
Self::Infinity => write!(f, "infinity"),
Self::NegInfinity => write!(f, "-infinity"),
}
}
}
impl PgTimestamp {
pub fn is_infinity(&self) -> bool {
matches!(self, Self::Infinity | Self::NegInfinity)
}
pub fn is_finite(&self) -> bool {
matches!(self, Self::Value(_))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum PgDate {
Value(i32),
Infinity,
NegInfinity,
}
impl std::fmt::Display for PgDate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Value(d) => write!(f, "{d}"),
Self::Infinity => write!(f, "infinity"),
Self::NegInfinity => write!(f, "-infinity"),
}
}
}
impl PgDate {
pub fn is_infinity(&self) -> bool {
matches!(self, Self::Infinity | Self::NegInfinity)
}
pub fn is_finite(&self) -> bool {
matches!(self, Self::Value(_))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pg_numeric_valid() {
assert!(PgNumeric::try_from("0").is_ok());
assert!(PgNumeric::try_from("123").is_ok());
assert!(PgNumeric::try_from("-456").is_ok());
assert!(PgNumeric::try_from("3.14").is_ok());
assert!(PgNumeric::try_from("-0.001").is_ok());
assert!(PgNumeric::try_from(".5").is_ok());
assert!(PgNumeric::try_from("1e10").is_ok());
assert!(PgNumeric::try_from("2.5E-3").is_ok());
assert!(PgNumeric::try_from("+42").is_ok());
assert!(PgNumeric::try_from("NaN").is_ok());
assert!(PgNumeric::try_from("Infinity").is_ok());
assert!(PgNumeric::try_from("-Infinity").is_ok());
}
#[test]
fn test_pg_numeric_invalid() {
assert!(PgNumeric::try_from("").is_err());
assert!(PgNumeric::try_from("abc").is_err());
assert!(PgNumeric::try_from("12.34.56").is_err());
assert!(PgNumeric::try_from("1e").is_err());
assert!(PgNumeric::try_from(".").is_err());
assert!(PgNumeric::try_from("- 1").is_err());
}
#[test]
fn test_pg_inet_valid_ipv4() {
assert!(PgInet::try_from("192.168.1.1").is_ok());
assert!(PgInet::try_from("10.0.0.0/8").is_ok());
assert!(PgInet::try_from("0.0.0.0/0").is_ok());
assert!(PgInet::try_from("255.255.255.255/32").is_ok());
}
#[test]
fn test_pg_inet_valid_ipv6() {
assert!(PgInet::try_from("::1").is_ok());
assert!(PgInet::try_from("fe80::1/64").is_ok());
assert!(PgInet::try_from("2001:db8::1/128").is_ok());
}
#[test]
fn test_pg_inet_invalid() {
assert!(PgInet::try_from("").is_err());
assert!(PgInet::try_from("not an address").is_err());
assert!(PgInet::try_from("192.168.1.1/33").is_err()); assert!(PgInet::try_from("::1/129").is_err()); assert!(PgInet::try_from("192.168.1.1/abc").is_err());
}
}