sqlx_postgres/types/geometry/
box.rs

1use crate::decode::Decode;
2use crate::encode::{Encode, IsNull};
3use crate::error::BoxDynError;
4use crate::types::Type;
5use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres};
6use sqlx_core::bytes::Buf;
7use std::str::FromStr;
8
9const ERROR: &str = "error decoding BOX";
10
11/// ## Postgres Geometric Box type
12///
13/// Description: Rectangular box
14/// Representation: `((upper_right_x,upper_right_y),(lower_left_x,lower_left_y))`
15///
16/// Boxes are represented by pairs of points that are opposite corners of the box. Values of type box are specified using any of the following syntaxes:
17///
18/// ```text
19/// ( ( upper_right_x , upper_right_y ) , ( lower_left_x , lower_left_y ) )
20/// ( upper_right_x , upper_right_y ) , ( lower_left_x , lower_left_y )
21///   upper_right_x , upper_right_y   ,   lower_left_x , lower_left_y
22/// ```
23/// where `(upper_right_x,upper_right_y) and (lower_left_x,lower_left_y)` are any two opposite corners of the box.
24/// Any two opposite corners can be supplied on input, but the values will be reordered as needed to store the upper right and lower left corners, in that order.
25///
26/// See [Postgres Manual, Section 8.8.4: Geometric Types - Boxes][PG.S.8.8.4] for details.
27///
28/// [PG.S.8.8.4]: https://www.postgresql.org/docs/current/datatype-geometric.html#DATATYPE-GEOMETRIC-BOXES
29///
30#[derive(Debug, Clone, PartialEq)]
31pub struct PgBox {
32    pub upper_right_x: f64,
33    pub upper_right_y: f64,
34    pub lower_left_x: f64,
35    pub lower_left_y: f64,
36}
37
38impl Type<Postgres> for PgBox {
39    fn type_info() -> PgTypeInfo {
40        PgTypeInfo::with_name("box")
41    }
42}
43
44impl PgHasArrayType for PgBox {
45    fn array_type_info() -> PgTypeInfo {
46        PgTypeInfo::with_name("_box")
47    }
48}
49
50impl<'r> Decode<'r, Postgres> for PgBox {
51    fn decode(value: PgValueRef<'r>) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
52        match value.format() {
53            PgValueFormat::Text => Ok(PgBox::from_str(value.as_str()?)?),
54            PgValueFormat::Binary => Ok(PgBox::from_bytes(value.as_bytes()?)?),
55        }
56    }
57}
58
59impl<'q> Encode<'q, Postgres> for PgBox {
60    fn produces(&self) -> Option<PgTypeInfo> {
61        Some(PgTypeInfo::with_name("box"))
62    }
63
64    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
65        self.serialize(buf)?;
66        Ok(IsNull::No)
67    }
68}
69
70impl FromStr for PgBox {
71    type Err = BoxDynError;
72
73    fn from_str(s: &str) -> Result<Self, Self::Err> {
74        let sanitised = s.replace(['(', ')', '[', ']', ' '], "");
75        let mut parts = sanitised.split(',');
76
77        let upper_right_x = parts
78            .next()
79            .and_then(|s| s.parse::<f64>().ok())
80            .ok_or_else(|| format!("{}: could not get upper_right_x from {}", ERROR, s))?;
81
82        let upper_right_y = parts
83            .next()
84            .and_then(|s| s.parse::<f64>().ok())
85            .ok_or_else(|| format!("{}: could not get upper_right_y from {}", ERROR, s))?;
86
87        let lower_left_x = parts
88            .next()
89            .and_then(|s| s.parse::<f64>().ok())
90            .ok_or_else(|| format!("{}: could not get lower_left_x from {}", ERROR, s))?;
91
92        let lower_left_y = parts
93            .next()
94            .and_then(|s| s.parse::<f64>().ok())
95            .ok_or_else(|| format!("{}: could not get lower_left_y from {}", ERROR, s))?;
96
97        if parts.next().is_some() {
98            return Err(format!("{}: too many numbers inputted in {}", ERROR, s).into());
99        }
100
101        Ok(PgBox {
102            upper_right_x,
103            upper_right_y,
104            lower_left_x,
105            lower_left_y,
106        })
107    }
108}
109
110impl PgBox {
111    fn from_bytes(mut bytes: &[u8]) -> Result<PgBox, BoxDynError> {
112        let upper_right_x = bytes.get_f64();
113        let upper_right_y = bytes.get_f64();
114        let lower_left_x = bytes.get_f64();
115        let lower_left_y = bytes.get_f64();
116
117        Ok(PgBox {
118            upper_right_x,
119            upper_right_y,
120            lower_left_x,
121            lower_left_y,
122        })
123    }
124
125    fn serialize(&self, buff: &mut PgArgumentBuffer) -> Result<(), String> {
126        let min_x = &self.upper_right_x.min(self.lower_left_x);
127        let min_y = &self.upper_right_y.min(self.lower_left_y);
128        let max_x = &self.upper_right_x.max(self.lower_left_x);
129        let max_y = &self.upper_right_y.max(self.lower_left_y);
130
131        buff.extend_from_slice(&max_x.to_be_bytes());
132        buff.extend_from_slice(&max_y.to_be_bytes());
133        buff.extend_from_slice(&min_x.to_be_bytes());
134        buff.extend_from_slice(&min_y.to_be_bytes());
135
136        Ok(())
137    }
138
139    #[cfg(test)]
140    fn serialize_to_vec(&self) -> Vec<u8> {
141        let mut buff = PgArgumentBuffer::default();
142        self.serialize(&mut buff).unwrap();
143        buff.to_vec()
144    }
145}
146
147#[cfg(test)]
148mod box_tests {
149
150    use std::str::FromStr;
151
152    use super::PgBox;
153
154    const BOX_BYTES: &[u8] = &[
155        64, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0,
156        0, 0, 0, 0,
157    ];
158
159    #[test]
160    fn can_deserialise_box_type_bytes_in_order() {
161        let pg_box = PgBox::from_bytes(BOX_BYTES).unwrap();
162        assert_eq!(
163            pg_box,
164            PgBox {
165                upper_right_x: 2.,
166                upper_right_y: 2.,
167                lower_left_x: -2.,
168                lower_left_y: -2.
169            }
170        )
171    }
172
173    #[test]
174    fn can_deserialise_box_type_str_first_syntax() {
175        let pg_box = PgBox::from_str("[( 1, 2), (3, 4 )]").unwrap();
176        assert_eq!(
177            pg_box,
178            PgBox {
179                upper_right_x: 1.,
180                upper_right_y: 2.,
181                lower_left_x: 3.,
182                lower_left_y: 4.
183            }
184        );
185    }
186    #[test]
187    fn can_deserialise_box_type_str_second_syntax() {
188        let pg_box = PgBox::from_str("(( 1, 2), (3, 4 ))").unwrap();
189        assert_eq!(
190            pg_box,
191            PgBox {
192                upper_right_x: 1.,
193                upper_right_y: 2.,
194                lower_left_x: 3.,
195                lower_left_y: 4.
196            }
197        );
198    }
199
200    #[test]
201    fn can_deserialise_box_type_str_third_syntax() {
202        let pg_box = PgBox::from_str("(1, 2), (3, 4 )").unwrap();
203        assert_eq!(
204            pg_box,
205            PgBox {
206                upper_right_x: 1.,
207                upper_right_y: 2.,
208                lower_left_x: 3.,
209                lower_left_y: 4.
210            }
211        );
212    }
213
214    #[test]
215    fn can_deserialise_box_type_str_fourth_syntax() {
216        let pg_box = PgBox::from_str("1, 2, 3, 4").unwrap();
217        assert_eq!(
218            pg_box,
219            PgBox {
220                upper_right_x: 1.,
221                upper_right_y: 2.,
222                lower_left_x: 3.,
223                lower_left_y: 4.
224            }
225        );
226    }
227
228    #[test]
229    fn cannot_deserialise_too_many_numbers() {
230        let input_str = "1, 2, 3, 4, 5";
231        let pg_box = PgBox::from_str(input_str);
232        assert!(pg_box.is_err());
233        if let Err(err) = pg_box {
234            assert_eq!(
235                err.to_string(),
236                format!("error decoding BOX: too many numbers inputted in {input_str}")
237            )
238        }
239    }
240
241    #[test]
242    fn cannot_deserialise_too_few_numbers() {
243        let input_str = "1, 2, 3 ";
244        let pg_box = PgBox::from_str(input_str);
245        assert!(pg_box.is_err());
246        if let Err(err) = pg_box {
247            assert_eq!(
248                err.to_string(),
249                format!("error decoding BOX: could not get lower_left_y from {input_str}")
250            )
251        }
252    }
253
254    #[test]
255    fn cannot_deserialise_invalid_numbers() {
256        let input_str = "1, 2, 3, FOUR";
257        let pg_box = PgBox::from_str(input_str);
258        assert!(pg_box.is_err());
259        if let Err(err) = pg_box {
260            assert_eq!(
261                err.to_string(),
262                format!("error decoding BOX: could not get lower_left_y from {input_str}")
263            )
264        }
265    }
266
267    #[test]
268    fn can_deserialise_box_type_str_float() {
269        let pg_box = PgBox::from_str("(1.1, 2.2), (3.3, 4.4)").unwrap();
270        assert_eq!(
271            pg_box,
272            PgBox {
273                upper_right_x: 1.1,
274                upper_right_y: 2.2,
275                lower_left_x: 3.3,
276                lower_left_y: 4.4
277            }
278        );
279    }
280
281    #[test]
282    fn can_serialise_box_type_in_order() {
283        let pg_box = PgBox {
284            upper_right_x: 2.,
285            lower_left_x: -2.,
286            upper_right_y: -2.,
287            lower_left_y: 2.,
288        };
289        assert_eq!(pg_box.serialize_to_vec(), BOX_BYTES,)
290    }
291
292    #[test]
293    fn can_serialise_box_type_out_of_order() {
294        let pg_box = PgBox {
295            upper_right_x: -2.,
296            lower_left_x: 2.,
297            upper_right_y: 2.,
298            lower_left_y: -2.,
299        };
300        assert_eq!(pg_box.serialize_to_vec(), BOX_BYTES,)
301    }
302
303    #[test]
304    fn can_order_box() {
305        let pg_box = PgBox {
306            upper_right_x: -2.,
307            lower_left_x: 2.,
308            upper_right_y: 2.,
309            lower_left_y: -2.,
310        };
311        let bytes = pg_box.serialize_to_vec();
312
313        let pg_box = PgBox::from_bytes(&bytes).unwrap();
314        assert_eq!(
315            pg_box,
316            PgBox {
317                upper_right_x: 2.,
318                upper_right_y: 2.,
319                lower_left_x: -2.,
320                lower_left_y: -2.
321            }
322        )
323    }
324}