sqlx_postgres/types/geometry/
line_segment.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 LSEG";
10
11/// ## Postgres Geometric Line Segment type
12///
13/// Description: Finite line segment
14/// Representation: `((start_x,start_y),(end_x,end_y))`
15///
16///
17/// Line segments are represented by pairs of points that are the endpoints of the segment. Values of type lseg are specified using any of the following syntaxes:
18/// ```text
19/// [ ( start_x , start_y ) , ( end_x , end_y ) ]
20/// ( ( start_x , start_y ) , ( end_x , end_y ) )
21///   ( start_x , start_y ) , ( end_x , end_y )
22///     start_x , start_y   ,   end_x , end_y
23/// ```
24/// where `(start_x,start_y) and (end_x,end_y)` are the end points of the line segment.
25///
26/// See [Postgres Manual, Section 8.8.3, Geometric Types - Line Segments][PG.S.8.8.3] for details.
27///
28/// [PG.S.8.8.3]: https://www.postgresql.org/docs/current/datatype-geometric.html#DATATYPE-LSEG
29///
30#[doc(alias = "line segment")]
31#[derive(Debug, Clone, PartialEq)]
32pub struct PgLSeg {
33    pub start_x: f64,
34    pub start_y: f64,
35    pub end_x: f64,
36    pub end_y: f64,
37}
38
39impl Type<Postgres> for PgLSeg {
40    fn type_info() -> PgTypeInfo {
41        PgTypeInfo::with_name("lseg")
42    }
43}
44
45impl PgHasArrayType for PgLSeg {
46    fn array_type_info() -> PgTypeInfo {
47        PgTypeInfo::with_name("_lseg")
48    }
49}
50
51impl<'r> Decode<'r, Postgres> for PgLSeg {
52    fn decode(value: PgValueRef<'r>) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
53        match value.format() {
54            PgValueFormat::Text => Ok(PgLSeg::from_str(value.as_str()?)?),
55            PgValueFormat::Binary => Ok(PgLSeg::from_bytes(value.as_bytes()?)?),
56        }
57    }
58}
59
60impl<'q> Encode<'q, Postgres> for PgLSeg {
61    fn produces(&self) -> Option<PgTypeInfo> {
62        Some(PgTypeInfo::with_name("lseg"))
63    }
64
65    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
66        self.serialize(buf)?;
67        Ok(IsNull::No)
68    }
69}
70
71impl FromStr for PgLSeg {
72    type Err = BoxDynError;
73
74    fn from_str(s: &str) -> Result<Self, Self::Err> {
75        let sanitised = s.replace(['(', ')', '[', ']', ' '], "");
76        let mut parts = sanitised.split(',');
77
78        let start_x = parts
79            .next()
80            .and_then(|s| s.parse::<f64>().ok())
81            .ok_or_else(|| format!("{}: could not get start_x from {}", ERROR, s))?;
82
83        let start_y = parts
84            .next()
85            .and_then(|s| s.parse::<f64>().ok())
86            .ok_or_else(|| format!("{}: could not get start_y from {}", ERROR, s))?;
87
88        let end_x = parts
89            .next()
90            .and_then(|s| s.parse::<f64>().ok())
91            .ok_or_else(|| format!("{}: could not get end_x from {}", ERROR, s))?;
92
93        let end_y = parts
94            .next()
95            .and_then(|s| s.parse::<f64>().ok())
96            .ok_or_else(|| format!("{}: could not get end_y from {}", ERROR, s))?;
97
98        if parts.next().is_some() {
99            return Err(format!("{}: too many numbers inputted in {}", ERROR, s).into());
100        }
101
102        Ok(PgLSeg {
103            start_x,
104            start_y,
105            end_x,
106            end_y,
107        })
108    }
109}
110
111impl PgLSeg {
112    fn from_bytes(mut bytes: &[u8]) -> Result<PgLSeg, BoxDynError> {
113        let start_x = bytes.get_f64();
114        let start_y = bytes.get_f64();
115        let end_x = bytes.get_f64();
116        let end_y = bytes.get_f64();
117
118        Ok(PgLSeg {
119            start_x,
120            start_y,
121            end_x,
122            end_y,
123        })
124    }
125
126    fn serialize(&self, buff: &mut PgArgumentBuffer) -> Result<(), BoxDynError> {
127        buff.extend_from_slice(&self.start_x.to_be_bytes());
128        buff.extend_from_slice(&self.start_y.to_be_bytes());
129        buff.extend_from_slice(&self.end_x.to_be_bytes());
130        buff.extend_from_slice(&self.end_y.to_be_bytes());
131        Ok(())
132    }
133
134    #[cfg(test)]
135    fn serialize_to_vec(&self) -> Vec<u8> {
136        let mut buff = PgArgumentBuffer::default();
137        self.serialize(&mut buff).unwrap();
138        buff.to_vec()
139    }
140}
141
142#[cfg(test)]
143mod lseg_tests {
144
145    use std::str::FromStr;
146
147    use super::PgLSeg;
148
149    const LINE_SEGMENT_BYTES: &[u8] = &[
150        63, 241, 153, 153, 153, 153, 153, 154, 64, 1, 153, 153, 153, 153, 153, 154, 64, 10, 102,
151        102, 102, 102, 102, 102, 64, 17, 153, 153, 153, 153, 153, 154,
152    ];
153
154    #[test]
155    fn can_deserialise_lseg_type_bytes() {
156        let lseg = PgLSeg::from_bytes(LINE_SEGMENT_BYTES).unwrap();
157        assert_eq!(
158            lseg,
159            PgLSeg {
160                start_x: 1.1,
161                start_y: 2.2,
162                end_x: 3.3,
163                end_y: 4.4
164            }
165        )
166    }
167
168    #[test]
169    fn can_deserialise_lseg_type_str_first_syntax() {
170        let lseg = PgLSeg::from_str("[( 1, 2), (3, 4 )]").unwrap();
171        assert_eq!(
172            lseg,
173            PgLSeg {
174                start_x: 1.,
175                start_y: 2.,
176                end_x: 3.,
177                end_y: 4.
178            }
179        );
180    }
181    #[test]
182    fn can_deserialise_lseg_type_str_second_syntax() {
183        let lseg = PgLSeg::from_str("(( 1, 2), (3, 4 ))").unwrap();
184        assert_eq!(
185            lseg,
186            PgLSeg {
187                start_x: 1.,
188                start_y: 2.,
189                end_x: 3.,
190                end_y: 4.
191            }
192        );
193    }
194
195    #[test]
196    fn can_deserialise_lseg_type_str_third_syntax() {
197        let lseg = PgLSeg::from_str("(1, 2), (3, 4 )").unwrap();
198        assert_eq!(
199            lseg,
200            PgLSeg {
201                start_x: 1.,
202                start_y: 2.,
203                end_x: 3.,
204                end_y: 4.
205            }
206        );
207    }
208
209    #[test]
210    fn can_deserialise_lseg_type_str_fourth_syntax() {
211        let lseg = PgLSeg::from_str("1, 2, 3, 4").unwrap();
212        assert_eq!(
213            lseg,
214            PgLSeg {
215                start_x: 1.,
216                start_y: 2.,
217                end_x: 3.,
218                end_y: 4.
219            }
220        );
221    }
222
223    #[test]
224    fn can_deserialise_too_many_numbers() {
225        let input_str = "1, 2, 3, 4, 5";
226        let lseg = PgLSeg::from_str(input_str);
227        assert!(lseg.is_err());
228        if let Err(err) = lseg {
229            assert_eq!(
230                err.to_string(),
231                format!("error decoding LSEG: too many numbers inputted in {input_str}")
232            )
233        }
234    }
235
236    #[test]
237    fn can_deserialise_too_few_numbers() {
238        let input_str = "1, 2, 3";
239        let lseg = PgLSeg::from_str(input_str);
240        assert!(lseg.is_err());
241        if let Err(err) = lseg {
242            assert_eq!(
243                err.to_string(),
244                format!("error decoding LSEG: could not get end_y from {input_str}")
245            )
246        }
247    }
248
249    #[test]
250    fn can_deserialise_invalid_numbers() {
251        let input_str = "1, 2, 3, FOUR";
252        let lseg = PgLSeg::from_str(input_str);
253        assert!(lseg.is_err());
254        if let Err(err) = lseg {
255            assert_eq!(
256                err.to_string(),
257                format!("error decoding LSEG: could not get end_y from {input_str}")
258            )
259        }
260    }
261
262    #[test]
263    fn can_deserialise_lseg_type_str_float() {
264        let lseg = PgLSeg::from_str("(1.1, 2.2), (3.3, 4.4)").unwrap();
265        assert_eq!(
266            lseg,
267            PgLSeg {
268                start_x: 1.1,
269                start_y: 2.2,
270                end_x: 3.3,
271                end_y: 4.4
272            }
273        );
274    }
275
276    #[test]
277    fn can_serialise_lseg_type() {
278        let lseg = PgLSeg {
279            start_x: 1.1,
280            start_y: 2.2,
281            end_x: 3.3,
282            end_y: 4.4,
283        };
284        assert_eq!(lseg.serialize_to_vec(), LINE_SEGMENT_BYTES,)
285    }
286}