sqlx_xugu/types/geometry/
path.rs1use 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#[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}