Skip to main content

qail_pg/types/
mod.rs

1//! Type conversion traits and implementations for PostgreSQL types.
2//!
3//! This module provides traits for converting Rust types to/from PostgreSQL wire format.
4
5pub mod numeric;
6pub mod temporal;
7
8pub use numeric::Numeric;
9pub use temporal::{Date, Time, Timestamp};
10
11use crate::protocol::types::{decode_json, decode_jsonb, decode_uuid, oid, try_decode_text_array};
12
13/// Error type for type conversion failures.
14#[derive(Debug, Clone)]
15pub enum TypeError {
16    /// Wrong OID for expected type
17    UnexpectedOid {
18        /// Human-readable name of the expected type (e.g. `"uuid"`).
19        expected: &'static str,
20        /// Actual OID received from the server.
21        got: u32,
22    },
23    /// Invalid binary data
24    InvalidData(String),
25    /// Null value where non-null expected
26    UnexpectedNull,
27}
28
29impl std::fmt::Display for TypeError {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        match self {
32            TypeError::UnexpectedOid { expected, got } => {
33                write!(f, "Expected {} type, got OID {}", expected, got)
34            }
35            TypeError::InvalidData(msg) => write!(f, "Invalid data: {}", msg),
36            TypeError::UnexpectedNull => write!(f, "Unexpected NULL value"),
37        }
38    }
39}
40
41impl std::error::Error for TypeError {}
42
43/// Trait for converting PostgreSQL binary/text data to Rust types.
44pub trait FromPg: Sized {
45    /// Convert from PostgreSQL wire format.
46    /// # Arguments
47    /// * `bytes` - Raw bytes from PostgreSQL (may be text or binary format)
48    /// * `oid` - PostgreSQL type OID
49    /// * `format` - 0 = text, 1 = binary
50    fn from_pg(bytes: &[u8], oid: u32, format: i16) -> Result<Self, TypeError>;
51}
52
53/// Trait for converting Rust types to PostgreSQL wire format.
54pub trait ToPg {
55    /// Convert to PostgreSQL wire format.
56    /// Returns (bytes, oid, format_code)
57    fn to_pg(&self) -> (Vec<u8>, u32, i16);
58}
59
60// ==================== String Types ====================
61
62impl FromPg for String {
63    fn from_pg(bytes: &[u8], _oid: u32, _format: i16) -> Result<Self, TypeError> {
64        std::str::from_utf8(bytes)
65            .map(str::to_owned)
66            .map_err(|e| TypeError::InvalidData(format!("Invalid UTF-8: {}", e)))
67    }
68}
69
70impl ToPg for String {
71    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
72        (self.as_bytes().to_vec(), oid::TEXT, 0)
73    }
74}
75
76impl ToPg for &str {
77    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
78        (self.as_bytes().to_vec(), oid::TEXT, 0)
79    }
80}
81
82// ==================== Integer Types ====================
83
84impl FromPg for i32 {
85    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
86        if format == 1 {
87            match oid_val {
88                oid::INT2 => {
89                    if bytes.len() != 2 {
90                        return Err(TypeError::InvalidData(
91                            "Expected 2 bytes for int2".to_string(),
92                        ));
93                    }
94                    Ok(i16::from_be_bytes([bytes[0], bytes[1]]) as i32)
95                }
96                oid::INT4 => {
97                    if bytes.len() != 4 {
98                        return Err(TypeError::InvalidData(
99                            "Expected 4 bytes for int4".to_string(),
100                        ));
101                    }
102                    Ok(i32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
103                }
104                _ => Err(TypeError::UnexpectedOid {
105                    expected: "int2/int4",
106                    got: oid_val,
107                }),
108            }
109        } else {
110            // Text format
111            std::str::from_utf8(bytes)
112                .map_err(|e| TypeError::InvalidData(e.to_string()))?
113                .parse()
114                .map_err(|e| TypeError::InvalidData(format!("Invalid i32: {}", e)))
115        }
116    }
117}
118
119impl ToPg for i32 {
120    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
121        (self.to_be_bytes().to_vec(), oid::INT4, 1)
122    }
123}
124
125impl FromPg for i64 {
126    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
127        if format == 1 {
128            match oid_val {
129                oid::INT2 => {
130                    if bytes.len() != 2 {
131                        return Err(TypeError::InvalidData(
132                            "Expected 2 bytes for int2".to_string(),
133                        ));
134                    }
135                    Ok(i16::from_be_bytes([bytes[0], bytes[1]]) as i64)
136                }
137                oid::INT4 => {
138                    if bytes.len() != 4 {
139                        return Err(TypeError::InvalidData(
140                            "Expected 4 bytes for int4".to_string(),
141                        ));
142                    }
143                    Ok(i32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as i64)
144                }
145                oid::INT8 => {
146                    if bytes.len() != 8 {
147                        return Err(TypeError::InvalidData(
148                            "Expected 8 bytes for int8".to_string(),
149                        ));
150                    }
151                    Ok(i64::from_be_bytes([
152                        bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6],
153                        bytes[7],
154                    ]))
155                }
156                _ => Err(TypeError::UnexpectedOid {
157                    expected: "int2/int4/int8",
158                    got: oid_val,
159                }),
160            }
161        } else {
162            // Text format
163            std::str::from_utf8(bytes)
164                .map_err(|e| TypeError::InvalidData(e.to_string()))?
165                .parse()
166                .map_err(|e| TypeError::InvalidData(format!("Invalid i64: {}", e)))
167        }
168    }
169}
170
171impl ToPg for i64 {
172    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
173        (self.to_be_bytes().to_vec(), oid::INT8, 1)
174    }
175}
176
177// ==================== Float Types ====================
178
179impl FromPg for f64 {
180    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
181        if format == 1 {
182            match oid_val {
183                oid::FLOAT4 => {
184                    if bytes.len() != 4 {
185                        return Err(TypeError::InvalidData(
186                            "Expected 4 bytes for float4".to_string(),
187                        ));
188                    }
189                    Ok(f32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as f64)
190                }
191                oid::FLOAT8 => {
192                    if bytes.len() != 8 {
193                        return Err(TypeError::InvalidData(
194                            "Expected 8 bytes for float8".to_string(),
195                        ));
196                    }
197                    Ok(f64::from_be_bytes([
198                        bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6],
199                        bytes[7],
200                    ]))
201                }
202                _ => Err(TypeError::UnexpectedOid {
203                    expected: "float4/float8",
204                    got: oid_val,
205                }),
206            }
207        } else {
208            // Text format
209            std::str::from_utf8(bytes)
210                .map_err(|e| TypeError::InvalidData(e.to_string()))?
211                .parse()
212                .map_err(|e| TypeError::InvalidData(format!("Invalid f64: {}", e)))
213        }
214    }
215}
216
217impl ToPg for f64 {
218    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
219        (self.to_be_bytes().to_vec(), oid::FLOAT8, 1)
220    }
221}
222
223// ==================== Boolean ====================
224
225impl FromPg for bool {
226    fn from_pg(bytes: &[u8], _oid: u32, format: i16) -> Result<Self, TypeError> {
227        if format == 1 {
228            if bytes.len() != 1 {
229                return Err(TypeError::InvalidData(
230                    "Expected 1 byte for boolean".to_string(),
231                ));
232            }
233            match bytes[0] {
234                0 => Ok(false),
235                1 => Ok(true),
236                value => Err(TypeError::InvalidData(format!(
237                    "Invalid binary boolean: {}",
238                    value
239                ))),
240            }
241        } else {
242            // Text: 't' or 'f'
243            match std::str::from_utf8(bytes)
244                .map_err(|e| TypeError::InvalidData(e.to_string()))?
245                .trim()
246            {
247                "t" | "T" | "true" | "TRUE" | "1" => Ok(true),
248                "f" | "F" | "false" | "FALSE" | "0" => Ok(false),
249                _ => Err(TypeError::InvalidData("Invalid boolean".to_string())),
250            }
251        }
252    }
253}
254
255impl ToPg for bool {
256    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
257        (vec![if *self { 1 } else { 0 }], oid::BOOL, 1)
258    }
259}
260
261// ==================== UUID ====================
262
263/// UUID type (uses String internally for simplicity)
264#[derive(Debug, Clone, PartialEq)]
265pub struct Uuid(pub String);
266
267impl FromPg for Uuid {
268    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
269        if oid_val != oid::UUID {
270            return Err(TypeError::UnexpectedOid {
271                expected: "uuid",
272                got: oid_val,
273            });
274        }
275
276        if format == 1 {
277            if bytes.len() != 16 {
278                return Err(TypeError::InvalidData(
279                    "Expected 16 bytes for UUID".to_string(),
280                ));
281            }
282            decode_uuid(bytes).map(Uuid).map_err(TypeError::InvalidData)
283        } else {
284            // Text format
285            std::str::from_utf8(bytes)
286                .map(str::to_owned)
287                .map(Uuid)
288                .map_err(|e| TypeError::InvalidData(e.to_string()))
289        }
290    }
291}
292
293impl ToPg for Uuid {
294    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
295        // Send as text for simplicity
296        (self.0.as_bytes().to_vec(), oid::UUID, 0)
297    }
298}
299
300#[cfg(feature = "uuid")]
301impl FromPg for uuid::Uuid {
302    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
303        let wrapped = Uuid::from_pg(bytes, oid_val, format)?;
304        uuid::Uuid::parse_str(&wrapped.0)
305            .map_err(|e| TypeError::InvalidData(format!("Invalid UUID: {}", e)))
306    }
307}
308
309#[cfg(feature = "uuid")]
310impl ToPg for uuid::Uuid {
311    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
312        (self.as_bytes().to_vec(), oid::UUID, 1)
313    }
314}
315
316// ==================== Network Types ====================
317
318fn from_utf8_string(bytes: &[u8]) -> Result<String, TypeError> {
319    std::str::from_utf8(bytes)
320        .map(|s| s.to_string())
321        .map_err(|e| TypeError::InvalidData(e.to_string()))
322}
323
324fn decode_inet_like_binary(bytes: &[u8], force_prefix: bool) -> Result<String, TypeError> {
325    // inet/cidr binary format:
326    // 1 byte family (2 = IPv4, 3 = IPv6)
327    // 1 byte bits (netmask length)
328    // 1 byte is_cidr (0 = inet, 1 = cidr)
329    // 1 byte addr_len
330    // N bytes address
331    if bytes.len() < 4 {
332        return Err(TypeError::InvalidData(
333            "inet/cidr binary payload too short".to_string(),
334        ));
335    }
336
337    let family = bytes[0];
338    let bits = bytes[1];
339    let is_cidr = bytes[2];
340    let addr_len = bytes[3] as usize;
341
342    if bytes.len() != 4 + addr_len {
343        return Err(TypeError::InvalidData(
344            "inet/cidr binary payload length mismatch".to_string(),
345        ));
346    }
347    if is_cidr > 1 {
348        return Err(TypeError::InvalidData(
349            "invalid inet/cidr is_cidr flag".to_string(),
350        ));
351    }
352
353    let addr = &bytes[4..];
354    match family {
355        2 => {
356            if addr_len != 4 {
357                return Err(TypeError::InvalidData(
358                    "invalid IPv4 inet/cidr address length".to_string(),
359                ));
360            }
361            if bits > 32 {
362                return Err(TypeError::InvalidData(
363                    "invalid IPv4 inet/cidr prefix length".to_string(),
364                ));
365            }
366            let ip = std::net::Ipv4Addr::from([addr[0], addr[1], addr[2], addr[3]]);
367            let include_prefix = force_prefix || is_cidr != 0 || bits != 32;
368            if include_prefix {
369                Ok(format!("{}/{}", ip, bits))
370            } else {
371                Ok(ip.to_string())
372            }
373        }
374        3 => {
375            if addr_len != 16 {
376                return Err(TypeError::InvalidData(
377                    "invalid IPv6 inet/cidr address length".to_string(),
378                ));
379            }
380            if bits > 128 {
381                return Err(TypeError::InvalidData(
382                    "invalid IPv6 inet/cidr prefix length".to_string(),
383                ));
384            }
385            let mut full = [0u8; 16];
386            full.copy_from_slice(addr);
387            let ip = std::net::Ipv6Addr::from(full);
388            let include_prefix = force_prefix || is_cidr != 0 || bits != 128;
389            if include_prefix {
390                Ok(format!("{}/{}", ip, bits))
391            } else {
392                Ok(ip.to_string())
393            }
394        }
395        _ => Err(TypeError::InvalidData(format!(
396            "unsupported inet/cidr address family: {}",
397            family
398        ))),
399    }
400}
401
402/// IPv4/IPv6 host/network address (`inet`)
403#[derive(Debug, Clone, PartialEq, Eq)]
404pub struct Inet(pub String);
405
406impl Inet {
407    /// Create a new `Inet` value from text representation.
408    pub fn new(s: impl Into<String>) -> Self {
409        Self(s.into())
410    }
411
412    /// Borrow the underlying textual representation.
413    pub fn as_str(&self) -> &str {
414        &self.0
415    }
416}
417
418impl FromPg for Inet {
419    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
420        if oid_val != oid::INET {
421            return Err(TypeError::UnexpectedOid {
422                expected: "inet",
423                got: oid_val,
424            });
425        }
426
427        let s = if format == 1 {
428            decode_inet_like_binary(bytes, false)?
429        } else {
430            from_utf8_string(bytes)?
431        };
432        Ok(Inet(s))
433    }
434}
435
436impl ToPg for Inet {
437    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
438        (self.0.as_bytes().to_vec(), oid::INET, 0)
439    }
440}
441
442/// IPv4/IPv6 network block (`cidr`)
443#[derive(Debug, Clone, PartialEq, Eq)]
444pub struct Cidr(pub String);
445
446impl Cidr {
447    /// Create a new `Cidr` value from text representation.
448    pub fn new(s: impl Into<String>) -> Self {
449        Self(s.into())
450    }
451
452    /// Borrow the underlying textual representation.
453    pub fn as_str(&self) -> &str {
454        &self.0
455    }
456}
457
458impl FromPg for Cidr {
459    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
460        if oid_val != oid::CIDR {
461            return Err(TypeError::UnexpectedOid {
462                expected: "cidr",
463                got: oid_val,
464            });
465        }
466
467        let s = if format == 1 {
468            decode_inet_like_binary(bytes, true)?
469        } else {
470            from_utf8_string(bytes)?
471        };
472        Ok(Cidr(s))
473    }
474}
475
476impl ToPg for Cidr {
477    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
478        (self.0.as_bytes().to_vec(), oid::CIDR, 0)
479    }
480}
481
482/// MAC address (`macaddr`)
483#[derive(Debug, Clone, PartialEq, Eq)]
484pub struct MacAddr(pub String);
485
486impl MacAddr {
487    /// Create a new `MacAddr` value from text representation.
488    pub fn new(s: impl Into<String>) -> Self {
489        Self(s.into())
490    }
491
492    /// Borrow the underlying textual representation.
493    pub fn as_str(&self) -> &str {
494        &self.0
495    }
496}
497
498impl FromPg for MacAddr {
499    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
500        if oid_val != oid::MACADDR {
501            return Err(TypeError::UnexpectedOid {
502                expected: "macaddr",
503                got: oid_val,
504            });
505        }
506
507        let s = if format == 1 {
508            if bytes.len() != 6 {
509                return Err(TypeError::InvalidData(
510                    "Expected 6 bytes for macaddr".to_string(),
511                ));
512            }
513            format!(
514                "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
515                bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5]
516            )
517        } else {
518            from_utf8_string(bytes)?
519        };
520
521        Ok(MacAddr(s))
522    }
523}
524
525impl ToPg for MacAddr {
526    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
527        (self.0.as_bytes().to_vec(), oid::MACADDR, 0)
528    }
529}
530
531// ==================== JSON/JSONB ====================
532
533/// JSON value (wraps the raw JSON string)
534#[derive(Debug, Clone, PartialEq)]
535pub struct Json(pub String);
536
537impl FromPg for Json {
538    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
539        let json_str = match (oid_val, format) {
540            (oid::JSON, _) | (oid::JSONB, 0) => {
541                decode_json(bytes).map_err(TypeError::InvalidData)?
542            }
543            (oid::JSONB, 1) => decode_jsonb(bytes).map_err(TypeError::InvalidData)?,
544            (oid::JSONB, other) => {
545                return Err(TypeError::InvalidData(format!(
546                    "Unsupported JSONB format code: {}",
547                    other
548                )));
549            }
550            _ => {
551                return Err(TypeError::UnexpectedOid {
552                    expected: "json/jsonb",
553                    got: oid_val,
554                });
555            }
556        };
557        Ok(Json(json_str))
558    }
559}
560
561impl ToPg for Json {
562    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
563        // Send as JSONB with version byte
564        let mut buf = Vec::with_capacity(1 + self.0.len());
565        buf.push(1); // JSONB version
566        buf.extend_from_slice(self.0.as_bytes());
567        (buf, oid::JSONB, 1)
568    }
569}
570
571// ==================== Arrays ====================
572
573impl FromPg for Vec<String> {
574    fn from_pg(bytes: &[u8], _oid: u32, format: i16) -> Result<Self, TypeError> {
575        if format == 1 {
576            return Err(TypeError::InvalidData(
577                "binary array decoding is not supported for Vec<String>".to_string(),
578            ));
579        }
580        let s = std::str::from_utf8(bytes).map_err(|e| TypeError::InvalidData(e.to_string()))?;
581        try_decode_text_array(s).map_err(TypeError::InvalidData)
582    }
583}
584
585impl FromPg for Vec<i64> {
586    fn from_pg(bytes: &[u8], _oid: u32, format: i16) -> Result<Self, TypeError> {
587        if format == 1 {
588            return Err(TypeError::InvalidData(
589                "binary array decoding is not supported for Vec<i64>".to_string(),
590            ));
591        }
592        let s = std::str::from_utf8(bytes).map_err(|e| TypeError::InvalidData(e.to_string()))?;
593        crate::protocol::types::decode_int_array(s).map_err(TypeError::InvalidData)
594    }
595}
596
597// ==================== Option<T> ====================
598
599impl<T: FromPg> FromPg for Option<T> {
600    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
601        // This is for non-null; actual NULL handling is done at row level
602        Ok(Some(T::from_pg(bytes, oid_val, format)?))
603    }
604}
605
606// ==================== Bytes ====================
607
608fn decode_bytea_hex_text(bytes: &[u8]) -> Result<Vec<u8>, TypeError> {
609    if !bytes.starts_with(br"\x") {
610        return Ok(bytes.to_vec());
611    }
612
613    let hex = &bytes[2..];
614    if !hex.len().is_multiple_of(2) {
615        return Err(TypeError::InvalidData(
616            "Invalid bytea hex text length".to_string(),
617        ));
618    }
619
620    let mut out = Vec::with_capacity(hex.len() / 2);
621    for pair in hex.chunks_exact(2) {
622        let hi = decode_hex_nibble(pair[0])?;
623        let lo = decode_hex_nibble(pair[1])?;
624        out.push((hi << 4) | lo);
625    }
626    Ok(out)
627}
628
629fn decode_hex_nibble(byte: u8) -> Result<u8, TypeError> {
630    match byte {
631        b'0'..=b'9' => Ok(byte - b'0'),
632        b'a'..=b'f' => Ok(byte - b'a' + 10),
633        b'A'..=b'F' => Ok(byte - b'A' + 10),
634        _ => Err(TypeError::InvalidData(format!(
635            "Invalid bytea hex digit: {}",
636            byte as char
637        ))),
638    }
639}
640
641impl FromPg for Vec<u8> {
642    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
643        if format == 0 && oid_val == oid::BYTEA {
644            return decode_bytea_hex_text(bytes);
645        }
646        Ok(bytes.to_vec())
647    }
648}
649
650impl ToPg for Vec<u8> {
651    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
652        (self.clone(), oid::BYTEA, 1)
653    }
654}
655
656impl ToPg for &[u8] {
657    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
658        (self.to_vec(), oid::BYTEA, 1)
659    }
660}
661
662#[cfg(test)]
663mod tests {
664    use super::*;
665
666    #[test]
667    fn test_string_from_pg() {
668        let result = String::from_pg(b"hello", oid::TEXT, 0).unwrap();
669        assert_eq!(result, "hello");
670    }
671
672    #[test]
673    fn test_i32_from_pg_text() {
674        let result = i32::from_pg(b"42", oid::INT4, 0).unwrap();
675        assert_eq!(result, 42);
676    }
677
678    #[test]
679    fn test_i32_from_pg_binary() {
680        let bytes = 42i32.to_be_bytes();
681        let result = i32::from_pg(&bytes, oid::INT4, 1).unwrap();
682        assert_eq!(result, 42);
683    }
684
685    #[test]
686    fn test_i32_from_pg_binary_accepts_int2_oid() {
687        let bytes = 123i16.to_be_bytes();
688        let result = i32::from_pg(&bytes, oid::INT2, 1).unwrap();
689        assert_eq!(result, 123);
690    }
691
692    #[test]
693    fn test_i64_from_pg_binary_accepts_int4_oid() {
694        let bytes = 123_456i32.to_be_bytes();
695        let result = i64::from_pg(&bytes, oid::INT4, 1).unwrap();
696        assert_eq!(result, 123_456);
697    }
698
699    #[test]
700    fn test_f64_from_pg_binary_accepts_float4_oid() {
701        let bytes = 1.5f32.to_be_bytes();
702        let result = f64::from_pg(&bytes, oid::FLOAT4, 1).unwrap();
703        assert_eq!(result, 1.5);
704    }
705
706    #[test]
707    fn test_bool_from_pg() {
708        assert!(bool::from_pg(b"t", oid::BOOL, 0).unwrap());
709        assert!(!bool::from_pg(b"f", oid::BOOL, 0).unwrap());
710        assert!(bool::from_pg(&[1], oid::BOOL, 1).unwrap());
711        assert!(!bool::from_pg(&[0], oid::BOOL, 1).unwrap());
712    }
713
714    #[test]
715    fn test_bool_from_pg_rejects_malformed_values() {
716        assert!(bool::from_pg(&[], oid::BOOL, 1).is_err());
717        assert!(bool::from_pg(&[2], oid::BOOL, 1).is_err());
718        assert!(bool::from_pg(b"trash", oid::BOOL, 0).is_err());
719        assert!(bool::from_pg(b"falsey", oid::BOOL, 0).is_err());
720    }
721
722    #[test]
723    fn test_uuid_from_pg_binary() {
724        let uuid_bytes: [u8; 16] = [
725            0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44,
726            0x00, 0x00,
727        ];
728        let result = Uuid::from_pg(&uuid_bytes, oid::UUID, 1).unwrap();
729        assert_eq!(result.0, "550e8400-e29b-41d4-a716-446655440000");
730    }
731
732    #[test]
733    fn test_uuid_from_pg_binary_rejects_bad_length() {
734        let err = Uuid::from_pg(b"550e8400-e29b-41d4-a716-446655440000", oid::UUID, 1).unwrap_err();
735
736        assert!(matches!(err, TypeError::InvalidData(msg) if msg.contains("16 bytes")));
737    }
738
739    #[cfg(feature = "uuid")]
740    #[test]
741    fn test_std_uuid_from_pg_binary() {
742        let uuid_bytes: [u8; 16] = [
743            0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44,
744            0x00, 0x00,
745        ];
746        let result = uuid::Uuid::from_pg(&uuid_bytes, oid::UUID, 1).unwrap();
747        assert_eq!(result.to_string(), "550e8400-e29b-41d4-a716-446655440000");
748    }
749
750    #[test]
751    fn test_inet_from_pg_text() {
752        let inet = Inet::from_pg(b"10.0.0.1", oid::INET, 0).unwrap();
753        assert_eq!(inet.0, "10.0.0.1");
754    }
755
756    #[test]
757    fn test_inet_from_pg_binary_ipv4() {
758        // family=2 (IPv4), bits=32, is_cidr=0, addr_len=4, addr=10.1.2.3
759        let bytes = [2u8, 32, 0, 4, 10, 1, 2, 3];
760        let inet = Inet::from_pg(&bytes, oid::INET, 1).unwrap();
761        assert_eq!(inet.0, "10.1.2.3");
762    }
763
764    #[test]
765    fn test_bytea_text_hex_decodes_to_bytes() {
766        let bytes = Vec::<u8>::from_pg(br"\x000102ff", oid::BYTEA, 0).unwrap();
767        assert_eq!(bytes, vec![0, 1, 2, 255]);
768    }
769
770    #[test]
771    fn test_binary_arrays_return_explicit_error() {
772        assert!(Vec::<String>::from_pg(b"\0\0\0\0", oid::TEXT_ARRAY, 1).is_err());
773        assert!(Vec::<i64>::from_pg(b"\0\0\0\0", oid::INT8_ARRAY, 1).is_err());
774    }
775
776    #[test]
777    fn test_cidr_from_pg_binary_ipv4() {
778        // family=2 (IPv4), bits=24, is_cidr=1, addr_len=4, addr=192.168.1.0
779        let bytes = [2u8, 24, 1, 4, 192, 168, 1, 0];
780        let cidr = Cidr::from_pg(&bytes, oid::CIDR, 1).unwrap();
781        assert_eq!(cidr.0, "192.168.1.0/24");
782    }
783
784    #[test]
785    fn test_network_types_reject_malformed_binary_payloads() {
786        assert!(Inet::from_pg(&[2u8, 33, 0, 4, 10, 1, 2, 3], oid::INET, 1).is_err());
787        assert!(Inet::from_pg(&[2u8, 32, 0, 1, 10], oid::INET, 1).is_err());
788        assert!(Inet::from_pg(&[2u8, 32, 2, 4, 10, 1, 2, 3], oid::INET, 1).is_err());
789
790        let mut ipv6 = vec![3u8, 129, 0, 16];
791        ipv6.extend_from_slice(&[0u8; 16]);
792        assert!(Inet::from_pg(&ipv6, oid::INET, 1).is_err());
793    }
794
795    #[test]
796    fn test_macaddr_from_pg_binary() {
797        let bytes = [0x08u8, 0x00, 0x2b, 0x01, 0x02, 0x03];
798        let mac = MacAddr::from_pg(&bytes, oid::MACADDR, 1).unwrap();
799        assert_eq!(mac.0, "08:00:2b:01:02:03");
800    }
801
802    #[test]
803    fn test_network_types_to_pg_oids() {
804        let inet = Inet::new("10.0.0.0/8");
805        let (_, inet_oid, inet_format) = inet.to_pg();
806        assert_eq!(inet_oid, oid::INET);
807        assert_eq!(inet_format, 0);
808
809        let cidr = Cidr::new("10.0.0.0/8");
810        let (_, cidr_oid, cidr_format) = cidr.to_pg();
811        assert_eq!(cidr_oid, oid::CIDR);
812        assert_eq!(cidr_format, 0);
813
814        let mac = MacAddr::new("08:00:2b:01:02:03");
815        let (_, mac_oid, mac_format) = mac.to_pg();
816        assert_eq!(mac_oid, oid::MACADDR);
817        assert_eq!(mac_format, 0);
818    }
819
820    #[test]
821    fn test_json_from_pg_honors_oid_and_format() {
822        let json = Json::from_pg(br#"{"ok":true}"#, oid::JSON, 0).unwrap();
823        assert_eq!(json.0, r#"{"ok":true}"#);
824
825        let jsonb_text = Json::from_pg(br#"{"ok":true}"#, oid::JSONB, 0).unwrap();
826        assert_eq!(jsonb_text.0, r#"{"ok":true}"#);
827
828        let jsonb_binary = Json::from_pg(&[1, b'{', b'}'], oid::JSONB, 1).unwrap();
829        assert_eq!(jsonb_binary.0, "{}");
830
831        assert!(Json::from_pg(&[], oid::JSONB, 1).is_err());
832        assert!(Json::from_pg(b"42", oid::INT4, 0).is_err());
833    }
834}