img_gen_spec/validators/layers/
polygon.rs1use super::{Border, ColorKind, LayerOffset};
2
3#[cfg(feature = "pyo3")]
4use pyo3::prelude::*;
5
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, Serialize)]
10#[cfg_attr(feature = "pyo3", pyclass(module = "img_gen", from_py_object))]
11pub struct RegularPolygonSides(u32);
12
13impl RegularPolygonSides {
14 pub fn new(sides: u32) -> Option<Self> {
16 if sides < 3 { None } else { Some(Self(sides)) }
17 }
18
19 pub fn get(&self) -> u32 {
21 self.0
22 }
23}
24
25#[derive(Debug, Clone, Serialize)]
27#[cfg_attr(feature = "pyo3", pyclass(module = "img_gen", from_py_object))]
28pub struct IrregularPolygonSides(Vec<LayerOffset>);
29
30impl IrregularPolygonSides {
31 pub fn new(offsets: Vec<LayerOffset>) -> Option<Self> {
33 let mut unique = Vec::new();
34 for offset in offsets {
35 if !unique.contains(&offset) {
36 unique.push(offset);
37 }
38 }
39 if unique.len() < 3 {
40 None
41 } else {
42 Some(Self(unique))
43 }
44 }
45
46 pub fn as_slice(&self) -> &[LayerOffset] {
48 &self.0
49 }
50}
51
52#[cfg_attr(feature = "pyo3", pyclass(module = "img_gen", from_py_object))]
54#[derive(Debug, Clone)]
55pub enum PolygonSides {
56 Regular(RegularPolygonSides),
58 Irregular(IrregularPolygonSides),
60}
61
62impl From<RegularPolygonSides> for PolygonSides {
63 fn from(value: RegularPolygonSides) -> Self {
64 Self::Regular(value)
65 }
66}
67
68impl From<IrregularPolygonSides> for PolygonSides {
69 fn from(value: IrregularPolygonSides) -> Self {
70 Self::Irregular(value)
71 }
72}
73
74impl Default for PolygonSides {
75 fn default() -> Self {
76 RegularPolygonSides(3).into()
77 }
78}
79
80impl<'de> Deserialize<'de> for RegularPolygonSides {
81 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
82 where
83 D: serde::Deserializer<'de>,
84 {
85 use serde::de::Error;
86
87 let sides = u32::deserialize(deserializer)?;
88 RegularPolygonSides::new(sides).ok_or_else(|| {
89 D::Error::custom(format!(
90 "Regular Polygons cannot have less than 3 sides, got {sides}"
91 ))
92 })
93 }
94}
95
96impl<'de> Deserialize<'de> for IrregularPolygonSides {
97 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
98 where
99 D: serde::Deserializer<'de>,
100 {
101 use serde::de::Error;
102
103 let offsets = Vec::<LayerOffset>::deserialize(deserializer)?;
104 IrregularPolygonSides::new(offsets).ok_or_else(|| {
105 D::Error::custom("Irregular Polygons cannot have less than 3 unique points")
106 })
107 }
108}
109
110impl<'de> Deserialize<'de> for PolygonSides {
111 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
112 where
113 D: serde::Deserializer<'de>,
114 {
115 PolygonSides::deserialize_sides(deserializer)
116 }
117}
118
119impl Serialize for PolygonSides {
120 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
121 where
122 S: serde::Serializer,
123 {
124 PolygonSides::serialize_sides(self, serializer)
125 }
126}
127
128impl PolygonSides {
129 pub fn deserialize<'de, D>(deserializer: D) -> Result<Self, D::Error>
134 where
135 D: serde::Deserializer<'de>,
136 {
137 PolygonSides::deserialize_sides(deserializer)
138 }
139
140 fn deserialize_sides<'de, D>(deserializer: D) -> Result<Self, D::Error>
141 where
142 D: serde::Deserializer<'de>,
143 {
144 use serde::de::{Error, SeqAccess, Visitor};
145
146 struct PolygonSidesVisitor;
147
148 impl<'de> Visitor<'de> for PolygonSidesVisitor {
149 type Value = PolygonSides;
150
151 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 formatter.write_str("an integer >= 3 or a sequence of at least 3 unique offsets")
153 }
154
155 fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
156 where
157 E: Error,
158 {
159 let sides = u32::try_from(value)
160 .map_err(|_| E::custom(format!("PolygonSides value overflow: {value}")))?;
161 RegularPolygonSides::new(sides)
162 .map(Into::into)
163 .ok_or_else(|| {
164 E::custom(format!(
165 "Regular Polygons cannot have less than 3 sides, got {sides}"
166 ))
167 })
168 }
169
170 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
171 where
172 A: SeqAccess<'de>,
173 {
174 let mut offsets = Vec::new();
175 while let Some(offset) = seq.next_element::<LayerOffset>()? {
176 offsets.push(offset);
177 }
178 IrregularPolygonSides::new(offsets)
179 .map(Into::into)
180 .ok_or_else(|| {
181 A::Error::custom("Irregular Polygons cannot have less than 3 unique points")
182 })
183 }
184 }
185
186 deserializer.deserialize_any(PolygonSidesVisitor)
187 }
188
189 pub fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
191 where
192 S: serde::Serializer,
193 {
194 PolygonSides::serialize_sides(self, serializer)
195 }
196
197 fn serialize_sides<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
198 where
199 S: serde::Serializer,
200 {
201 use serde::ser::SerializeSeq;
202
203 match self {
204 PolygonSides::Regular(r) => serializer.serialize_u32(r.get()),
205 PolygonSides::Irregular(i) => {
206 let offsets = i.as_slice();
207 let mut seq = serializer.serialize_seq(Some(offsets.len()))?;
208 for offset in offsets {
209 seq.serialize_element(offset)?;
210 }
211 seq.end()
212 }
213 }
214 }
215}
216
217#[cfg_attr(feature = "pyo3", pyclass(module = "img_gen", from_py_object))]
219#[derive(Debug, Clone, Default, Serialize, Deserialize)]
220pub struct Polygon {
221 #[cfg(feature = "pyo3")]
223 #[pyo3(get, set)]
224 pub border: Option<Border>,
225 #[cfg(not(feature = "pyo3"))]
227 pub border: Option<Border>,
228
229 #[cfg(feature = "pyo3")]
231 #[pyo3(get, set)]
232 #[serde(default = "ColorKind::transparent_default")]
233 pub color: ColorKind,
234
235 #[cfg(not(feature = "pyo3"))]
237 #[serde(default = "ColorKind::transparent_default")]
238 pub color: ColorKind,
239
240 #[cfg(feature = "pyo3")]
244 #[pyo3(get, set)]
245 #[serde(
246 default,
247 deserialize_with = "PolygonSides::deserialize",
248 serialize_with = "PolygonSides::serialize"
249 )]
250 pub sides: PolygonSides,
251
252 #[cfg(not(feature = "pyo3"))]
256 #[serde(
257 default,
258 deserialize_with = "PolygonSides::deserialize",
259 serialize_with = "PolygonSides::serialize"
260 )]
261 pub sides: PolygonSides,
262
263 #[cfg(feature = "pyo3")]
267 #[pyo3(get, set)]
268 #[serde(default)]
269 pub rotation: f32,
270 #[cfg(not(feature = "pyo3"))]
274 #[serde(default)]
275 pub rotation: f32,
276}
277
278#[cfg(test)]
279mod test {
280 #![allow(clippy::unwrap_used, clippy::panic)]
281
282 use super::{IrregularPolygonSides, PolygonSides, RegularPolygonSides};
283 use crate::LayerOffset;
284
285 #[test]
286 fn sides() {
287 assert!(RegularPolygonSides::new(2).is_none());
288 assert!(matches!(PolygonSides::default(), PolygonSides::Regular(v) if v.get() == 3));
289 assert!(IrregularPolygonSides::new(vec![LayerOffset::default(); 2]).is_none());
290
291 assert!(RegularPolygonSides::new(2).is_none());
292 assert!(RegularPolygonSides::new(3).is_some());
293 assert!(IrregularPolygonSides::new(vec![LayerOffset::default(); 2]).is_none());
294 assert!(
295 IrregularPolygonSides::new(vec![
296 LayerOffset { x: 0, y: 0 },
297 LayerOffset { x: 100, y: 0 },
298 LayerOffset { x: 50, y: 100 },
299 ])
300 .is_some()
301 );
302
303 let regular: PolygonSides = RegularPolygonSides::new(3).unwrap().into();
304 assert!(matches!(regular, PolygonSides::Regular(v) if v.get() == 3));
305
306 let irregular: PolygonSides = IrregularPolygonSides::new(vec![
307 LayerOffset { x: 0, y: 0 },
308 LayerOffset { x: 100, y: 0 },
309 LayerOffset { x: 50, y: 100 },
310 ])
311 .unwrap()
312 .into();
313 assert!(matches!(irregular, PolygonSides::Irregular(v) if v.as_slice().len() == 3));
314 }
315
316 #[test]
317 fn deserialize_sides() {
318 use serde::de::{IntoDeserializer, value::U32Deserializer};
319
320 let yaml = 2_u32;
321 let deserializer: U32Deserializer<serde_saphyr::Error> = yaml.into_deserializer();
322 let deserialized = PolygonSides::deserialize(deserializer)
323 .unwrap_err()
324 .to_string();
325 eprintln!("Deserialization error: {deserialized}");
326 assert!(deserialized.contains("Regular Polygons cannot have less than 3 sides"));
327
328 let yaml = 5_u32;
329 let deserializer: U32Deserializer<serde_saphyr::Error> = yaml.into_deserializer();
330 let deserialized = PolygonSides::deserialize(deserializer).unwrap();
331 assert!(matches!(deserialized, PolygonSides::Regular(v) if v.get() == 5));
332 }
333
334 #[test]
335 fn deserialize_irregular_sides() {
336 use serde::Deserialize;
337
338 #[derive(Debug, Deserialize)]
339 struct TestPolygonSides {
340 #[serde(deserialize_with = "PolygonSides::deserialize")]
341 sides: PolygonSides,
342 }
343
344 let expected = [
345 LayerOffset { x: 0, y: 0 },
346 LayerOffset { x: 100, y: 0 },
347 LayerOffset { x: 50, y: 100 },
348 ];
349
350 let yaml = "sides:\n - { x: 0, y: 0 }\n - { x: 100, y: 0 }\n - { x: 50, y: 100 }\n";
351 let deserialized: TestPolygonSides = serde_saphyr::from_str(yaml).unwrap();
352
353 if let PolygonSides::Irregular(points) = deserialized.sides {
354 let points = points.as_slice();
355 assert!(points.len() == expected.len());
356 assert!(points[0].x == expected[0].x && points[0].y == expected[0].y);
357 assert!(points[1].x == expected[1].x && points[1].y == expected[1].y);
358 assert!(points[2].x == expected[2].x && points[2].y == expected[2].y);
359 } else {
360 panic!("Expected irregular polygon sides");
361 }
362 }
363
364 #[test]
365 fn deserialize_irregular_sides_invalid_len() {
366 use serde::de::value::{Error, MapDeserializer, SeqDeserializer};
367
368 let first = MapDeserializer::<_, Error>::new([("x", 0_i32), ("y", 0_i32)].into_iter());
369 let second = MapDeserializer::<_, Error>::new([("x", 100_i32), ("y", 0_i32)].into_iter());
370 let deserializer = SeqDeserializer::<_, Error>::new([first, second].into_iter());
371
372 let error = PolygonSides::deserialize(deserializer)
373 .unwrap_err()
374 .to_string();
375 assert!(error.contains("Irregular Polygons cannot have less than 3 unique points"));
376 }
377
378 #[test]
379 fn deserialize_sides_invalid_str() {
380 use serde::de::value::StrDeserializer;
381
382 let value = StrDeserializer::<serde_saphyr::Error>::new("not-a-valid-polygon-sides");
383 let error = PolygonSides::deserialize(value).unwrap_err().to_string();
384 assert!(error.contains("an integer >= 3 or a sequence of at least 3 unique offsets"));
385 }
386
387 #[test]
388 fn deserialize_regular_polygon_sides_validates_minimum() {
389 use serde::Deserialize;
390 use serde::de::{IntoDeserializer, value::U32Deserializer};
391
392 let deserializer: U32Deserializer<serde_saphyr::Error> = 2_u32.into_deserializer();
393 let error = RegularPolygonSides::deserialize(deserializer)
394 .unwrap_err()
395 .to_string();
396 assert!(error.contains("Regular Polygons cannot have less than 3 sides"));
397 }
398
399 #[test]
400 fn deserialize_irregular_polygon_sides_validates_minimum_unique_points() {
401 let yaml = "- { x: 0, y: 0 }\n- { x: 0, y: 0 }\n- { x: 100, y: 0 }\n";
402 let error = serde_saphyr::from_str::<IrregularPolygonSides>(yaml)
403 .unwrap_err()
404 .to_string();
405 assert!(error.contains("Irregular Polygons cannot have less than 3 unique points"));
406 }
407
408 #[test]
409 fn polygon_sides_deserialize_trait_validates_regular_minimum() {
410 let err = serde_json::from_str::<PolygonSides>("2")
411 .unwrap_err()
412 .to_string();
413 assert!(err.contains("Regular Polygons cannot have less than 3 sides"));
414
415 let parsed = serde_json::from_str::<PolygonSides>("5").unwrap();
416 assert!(matches!(parsed, PolygonSides::Regular(v) if v.get() == 5));
417 }
418
419 #[test]
420 fn polygon_sides_deserialize_trait_validates_irregular_unique_points() {
421 let err = serde_json::from_str::<PolygonSides>(
422 r#"[{"x":0,"y":0},{"x":0,"y":0},{"x":100,"y":0}]"#,
423 )
424 .unwrap_err()
425 .to_string();
426 assert!(err.contains("Irregular Polygons cannot have less than 3 unique points"));
427
428 let parsed = serde_json::from_str::<PolygonSides>(
429 r#"[{"x":0,"y":0},{"x":100,"y":0},{"x":50,"y":100}]"#,
430 )
431 .unwrap();
432 assert!(matches!(parsed, PolygonSides::Irregular(v) if v.as_slice().len() == 3));
433 }
434
435 #[test]
436 fn polygon_sides_serialize_trait_emits_expected_shape() {
437 let regular: PolygonSides = RegularPolygonSides::new(6).unwrap().into();
438 assert_eq!(
439 serde_json::to_value(®ular).unwrap(),
440 serde_json::json!(6)
441 );
442
443 let irregular: PolygonSides = IrregularPolygonSides::new(vec![
444 LayerOffset { x: 0, y: 0 },
445 LayerOffset { x: 100, y: 0 },
446 LayerOffset { x: 50, y: 100 },
447 ])
448 .unwrap()
449 .into();
450 assert_eq!(
451 serde_json::to_value(&irregular).unwrap(),
452 serde_json::json!([
453 { "x": 0, "y": 0 },
454 { "x": 100, "y": 0 },
455 { "x": 50, "y": 100 }
456 ])
457 );
458 }
459}