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: u32, format: i16) -> Result<Self, TypeError> {
86        if format == 1 {
87            // Binary format: 4 bytes big-endian
88            if bytes.len() != 4 {
89                return Err(TypeError::InvalidData(
90                    "Expected 4 bytes for i32".to_string(),
91                ));
92            }
93            Ok(i32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
94        } else {
95            // Text format
96            std::str::from_utf8(bytes)
97                .map_err(|e| TypeError::InvalidData(e.to_string()))?
98                .parse()
99                .map_err(|e| TypeError::InvalidData(format!("Invalid i32: {}", e)))
100        }
101    }
102}
103
104impl ToPg for i32 {
105    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
106        (self.to_be_bytes().to_vec(), oid::INT4, 1)
107    }
108}
109
110impl FromPg for i64 {
111    fn from_pg(bytes: &[u8], _oid: u32, format: i16) -> Result<Self, TypeError> {
112        if format == 1 {
113            // Binary format: 8 bytes big-endian
114            if bytes.len() != 8 {
115                return Err(TypeError::InvalidData(
116                    "Expected 8 bytes for i64".to_string(),
117                ));
118            }
119            Ok(i64::from_be_bytes([
120                bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
121            ]))
122        } else {
123            // Text format
124            std::str::from_utf8(bytes)
125                .map_err(|e| TypeError::InvalidData(e.to_string()))?
126                .parse()
127                .map_err(|e| TypeError::InvalidData(format!("Invalid i64: {}", e)))
128        }
129    }
130}
131
132impl ToPg for i64 {
133    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
134        (self.to_be_bytes().to_vec(), oid::INT8, 1)
135    }
136}
137
138// ==================== Float Types ====================
139
140impl FromPg for f64 {
141    fn from_pg(bytes: &[u8], _oid: u32, format: i16) -> Result<Self, TypeError> {
142        if format == 1 {
143            // Binary format: 8 bytes IEEE 754
144            if bytes.len() != 8 {
145                return Err(TypeError::InvalidData(
146                    "Expected 8 bytes for f64".to_string(),
147                ));
148            }
149            Ok(f64::from_be_bytes([
150                bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
151            ]))
152        } else {
153            // Text format
154            std::str::from_utf8(bytes)
155                .map_err(|e| TypeError::InvalidData(e.to_string()))?
156                .parse()
157                .map_err(|e| TypeError::InvalidData(format!("Invalid f64: {}", e)))
158        }
159    }
160}
161
162impl ToPg for f64 {
163    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
164        (self.to_be_bytes().to_vec(), oid::FLOAT8, 1)
165    }
166}
167
168// ==================== Boolean ====================
169
170impl FromPg for bool {
171    fn from_pg(bytes: &[u8], _oid: u32, format: i16) -> Result<Self, TypeError> {
172        if format == 1 {
173            if bytes.len() != 1 {
174                return Err(TypeError::InvalidData(
175                    "Expected 1 byte for boolean".to_string(),
176                ));
177            }
178            match bytes[0] {
179                0 => Ok(false),
180                1 => Ok(true),
181                value => Err(TypeError::InvalidData(format!(
182                    "Invalid binary boolean: {}",
183                    value
184                ))),
185            }
186        } else {
187            // Text: 't' or 'f'
188            match std::str::from_utf8(bytes)
189                .map_err(|e| TypeError::InvalidData(e.to_string()))?
190                .trim()
191            {
192                "t" | "T" | "true" | "TRUE" | "1" => Ok(true),
193                "f" | "F" | "false" | "FALSE" | "0" => Ok(false),
194                _ => Err(TypeError::InvalidData("Invalid boolean".to_string())),
195            }
196        }
197    }
198}
199
200impl ToPg for bool {
201    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
202        (vec![if *self { 1 } else { 0 }], oid::BOOL, 1)
203    }
204}
205
206// ==================== UUID ====================
207
208/// UUID type (uses String internally for simplicity)
209#[derive(Debug, Clone, PartialEq)]
210pub struct Uuid(pub String);
211
212impl FromPg for Uuid {
213    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
214        if oid_val != oid::UUID {
215            return Err(TypeError::UnexpectedOid {
216                expected: "uuid",
217                got: oid_val,
218            });
219        }
220
221        if format == 1 {
222            if bytes.len() != 16 {
223                return Err(TypeError::InvalidData(
224                    "Expected 16 bytes for UUID".to_string(),
225                ));
226            }
227            decode_uuid(bytes).map(Uuid).map_err(TypeError::InvalidData)
228        } else {
229            // Text format
230            std::str::from_utf8(bytes)
231                .map(str::to_owned)
232                .map(Uuid)
233                .map_err(|e| TypeError::InvalidData(e.to_string()))
234        }
235    }
236}
237
238impl ToPg for Uuid {
239    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
240        // Send as text for simplicity
241        (self.0.as_bytes().to_vec(), oid::UUID, 0)
242    }
243}
244
245#[cfg(feature = "uuid")]
246impl FromPg for uuid::Uuid {
247    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
248        let wrapped = Uuid::from_pg(bytes, oid_val, format)?;
249        uuid::Uuid::parse_str(&wrapped.0)
250            .map_err(|e| TypeError::InvalidData(format!("Invalid UUID: {}", e)))
251    }
252}
253
254#[cfg(feature = "uuid")]
255impl ToPg for uuid::Uuid {
256    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
257        (self.as_bytes().to_vec(), oid::UUID, 1)
258    }
259}
260
261// ==================== Network Types ====================
262
263fn from_utf8_string(bytes: &[u8]) -> Result<String, TypeError> {
264    std::str::from_utf8(bytes)
265        .map(|s| s.to_string())
266        .map_err(|e| TypeError::InvalidData(e.to_string()))
267}
268
269fn decode_inet_like_binary(bytes: &[u8], force_prefix: bool) -> Result<String, TypeError> {
270    // inet/cidr binary format:
271    // 1 byte family (2 = IPv4, 3 = IPv6)
272    // 1 byte bits (netmask length)
273    // 1 byte is_cidr (0 = inet, 1 = cidr)
274    // 1 byte addr_len
275    // N bytes address
276    if bytes.len() < 4 {
277        return Err(TypeError::InvalidData(
278            "inet/cidr binary payload too short".to_string(),
279        ));
280    }
281
282    let family = bytes[0];
283    let bits = bytes[1];
284    let is_cidr = bytes[2];
285    let addr_len = bytes[3] as usize;
286
287    if bytes.len() != 4 + addr_len {
288        return Err(TypeError::InvalidData(
289            "inet/cidr binary payload length mismatch".to_string(),
290        ));
291    }
292    if is_cidr > 1 {
293        return Err(TypeError::InvalidData(
294            "invalid inet/cidr is_cidr flag".to_string(),
295        ));
296    }
297
298    let addr = &bytes[4..];
299    match family {
300        2 => {
301            if addr_len != 4 {
302                return Err(TypeError::InvalidData(
303                    "invalid IPv4 inet/cidr address length".to_string(),
304                ));
305            }
306            if bits > 32 {
307                return Err(TypeError::InvalidData(
308                    "invalid IPv4 inet/cidr prefix length".to_string(),
309                ));
310            }
311            let ip = std::net::Ipv4Addr::from([addr[0], addr[1], addr[2], addr[3]]);
312            let include_prefix = force_prefix || is_cidr != 0 || bits != 32;
313            if include_prefix {
314                Ok(format!("{}/{}", ip, bits))
315            } else {
316                Ok(ip.to_string())
317            }
318        }
319        3 => {
320            if addr_len != 16 {
321                return Err(TypeError::InvalidData(
322                    "invalid IPv6 inet/cidr address length".to_string(),
323                ));
324            }
325            if bits > 128 {
326                return Err(TypeError::InvalidData(
327                    "invalid IPv6 inet/cidr prefix length".to_string(),
328                ));
329            }
330            let mut full = [0u8; 16];
331            full.copy_from_slice(addr);
332            let ip = std::net::Ipv6Addr::from(full);
333            let include_prefix = force_prefix || is_cidr != 0 || bits != 128;
334            if include_prefix {
335                Ok(format!("{}/{}", ip, bits))
336            } else {
337                Ok(ip.to_string())
338            }
339        }
340        _ => Err(TypeError::InvalidData(format!(
341            "unsupported inet/cidr address family: {}",
342            family
343        ))),
344    }
345}
346
347/// IPv4/IPv6 host/network address (`inet`)
348#[derive(Debug, Clone, PartialEq, Eq)]
349pub struct Inet(pub String);
350
351impl Inet {
352    /// Create a new `Inet` value from text representation.
353    pub fn new(s: impl Into<String>) -> Self {
354        Self(s.into())
355    }
356
357    /// Borrow the underlying textual representation.
358    pub fn as_str(&self) -> &str {
359        &self.0
360    }
361}
362
363impl FromPg for Inet {
364    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
365        if oid_val != oid::INET {
366            return Err(TypeError::UnexpectedOid {
367                expected: "inet",
368                got: oid_val,
369            });
370        }
371
372        let s = if format == 1 {
373            decode_inet_like_binary(bytes, false)?
374        } else {
375            from_utf8_string(bytes)?
376        };
377        Ok(Inet(s))
378    }
379}
380
381impl ToPg for Inet {
382    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
383        (self.0.as_bytes().to_vec(), oid::INET, 0)
384    }
385}
386
387/// IPv4/IPv6 network block (`cidr`)
388#[derive(Debug, Clone, PartialEq, Eq)]
389pub struct Cidr(pub String);
390
391impl Cidr {
392    /// Create a new `Cidr` value from text representation.
393    pub fn new(s: impl Into<String>) -> Self {
394        Self(s.into())
395    }
396
397    /// Borrow the underlying textual representation.
398    pub fn as_str(&self) -> &str {
399        &self.0
400    }
401}
402
403impl FromPg for Cidr {
404    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
405        if oid_val != oid::CIDR {
406            return Err(TypeError::UnexpectedOid {
407                expected: "cidr",
408                got: oid_val,
409            });
410        }
411
412        let s = if format == 1 {
413            decode_inet_like_binary(bytes, true)?
414        } else {
415            from_utf8_string(bytes)?
416        };
417        Ok(Cidr(s))
418    }
419}
420
421impl ToPg for Cidr {
422    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
423        (self.0.as_bytes().to_vec(), oid::CIDR, 0)
424    }
425}
426
427/// MAC address (`macaddr`)
428#[derive(Debug, Clone, PartialEq, Eq)]
429pub struct MacAddr(pub String);
430
431impl MacAddr {
432    /// Create a new `MacAddr` value from text representation.
433    pub fn new(s: impl Into<String>) -> Self {
434        Self(s.into())
435    }
436
437    /// Borrow the underlying textual representation.
438    pub fn as_str(&self) -> &str {
439        &self.0
440    }
441}
442
443impl FromPg for MacAddr {
444    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
445        if oid_val != oid::MACADDR {
446            return Err(TypeError::UnexpectedOid {
447                expected: "macaddr",
448                got: oid_val,
449            });
450        }
451
452        let s = if format == 1 {
453            if bytes.len() != 6 {
454                return Err(TypeError::InvalidData(
455                    "Expected 6 bytes for macaddr".to_string(),
456                ));
457            }
458            format!(
459                "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
460                bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5]
461            )
462        } else {
463            from_utf8_string(bytes)?
464        };
465
466        Ok(MacAddr(s))
467    }
468}
469
470impl ToPg for MacAddr {
471    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
472        (self.0.as_bytes().to_vec(), oid::MACADDR, 0)
473    }
474}
475
476// ==================== JSON/JSONB ====================
477
478/// JSON value (wraps the raw JSON string)
479#[derive(Debug, Clone, PartialEq)]
480pub struct Json(pub String);
481
482impl FromPg for Json {
483    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
484        let json_str = match (oid_val, format) {
485            (oid::JSON, _) | (oid::JSONB, 0) => {
486                decode_json(bytes).map_err(TypeError::InvalidData)?
487            }
488            (oid::JSONB, 1) => decode_jsonb(bytes).map_err(TypeError::InvalidData)?,
489            (oid::JSONB, other) => {
490                return Err(TypeError::InvalidData(format!(
491                    "Unsupported JSONB format code: {}",
492                    other
493                )));
494            }
495            _ => {
496                return Err(TypeError::UnexpectedOid {
497                    expected: "json/jsonb",
498                    got: oid_val,
499                });
500            }
501        };
502        Ok(Json(json_str))
503    }
504}
505
506impl ToPg for Json {
507    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
508        // Send as JSONB with version byte
509        let mut buf = Vec::with_capacity(1 + self.0.len());
510        buf.push(1); // JSONB version
511        buf.extend_from_slice(self.0.as_bytes());
512        (buf, oid::JSONB, 1)
513    }
514}
515
516// ==================== Arrays ====================
517
518impl FromPg for Vec<String> {
519    fn from_pg(bytes: &[u8], _oid: u32, _format: i16) -> Result<Self, TypeError> {
520        let s = std::str::from_utf8(bytes).map_err(|e| TypeError::InvalidData(e.to_string()))?;
521        try_decode_text_array(s).map_err(TypeError::InvalidData)
522    }
523}
524
525impl FromPg for Vec<i64> {
526    fn from_pg(bytes: &[u8], _oid: u32, _format: i16) -> Result<Self, TypeError> {
527        let s = std::str::from_utf8(bytes).map_err(|e| TypeError::InvalidData(e.to_string()))?;
528        crate::protocol::types::decode_int_array(s).map_err(TypeError::InvalidData)
529    }
530}
531
532// ==================== Option<T> ====================
533
534impl<T: FromPg> FromPg for Option<T> {
535    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
536        // This is for non-null; actual NULL handling is done at row level
537        Ok(Some(T::from_pg(bytes, oid_val, format)?))
538    }
539}
540
541// ==================== Bytes ====================
542
543impl FromPg for Vec<u8> {
544    fn from_pg(bytes: &[u8], _oid: u32, _format: i16) -> Result<Self, TypeError> {
545        Ok(bytes.to_vec())
546    }
547}
548
549impl ToPg for Vec<u8> {
550    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
551        (self.clone(), oid::BYTEA, 1)
552    }
553}
554
555impl ToPg for &[u8] {
556    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
557        (self.to_vec(), oid::BYTEA, 1)
558    }
559}
560
561#[cfg(test)]
562mod tests {
563    use super::*;
564
565    #[test]
566    fn test_string_from_pg() {
567        let result = String::from_pg(b"hello", oid::TEXT, 0).unwrap();
568        assert_eq!(result, "hello");
569    }
570
571    #[test]
572    fn test_i32_from_pg_text() {
573        let result = i32::from_pg(b"42", oid::INT4, 0).unwrap();
574        assert_eq!(result, 42);
575    }
576
577    #[test]
578    fn test_i32_from_pg_binary() {
579        let bytes = 42i32.to_be_bytes();
580        let result = i32::from_pg(&bytes, oid::INT4, 1).unwrap();
581        assert_eq!(result, 42);
582    }
583
584    #[test]
585    fn test_bool_from_pg() {
586        assert!(bool::from_pg(b"t", oid::BOOL, 0).unwrap());
587        assert!(!bool::from_pg(b"f", oid::BOOL, 0).unwrap());
588        assert!(bool::from_pg(&[1], oid::BOOL, 1).unwrap());
589        assert!(!bool::from_pg(&[0], oid::BOOL, 1).unwrap());
590    }
591
592    #[test]
593    fn test_bool_from_pg_rejects_malformed_values() {
594        assert!(bool::from_pg(&[], oid::BOOL, 1).is_err());
595        assert!(bool::from_pg(&[2], oid::BOOL, 1).is_err());
596        assert!(bool::from_pg(b"trash", oid::BOOL, 0).is_err());
597        assert!(bool::from_pg(b"falsey", oid::BOOL, 0).is_err());
598    }
599
600    #[test]
601    fn test_uuid_from_pg_binary() {
602        let uuid_bytes: [u8; 16] = [
603            0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44,
604            0x00, 0x00,
605        ];
606        let result = Uuid::from_pg(&uuid_bytes, oid::UUID, 1).unwrap();
607        assert_eq!(result.0, "550e8400-e29b-41d4-a716-446655440000");
608    }
609
610    #[test]
611    fn test_uuid_from_pg_binary_rejects_bad_length() {
612        let err = Uuid::from_pg(b"550e8400-e29b-41d4-a716-446655440000", oid::UUID, 1).unwrap_err();
613
614        assert!(matches!(err, TypeError::InvalidData(msg) if msg.contains("16 bytes")));
615    }
616
617    #[cfg(feature = "uuid")]
618    #[test]
619    fn test_std_uuid_from_pg_binary() {
620        let uuid_bytes: [u8; 16] = [
621            0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44,
622            0x00, 0x00,
623        ];
624        let result = uuid::Uuid::from_pg(&uuid_bytes, oid::UUID, 1).unwrap();
625        assert_eq!(result.to_string(), "550e8400-e29b-41d4-a716-446655440000");
626    }
627
628    #[test]
629    fn test_inet_from_pg_text() {
630        let inet = Inet::from_pg(b"10.0.0.1", oid::INET, 0).unwrap();
631        assert_eq!(inet.0, "10.0.0.1");
632    }
633
634    #[test]
635    fn test_inet_from_pg_binary_ipv4() {
636        // family=2 (IPv4), bits=32, is_cidr=0, addr_len=4, addr=10.1.2.3
637        let bytes = [2u8, 32, 0, 4, 10, 1, 2, 3];
638        let inet = Inet::from_pg(&bytes, oid::INET, 1).unwrap();
639        assert_eq!(inet.0, "10.1.2.3");
640    }
641
642    #[test]
643    fn test_cidr_from_pg_binary_ipv4() {
644        // family=2 (IPv4), bits=24, is_cidr=1, addr_len=4, addr=192.168.1.0
645        let bytes = [2u8, 24, 1, 4, 192, 168, 1, 0];
646        let cidr = Cidr::from_pg(&bytes, oid::CIDR, 1).unwrap();
647        assert_eq!(cidr.0, "192.168.1.0/24");
648    }
649
650    #[test]
651    fn test_network_types_reject_malformed_binary_payloads() {
652        assert!(Inet::from_pg(&[2u8, 33, 0, 4, 10, 1, 2, 3], oid::INET, 1).is_err());
653        assert!(Inet::from_pg(&[2u8, 32, 0, 1, 10], oid::INET, 1).is_err());
654        assert!(Inet::from_pg(&[2u8, 32, 2, 4, 10, 1, 2, 3], oid::INET, 1).is_err());
655
656        let mut ipv6 = vec![3u8, 129, 0, 16];
657        ipv6.extend_from_slice(&[0u8; 16]);
658        assert!(Inet::from_pg(&ipv6, oid::INET, 1).is_err());
659    }
660
661    #[test]
662    fn test_macaddr_from_pg_binary() {
663        let bytes = [0x08u8, 0x00, 0x2b, 0x01, 0x02, 0x03];
664        let mac = MacAddr::from_pg(&bytes, oid::MACADDR, 1).unwrap();
665        assert_eq!(mac.0, "08:00:2b:01:02:03");
666    }
667
668    #[test]
669    fn test_network_types_to_pg_oids() {
670        let inet = Inet::new("10.0.0.0/8");
671        let (_, inet_oid, inet_format) = inet.to_pg();
672        assert_eq!(inet_oid, oid::INET);
673        assert_eq!(inet_format, 0);
674
675        let cidr = Cidr::new("10.0.0.0/8");
676        let (_, cidr_oid, cidr_format) = cidr.to_pg();
677        assert_eq!(cidr_oid, oid::CIDR);
678        assert_eq!(cidr_format, 0);
679
680        let mac = MacAddr::new("08:00:2b:01:02:03");
681        let (_, mac_oid, mac_format) = mac.to_pg();
682        assert_eq!(mac_oid, oid::MACADDR);
683        assert_eq!(mac_format, 0);
684    }
685
686    #[test]
687    fn test_json_from_pg_honors_oid_and_format() {
688        let json = Json::from_pg(br#"{"ok":true}"#, oid::JSON, 0).unwrap();
689        assert_eq!(json.0, r#"{"ok":true}"#);
690
691        let jsonb_text = Json::from_pg(br#"{"ok":true}"#, oid::JSONB, 0).unwrap();
692        assert_eq!(jsonb_text.0, r#"{"ok":true}"#);
693
694        let jsonb_binary = Json::from_pg(&[1, b'{', b'}'], oid::JSONB, 1).unwrap();
695        assert_eq!(jsonb_binary.0, "{}");
696
697        assert!(Json::from_pg(&[], oid::JSONB, 1).is_err());
698        assert!(Json::from_pg(b"42", oid::INT4, 0).is_err());
699    }
700}