sqlx_xugu/types/geometry/
path.rs

1use super::XgPoint;
2use crate::arguments::XuguArgumentValue;
3use crate::protocol::text::ColumnType;
4use crate::{Xugu, XuguTypeInfo, XuguValueRef};
5use sqlx_core::decode::Decode;
6use sqlx_core::encode::{Encode, IsNull};
7use sqlx_core::error::BoxDynError;
8use sqlx_core::types::Type;
9use sqlx_core::Error;
10use std::borrow::Cow;
11use std::fmt::{Display, Formatter, Write};
12use std::str::FromStr;
13
14/// ## Xugu Geometric Path type
15///
16/// Description: Open path or Closed path (similar to polygon)
17/// Representation: Open `[(x1,y1),...]`, Closed `((x1,y1),...)`
18///
19/// Paths are represented by lists of connected points. Paths can be open, where the first and last points in the list are considered not connected, or closed, where the first and last points are considered connected.
20/// Values of type path are specified using any of the following syntaxes:
21/// ```text
22/// [ ( x1 , y1 ) , ... , ( xn , yn ) ]
23/// ( ( x1 , y1 ) , ... , ( xn , yn ) )
24///   ( x1 , y1 ) , ... , ( xn , yn )
25///   ( x1 , y1   , ... ,   xn , yn )
26///     x1 , y1   , ... ,   xn , yn
27/// ```
28/// where the points are the end points of the line segments comprising the path. Square brackets `([])` indicate an open path, while parentheses `(())` indicate a closed path.
29/// When the outermost parentheses are omitted, as in the third through fifth syntaxes, a closed path is assumed.
30///
31#[derive(Debug, Clone, PartialEq)]
32pub struct XgPath {
33    pub closed: bool,
34    pub points: Vec<XgPoint>,
35}
36
37impl Type<Xugu> for XgPath {
38    fn type_info() -> XuguTypeInfo {
39        XuguTypeInfo::binary(ColumnType::PATH)
40    }
41
42    fn compatible(ty: &XuguTypeInfo) -> bool {
43        matches!(ty.r#type, ColumnType::CHAR | ColumnType::PATH)
44    }
45}
46
47impl<'r> Decode<'r, Xugu> for XgPath {
48    fn decode(value: XuguValueRef<'r>) -> Result<Self, BoxDynError> {
49        let s = value.as_str()?;
50        Ok(Self::from_str(s)?)
51    }
52}
53
54impl Encode<'_, Xugu> for XgPath {
55    fn encode_by_ref(&self, args: &mut Vec<XuguArgumentValue>) -> Result<IsNull, BoxDynError> {
56        let s = self.to_string();
57
58        args.push(XuguArgumentValue::Str(Cow::Owned(s)));
59
60        Ok(IsNull::No)
61    }
62
63    fn produces(&self) -> Option<XuguTypeInfo> {
64        Some(XuguTypeInfo::binary(ColumnType::CHAR))
65    }
66}
67
68impl Display for XgPath {
69    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
70        f.write_char(if self.closed { '(' } else { '[' })?;
71        for (i, p) in self.points.iter().enumerate() {
72            if i > 0 {
73                f.write_char(',')?;
74            }
75            Display::fmt(&p, f)?;
76        }
77        f.write_char(if self.closed { ')' } else { ']' })
78    }
79}
80
81fn parse_float_from_str(s: &str, error_msg: &str) -> Result<f64, Error> {
82    s.parse().map_err(|_| Error::Decode(error_msg.into()))
83}
84
85impl FromStr for XgPath {
86    type Err = Error;
87
88    fn from_str(s: &str) -> Result<Self, Self::Err> {
89        let closed = !s.contains('[');
90        let sanitised = s.replace(['(', ')', '[', ']', ' '], "");
91        let parts = sanitised.split(',').collect::<Vec<_>>();
92
93        let mut points = vec![];
94
95        if parts.len() % 2 != 0 {
96            return Err(Error::Decode(
97                format!("Unmatched pair in PATH: {}", s).into(),
98            ));
99        }
100
101        for chunk in parts.chunks_exact(2) {
102            if let [x_str, y_str] = chunk {
103                let x = parse_float_from_str(x_str, "could not get x")?;
104                let y = parse_float_from_str(y_str, "could not get y")?;
105
106                let point = XgPoint { x, y };
107                points.push(point);
108            }
109        }
110
111        if !points.is_empty() {
112            return Ok(Self { points, closed });
113        }
114
115        Err(Error::Decode(
116            format!("could not get path from {}", s).into(),
117        ))
118    }
119}
120
121impl XgPath {
122    pub fn is_closed(&self) -> bool {
123        self.closed
124    }
125    pub fn is_open(&self) -> bool {
126        !self.closed
127    }
128
129    pub fn close_path(&mut self) {
130        self.closed = true;
131    }
132
133    pub fn open_path(&mut self) {
134        self.closed = false;
135    }
136}