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(
238 default = "ColorKind::transparent_default",
239 alias = "linear_gradient",
240 alias = "radial_gradient",
241 alias = "conical_gradient",
242 alias = "linear-gradient",
243 alias = "radial-gradient",
244 alias = "conical-gradient"
245 )]
246 pub color: ColorKind,
247
248 #[cfg(feature = "pyo3")]
252 #[pyo3(get, set)]
253 #[serde(
254 default,
255 deserialize_with = "PolygonSides::deserialize",
256 serialize_with = "PolygonSides::serialize"
257 )]
258 pub sides: PolygonSides,
259
260 #[cfg(not(feature = "pyo3"))]
264 #[serde(
265 default,
266 deserialize_with = "PolygonSides::deserialize",
267 serialize_with = "PolygonSides::serialize"
268 )]
269 pub sides: PolygonSides,
270
271 #[cfg(feature = "pyo3")]
275 #[pyo3(get, set)]
276 #[serde(default)]
277 pub rotation: f32,
278 #[cfg(not(feature = "pyo3"))]
282 #[serde(default)]
283 pub rotation: f32,
284}
285
286#[cfg(test)]
287mod test {
288 #![allow(clippy::unwrap_used, clippy::panic)]
289
290 use super::{IrregularPolygonSides, PolygonSides, RegularPolygonSides};
291 use crate::LayerOffset;
292
293 #[test]
294 fn sides() {
295 assert!(RegularPolygonSides::new(2).is_none());
296 assert!(matches!(PolygonSides::default(), PolygonSides::Regular(v) if v.get() == 3));
297 assert!(IrregularPolygonSides::new(vec![LayerOffset::default(); 2]).is_none());
298
299 assert!(RegularPolygonSides::new(2).is_none());
300 assert!(RegularPolygonSides::new(3).is_some());
301 assert!(IrregularPolygonSides::new(vec![LayerOffset::default(); 2]).is_none());
302 assert!(
303 IrregularPolygonSides::new(vec![
304 LayerOffset { x: 0, y: 0 },
305 LayerOffset { x: 100, y: 0 },
306 LayerOffset { x: 50, y: 100 },
307 ])
308 .is_some()
309 );
310
311 let regular: PolygonSides = RegularPolygonSides::new(3).unwrap().into();
312 assert!(matches!(regular, PolygonSides::Regular(v) if v.get() == 3));
313
314 let irregular: PolygonSides = IrregularPolygonSides::new(vec![
315 LayerOffset { x: 0, y: 0 },
316 LayerOffset { x: 100, y: 0 },
317 LayerOffset { x: 50, y: 100 },
318 ])
319 .unwrap()
320 .into();
321 assert!(matches!(irregular, PolygonSides::Irregular(v) if v.as_slice().len() == 3));
322 }
323
324 #[test]
325 fn deserialize_sides() {
326 use serde::de::{IntoDeserializer, value::U32Deserializer};
327
328 let yaml = 2_u32;
329 let deserializer: U32Deserializer<serde_saphyr::Error> = yaml.into_deserializer();
330 let deserialized = PolygonSides::deserialize(deserializer)
331 .unwrap_err()
332 .to_string();
333 eprintln!("Deserialization error: {deserialized}");
334 assert!(deserialized.contains("Regular Polygons cannot have less than 3 sides"));
335
336 let yaml = 5_u32;
337 let deserializer: U32Deserializer<serde_saphyr::Error> = yaml.into_deserializer();
338 let deserialized = PolygonSides::deserialize(deserializer).unwrap();
339 assert!(matches!(deserialized, PolygonSides::Regular(v) if v.get() == 5));
340 }
341
342 #[test]
343 fn deserialize_irregular_sides() {
344 use serde::Deserialize;
345
346 #[derive(Debug, Deserialize)]
347 struct TestPolygonSides {
348 #[serde(deserialize_with = "PolygonSides::deserialize")]
349 sides: PolygonSides,
350 }
351
352 let expected = [
353 LayerOffset { x: 0, y: 0 },
354 LayerOffset { x: 100, y: 0 },
355 LayerOffset { x: 50, y: 100 },
356 ];
357
358 let yaml = "sides:\n - { x: 0, y: 0 }\n - { x: 100, y: 0 }\n - { x: 50, y: 100 }\n";
359 let deserialized: TestPolygonSides = serde_saphyr::from_str(yaml).unwrap();
360
361 if let PolygonSides::Irregular(points) = deserialized.sides {
362 let points = points.as_slice();
363 assert!(points.len() == expected.len());
364 assert!(points[0].x == expected[0].x && points[0].y == expected[0].y);
365 assert!(points[1].x == expected[1].x && points[1].y == expected[1].y);
366 assert!(points[2].x == expected[2].x && points[2].y == expected[2].y);
367 } else {
368 panic!("Expected irregular polygon sides");
369 }
370 }
371
372 #[test]
373 fn deserialize_irregular_sides_invalid_len() {
374 use serde::de::value::{Error, MapDeserializer, SeqDeserializer};
375
376 let first = MapDeserializer::<_, Error>::new([("x", 0_i32), ("y", 0_i32)].into_iter());
377 let second = MapDeserializer::<_, Error>::new([("x", 100_i32), ("y", 0_i32)].into_iter());
378 let deserializer = SeqDeserializer::<_, Error>::new([first, second].into_iter());
379
380 let error = PolygonSides::deserialize(deserializer)
381 .unwrap_err()
382 .to_string();
383 assert!(error.contains("Irregular Polygons cannot have less than 3 unique points"));
384 }
385
386 #[test]
387 fn deserialize_sides_invalid_str() {
388 use serde::de::value::StrDeserializer;
389
390 let value = StrDeserializer::<serde_saphyr::Error>::new("not-a-valid-polygon-sides");
391 let error = PolygonSides::deserialize(value).unwrap_err().to_string();
392 assert!(error.contains("an integer >= 3 or a sequence of at least 3 unique offsets"));
393 }
394
395 #[test]
396 fn deserialize_regular_polygon_sides_validates_minimum() {
397 use serde::Deserialize;
398 use serde::de::{IntoDeserializer, value::U32Deserializer};
399
400 let deserializer: U32Deserializer<serde_saphyr::Error> = 2_u32.into_deserializer();
401 let error = RegularPolygonSides::deserialize(deserializer)
402 .unwrap_err()
403 .to_string();
404 assert!(error.contains("Regular Polygons cannot have less than 3 sides"));
405 }
406
407 #[test]
408 fn deserialize_irregular_polygon_sides_validates_minimum_unique_points() {
409 let yaml = "- { x: 0, y: 0 }\n- { x: 0, y: 0 }\n- { x: 100, y: 0 }\n";
410 let error = serde_saphyr::from_str::<IrregularPolygonSides>(yaml)
411 .unwrap_err()
412 .to_string();
413 assert!(error.contains("Irregular Polygons cannot have less than 3 unique points"));
414 }
415
416 #[test]
417 fn polygon_sides_deserialize_trait_validates_regular_minimum() {
418 let err = serde_json::from_str::<PolygonSides>("2")
419 .unwrap_err()
420 .to_string();
421 assert!(err.contains("Regular Polygons cannot have less than 3 sides"));
422
423 let parsed = serde_json::from_str::<PolygonSides>("5").unwrap();
424 assert!(matches!(parsed, PolygonSides::Regular(v) if v.get() == 5));
425 }
426
427 #[test]
428 fn polygon_sides_deserialize_trait_validates_irregular_unique_points() {
429 let err = serde_json::from_str::<PolygonSides>(
430 r#"[{"x":0,"y":0},{"x":0,"y":0},{"x":100,"y":0}]"#,
431 )
432 .unwrap_err()
433 .to_string();
434 assert!(err.contains("Irregular Polygons cannot have less than 3 unique points"));
435
436 let parsed = serde_json::from_str::<PolygonSides>(
437 r#"[{"x":0,"y":0},{"x":100,"y":0},{"x":50,"y":100}]"#,
438 )
439 .unwrap();
440 assert!(matches!(parsed, PolygonSides::Irregular(v) if v.as_slice().len() == 3));
441 }
442
443 #[test]
444 fn polygon_sides_serialize_trait_emits_expected_shape() {
445 let regular: PolygonSides = RegularPolygonSides::new(6).unwrap().into();
446 assert_eq!(
447 serde_json::to_value(®ular).unwrap(),
448 serde_json::json!(6)
449 );
450
451 let irregular: PolygonSides = IrregularPolygonSides::new(vec![
452 LayerOffset { x: 0, y: 0 },
453 LayerOffset { x: 100, y: 0 },
454 LayerOffset { x: 50, y: 100 },
455 ])
456 .unwrap()
457 .into();
458 assert_eq!(
459 serde_json::to_value(&irregular).unwrap(),
460 serde_json::json!([
461 { "x": 0, "y": 0 },
462 { "x": 100, "y": 0 },
463 { "x": 50, "y": 100 }
464 ])
465 );
466 }
467}