zero_postgres/conversion/
string.rs

1//! String type implementations (&str, String).
2
3use crate::error::{Error, Result};
4use crate::protocol::types::{Oid, oid};
5
6use super::{FromWireValue, ToWireValue};
7
8impl<'a> FromWireValue<'a> for &'a str {
9    fn from_text(oid: Oid, bytes: &'a [u8]) -> Result<Self> {
10        // NUMERIC uses text format, so it can be decoded as str in text mode
11        if !matches!(
12            oid,
13            oid::TEXT | oid::VARCHAR | oid::BPCHAR | oid::NAME | oid::NUMERIC
14        ) {
15            return Err(Error::Decode(format!("cannot decode oid {} as str", oid)));
16        }
17        simdutf8::compat::from_utf8(bytes)
18            .map_err(|e| Error::Decode(format!("invalid UTF-8: {}", e)))
19    }
20
21    fn from_binary(oid: Oid, bytes: &'a [u8]) -> Result<Self> {
22        // Note: NUMERIC binary format is NOT UTF-8, so we don't accept it here
23        if !matches!(oid, oid::TEXT | oid::VARCHAR | oid::BPCHAR | oid::NAME) {
24            return Err(Error::Decode(format!("cannot decode oid {} as str", oid)));
25        }
26        simdutf8::compat::from_utf8(bytes)
27            .map_err(|e| Error::Decode(format!("invalid UTF-8: {}", e)))
28    }
29}
30
31impl FromWireValue<'_> for String {
32    fn from_text(oid: Oid, bytes: &[u8]) -> Result<Self> {
33        // NUMERIC uses text format, so it can be decoded as String in text mode
34        if !matches!(
35            oid,
36            oid::TEXT | oid::VARCHAR | oid::BPCHAR | oid::NAME | oid::NUMERIC
37        ) {
38            return Err(Error::Decode(format!(
39                "cannot decode oid {} as String",
40                oid
41            )));
42        }
43        simdutf8::compat::from_utf8(bytes)
44            .map(|s| s.to_owned())
45            .map_err(|e| Error::Decode(format!("invalid UTF-8: {}", e)))
46    }
47
48    fn from_binary(oid: Oid, bytes: &[u8]) -> Result<Self> {
49        // Note: NUMERIC binary format is NOT UTF-8, so we don't accept it here
50        if !matches!(oid, oid::TEXT | oid::VARCHAR | oid::BPCHAR | oid::NAME) {
51            return Err(Error::Decode(format!(
52                "cannot decode oid {} as String",
53                oid
54            )));
55        }
56        simdutf8::compat::from_utf8(bytes)
57            .map(|s| s.to_owned())
58            .map_err(|e| Error::Decode(format!("invalid UTF-8: {}", e)))
59    }
60}
61
62impl ToWireValue for str {
63    fn natural_oid(&self) -> Oid {
64        oid::TEXT
65    }
66
67    fn encode(&self, target_oid: Oid, buf: &mut Vec<u8>) -> Result<()> {
68        match target_oid {
69            oid::TEXT | oid::VARCHAR | oid::BPCHAR | oid::NAME | oid::JSON | oid::JSONB => {
70                let bytes = self.as_bytes();
71                buf.extend_from_slice(&(bytes.len() as i32).to_be_bytes());
72                buf.extend_from_slice(bytes);
73                Ok(())
74            }
75            _ => Err(Error::type_mismatch(self.natural_oid(), target_oid)),
76        }
77    }
78}
79
80impl ToWireValue for String {
81    fn natural_oid(&self) -> Oid {
82        oid::TEXT
83    }
84
85    fn encode(&self, target_oid: Oid, buf: &mut Vec<u8>) -> Result<()> {
86        self.as_str().encode(target_oid, buf)
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn test_string_text() {
96        assert_eq!(String::from_text(oid::TEXT, b"hello").unwrap(), "hello");
97    }
98
99    #[test]
100    fn test_type_mismatch() {
101        // Trying to decode INT4 as String should fail
102        assert!(String::from_binary(oid::INT4, &[0, 0, 0, 1]).is_err());
103    }
104}