pub mod numeric;
pub mod temporal;
pub use numeric::Numeric;
pub use temporal::{Date, Time, Timestamp};
use crate::protocol::types::{decode_json, decode_jsonb, decode_uuid, oid, try_decode_text_array};
#[derive(Debug, Clone)]
pub enum TypeError {
UnexpectedOid {
expected: &'static str,
got: u32,
},
InvalidData(String),
UnexpectedNull,
}
impl std::fmt::Display for TypeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TypeError::UnexpectedOid { expected, got } => {
write!(f, "Expected {} type, got OID {}", expected, got)
}
TypeError::InvalidData(msg) => write!(f, "Invalid data: {}", msg),
TypeError::UnexpectedNull => write!(f, "Unexpected NULL value"),
}
}
}
impl std::error::Error for TypeError {}
pub trait FromPg: Sized {
fn from_pg(bytes: &[u8], oid: u32, format: i16) -> Result<Self, TypeError>;
}
pub trait ToPg {
fn to_pg(&self) -> (Vec<u8>, u32, i16);
}
impl FromPg for String {
fn from_pg(bytes: &[u8], _oid: u32, _format: i16) -> Result<Self, TypeError> {
std::str::from_utf8(bytes)
.map(str::to_owned)
.map_err(|e| TypeError::InvalidData(format!("Invalid UTF-8: {}", e)))
}
}
impl ToPg for String {
fn to_pg(&self) -> (Vec<u8>, u32, i16) {
(self.as_bytes().to_vec(), oid::TEXT, 0)
}
}
impl ToPg for &str {
fn to_pg(&self) -> (Vec<u8>, u32, i16) {
(self.as_bytes().to_vec(), oid::TEXT, 0)
}
}
impl FromPg for i32 {
fn from_pg(bytes: &[u8], _oid: u32, format: i16) -> Result<Self, TypeError> {
if format == 1 {
if bytes.len() != 4 {
return Err(TypeError::InvalidData(
"Expected 4 bytes for i32".to_string(),
));
}
Ok(i32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
} else {
std::str::from_utf8(bytes)
.map_err(|e| TypeError::InvalidData(e.to_string()))?
.parse()
.map_err(|e| TypeError::InvalidData(format!("Invalid i32: {}", e)))
}
}
}
impl ToPg for i32 {
fn to_pg(&self) -> (Vec<u8>, u32, i16) {
(self.to_be_bytes().to_vec(), oid::INT4, 1)
}
}
impl FromPg for i64 {
fn from_pg(bytes: &[u8], _oid: u32, format: i16) -> Result<Self, TypeError> {
if format == 1 {
if bytes.len() != 8 {
return Err(TypeError::InvalidData(
"Expected 8 bytes for i64".to_string(),
));
}
Ok(i64::from_be_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
]))
} else {
std::str::from_utf8(bytes)
.map_err(|e| TypeError::InvalidData(e.to_string()))?
.parse()
.map_err(|e| TypeError::InvalidData(format!("Invalid i64: {}", e)))
}
}
}
impl ToPg for i64 {
fn to_pg(&self) -> (Vec<u8>, u32, i16) {
(self.to_be_bytes().to_vec(), oid::INT8, 1)
}
}
impl FromPg for f64 {
fn from_pg(bytes: &[u8], _oid: u32, format: i16) -> Result<Self, TypeError> {
if format == 1 {
if bytes.len() != 8 {
return Err(TypeError::InvalidData(
"Expected 8 bytes for f64".to_string(),
));
}
Ok(f64::from_be_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
]))
} else {
std::str::from_utf8(bytes)
.map_err(|e| TypeError::InvalidData(e.to_string()))?
.parse()
.map_err(|e| TypeError::InvalidData(format!("Invalid f64: {}", e)))
}
}
}
impl ToPg for f64 {
fn to_pg(&self) -> (Vec<u8>, u32, i16) {
(self.to_be_bytes().to_vec(), oid::FLOAT8, 1)
}
}
impl FromPg for bool {
fn from_pg(bytes: &[u8], _oid: u32, format: i16) -> Result<Self, TypeError> {
if format == 1 {
if bytes.len() != 1 {
return Err(TypeError::InvalidData(
"Expected 1 byte for boolean".to_string(),
));
}
match bytes[0] {
0 => Ok(false),
1 => Ok(true),
value => Err(TypeError::InvalidData(format!(
"Invalid binary boolean: {}",
value
))),
}
} else {
match std::str::from_utf8(bytes)
.map_err(|e| TypeError::InvalidData(e.to_string()))?
.trim()
{
"t" | "T" | "true" | "TRUE" | "1" => Ok(true),
"f" | "F" | "false" | "FALSE" | "0" => Ok(false),
_ => Err(TypeError::InvalidData("Invalid boolean".to_string())),
}
}
}
}
impl ToPg for bool {
fn to_pg(&self) -> (Vec<u8>, u32, i16) {
(vec![if *self { 1 } else { 0 }], oid::BOOL, 1)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Uuid(pub String);
impl FromPg for Uuid {
fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
if oid_val != oid::UUID {
return Err(TypeError::UnexpectedOid {
expected: "uuid",
got: oid_val,
});
}
if format == 1 {
if bytes.len() != 16 {
return Err(TypeError::InvalidData(
"Expected 16 bytes for UUID".to_string(),
));
}
decode_uuid(bytes).map(Uuid).map_err(TypeError::InvalidData)
} else {
std::str::from_utf8(bytes)
.map(str::to_owned)
.map(Uuid)
.map_err(|e| TypeError::InvalidData(e.to_string()))
}
}
}
impl ToPg for Uuid {
fn to_pg(&self) -> (Vec<u8>, u32, i16) {
(self.0.as_bytes().to_vec(), oid::UUID, 0)
}
}
#[cfg(feature = "uuid")]
impl FromPg for uuid::Uuid {
fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
let wrapped = Uuid::from_pg(bytes, oid_val, format)?;
uuid::Uuid::parse_str(&wrapped.0)
.map_err(|e| TypeError::InvalidData(format!("Invalid UUID: {}", e)))
}
}
#[cfg(feature = "uuid")]
impl ToPg for uuid::Uuid {
fn to_pg(&self) -> (Vec<u8>, u32, i16) {
(self.as_bytes().to_vec(), oid::UUID, 1)
}
}
fn from_utf8_string(bytes: &[u8]) -> Result<String, TypeError> {
std::str::from_utf8(bytes)
.map(|s| s.to_string())
.map_err(|e| TypeError::InvalidData(e.to_string()))
}
fn decode_inet_like_binary(bytes: &[u8], force_prefix: bool) -> Result<String, TypeError> {
if bytes.len() < 4 {
return Err(TypeError::InvalidData(
"inet/cidr binary payload too short".to_string(),
));
}
let family = bytes[0];
let bits = bytes[1];
let is_cidr = bytes[2];
let addr_len = bytes[3] as usize;
if bytes.len() != 4 + addr_len {
return Err(TypeError::InvalidData(
"inet/cidr binary payload length mismatch".to_string(),
));
}
if is_cidr > 1 {
return Err(TypeError::InvalidData(
"invalid inet/cidr is_cidr flag".to_string(),
));
}
let addr = &bytes[4..];
match family {
2 => {
if addr_len != 4 {
return Err(TypeError::InvalidData(
"invalid IPv4 inet/cidr address length".to_string(),
));
}
if bits > 32 {
return Err(TypeError::InvalidData(
"invalid IPv4 inet/cidr prefix length".to_string(),
));
}
let ip = std::net::Ipv4Addr::from([addr[0], addr[1], addr[2], addr[3]]);
let include_prefix = force_prefix || is_cidr != 0 || bits != 32;
if include_prefix {
Ok(format!("{}/{}", ip, bits))
} else {
Ok(ip.to_string())
}
}
3 => {
if addr_len != 16 {
return Err(TypeError::InvalidData(
"invalid IPv6 inet/cidr address length".to_string(),
));
}
if bits > 128 {
return Err(TypeError::InvalidData(
"invalid IPv6 inet/cidr prefix length".to_string(),
));
}
let mut full = [0u8; 16];
full.copy_from_slice(addr);
let ip = std::net::Ipv6Addr::from(full);
let include_prefix = force_prefix || is_cidr != 0 || bits != 128;
if include_prefix {
Ok(format!("{}/{}", ip, bits))
} else {
Ok(ip.to_string())
}
}
_ => Err(TypeError::InvalidData(format!(
"unsupported inet/cidr address family: {}",
family
))),
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Inet(pub String);
impl Inet {
pub fn new(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl FromPg for Inet {
fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
if oid_val != oid::INET {
return Err(TypeError::UnexpectedOid {
expected: "inet",
got: oid_val,
});
}
let s = if format == 1 {
decode_inet_like_binary(bytes, false)?
} else {
from_utf8_string(bytes)?
};
Ok(Inet(s))
}
}
impl ToPg for Inet {
fn to_pg(&self) -> (Vec<u8>, u32, i16) {
(self.0.as_bytes().to_vec(), oid::INET, 0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Cidr(pub String);
impl Cidr {
pub fn new(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl FromPg for Cidr {
fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
if oid_val != oid::CIDR {
return Err(TypeError::UnexpectedOid {
expected: "cidr",
got: oid_val,
});
}
let s = if format == 1 {
decode_inet_like_binary(bytes, true)?
} else {
from_utf8_string(bytes)?
};
Ok(Cidr(s))
}
}
impl ToPg for Cidr {
fn to_pg(&self) -> (Vec<u8>, u32, i16) {
(self.0.as_bytes().to_vec(), oid::CIDR, 0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MacAddr(pub String);
impl MacAddr {
pub fn new(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl FromPg for MacAddr {
fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
if oid_val != oid::MACADDR {
return Err(TypeError::UnexpectedOid {
expected: "macaddr",
got: oid_val,
});
}
let s = if format == 1 {
if bytes.len() != 6 {
return Err(TypeError::InvalidData(
"Expected 6 bytes for macaddr".to_string(),
));
}
format!(
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5]
)
} else {
from_utf8_string(bytes)?
};
Ok(MacAddr(s))
}
}
impl ToPg for MacAddr {
fn to_pg(&self) -> (Vec<u8>, u32, i16) {
(self.0.as_bytes().to_vec(), oid::MACADDR, 0)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Json(pub String);
impl FromPg for Json {
fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
let json_str = match (oid_val, format) {
(oid::JSON, _) | (oid::JSONB, 0) => {
decode_json(bytes).map_err(TypeError::InvalidData)?
}
(oid::JSONB, 1) => decode_jsonb(bytes).map_err(TypeError::InvalidData)?,
(oid::JSONB, other) => {
return Err(TypeError::InvalidData(format!(
"Unsupported JSONB format code: {}",
other
)));
}
_ => {
return Err(TypeError::UnexpectedOid {
expected: "json/jsonb",
got: oid_val,
});
}
};
Ok(Json(json_str))
}
}
impl ToPg for Json {
fn to_pg(&self) -> (Vec<u8>, u32, i16) {
let mut buf = Vec::with_capacity(1 + self.0.len());
buf.push(1); buf.extend_from_slice(self.0.as_bytes());
(buf, oid::JSONB, 1)
}
}
impl FromPg for Vec<String> {
fn from_pg(bytes: &[u8], _oid: u32, _format: i16) -> Result<Self, TypeError> {
let s = std::str::from_utf8(bytes).map_err(|e| TypeError::InvalidData(e.to_string()))?;
try_decode_text_array(s).map_err(TypeError::InvalidData)
}
}
impl FromPg for Vec<i64> {
fn from_pg(bytes: &[u8], _oid: u32, _format: i16) -> Result<Self, TypeError> {
let s = std::str::from_utf8(bytes).map_err(|e| TypeError::InvalidData(e.to_string()))?;
crate::protocol::types::decode_int_array(s).map_err(TypeError::InvalidData)
}
}
impl<T: FromPg> FromPg for Option<T> {
fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
Ok(Some(T::from_pg(bytes, oid_val, format)?))
}
}
impl FromPg for Vec<u8> {
fn from_pg(bytes: &[u8], _oid: u32, _format: i16) -> Result<Self, TypeError> {
Ok(bytes.to_vec())
}
}
impl ToPg for Vec<u8> {
fn to_pg(&self) -> (Vec<u8>, u32, i16) {
(self.clone(), oid::BYTEA, 1)
}
}
impl ToPg for &[u8] {
fn to_pg(&self) -> (Vec<u8>, u32, i16) {
(self.to_vec(), oid::BYTEA, 1)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_string_from_pg() {
let result = String::from_pg(b"hello", oid::TEXT, 0).unwrap();
assert_eq!(result, "hello");
}
#[test]
fn test_i32_from_pg_text() {
let result = i32::from_pg(b"42", oid::INT4, 0).unwrap();
assert_eq!(result, 42);
}
#[test]
fn test_i32_from_pg_binary() {
let bytes = 42i32.to_be_bytes();
let result = i32::from_pg(&bytes, oid::INT4, 1).unwrap();
assert_eq!(result, 42);
}
#[test]
fn test_bool_from_pg() {
assert!(bool::from_pg(b"t", oid::BOOL, 0).unwrap());
assert!(!bool::from_pg(b"f", oid::BOOL, 0).unwrap());
assert!(bool::from_pg(&[1], oid::BOOL, 1).unwrap());
assert!(!bool::from_pg(&[0], oid::BOOL, 1).unwrap());
}
#[test]
fn test_bool_from_pg_rejects_malformed_values() {
assert!(bool::from_pg(&[], oid::BOOL, 1).is_err());
assert!(bool::from_pg(&[2], oid::BOOL, 1).is_err());
assert!(bool::from_pg(b"trash", oid::BOOL, 0).is_err());
assert!(bool::from_pg(b"falsey", oid::BOOL, 0).is_err());
}
#[test]
fn test_uuid_from_pg_binary() {
let uuid_bytes: [u8; 16] = [
0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44,
0x00, 0x00,
];
let result = Uuid::from_pg(&uuid_bytes, oid::UUID, 1).unwrap();
assert_eq!(result.0, "550e8400-e29b-41d4-a716-446655440000");
}
#[test]
fn test_uuid_from_pg_binary_rejects_bad_length() {
let err = Uuid::from_pg(b"550e8400-e29b-41d4-a716-446655440000", oid::UUID, 1).unwrap_err();
assert!(matches!(err, TypeError::InvalidData(msg) if msg.contains("16 bytes")));
}
#[cfg(feature = "uuid")]
#[test]
fn test_std_uuid_from_pg_binary() {
let uuid_bytes: [u8; 16] = [
0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44,
0x00, 0x00,
];
let result = uuid::Uuid::from_pg(&uuid_bytes, oid::UUID, 1).unwrap();
assert_eq!(result.to_string(), "550e8400-e29b-41d4-a716-446655440000");
}
#[test]
fn test_inet_from_pg_text() {
let inet = Inet::from_pg(b"10.0.0.1", oid::INET, 0).unwrap();
assert_eq!(inet.0, "10.0.0.1");
}
#[test]
fn test_inet_from_pg_binary_ipv4() {
let bytes = [2u8, 32, 0, 4, 10, 1, 2, 3];
let inet = Inet::from_pg(&bytes, oid::INET, 1).unwrap();
assert_eq!(inet.0, "10.1.2.3");
}
#[test]
fn test_cidr_from_pg_binary_ipv4() {
let bytes = [2u8, 24, 1, 4, 192, 168, 1, 0];
let cidr = Cidr::from_pg(&bytes, oid::CIDR, 1).unwrap();
assert_eq!(cidr.0, "192.168.1.0/24");
}
#[test]
fn test_network_types_reject_malformed_binary_payloads() {
assert!(Inet::from_pg(&[2u8, 33, 0, 4, 10, 1, 2, 3], oid::INET, 1).is_err());
assert!(Inet::from_pg(&[2u8, 32, 0, 1, 10], oid::INET, 1).is_err());
assert!(Inet::from_pg(&[2u8, 32, 2, 4, 10, 1, 2, 3], oid::INET, 1).is_err());
let mut ipv6 = vec![3u8, 129, 0, 16];
ipv6.extend_from_slice(&[0u8; 16]);
assert!(Inet::from_pg(&ipv6, oid::INET, 1).is_err());
}
#[test]
fn test_macaddr_from_pg_binary() {
let bytes = [0x08u8, 0x00, 0x2b, 0x01, 0x02, 0x03];
let mac = MacAddr::from_pg(&bytes, oid::MACADDR, 1).unwrap();
assert_eq!(mac.0, "08:00:2b:01:02:03");
}
#[test]
fn test_network_types_to_pg_oids() {
let inet = Inet::new("10.0.0.0/8");
let (_, inet_oid, inet_format) = inet.to_pg();
assert_eq!(inet_oid, oid::INET);
assert_eq!(inet_format, 0);
let cidr = Cidr::new("10.0.0.0/8");
let (_, cidr_oid, cidr_format) = cidr.to_pg();
assert_eq!(cidr_oid, oid::CIDR);
assert_eq!(cidr_format, 0);
let mac = MacAddr::new("08:00:2b:01:02:03");
let (_, mac_oid, mac_format) = mac.to_pg();
assert_eq!(mac_oid, oid::MACADDR);
assert_eq!(mac_format, 0);
}
#[test]
fn test_json_from_pg_honors_oid_and_format() {
let json = Json::from_pg(br#"{"ok":true}"#, oid::JSON, 0).unwrap();
assert_eq!(json.0, r#"{"ok":true}"#);
let jsonb_text = Json::from_pg(br#"{"ok":true}"#, oid::JSONB, 0).unwrap();
assert_eq!(jsonb_text.0, r#"{"ok":true}"#);
let jsonb_binary = Json::from_pg(&[1, b'{', b'}'], oid::JSONB, 1).unwrap();
assert_eq!(jsonb_binary.0, "{}");
assert!(Json::from_pg(&[], oid::JSONB, 1).is_err());
assert!(Json::from_pg(b"42", oid::INT4, 0).is_err());
}
}