1use std::ops::Range;
6
7use crate::{
8 impl_default_coercion_rule,
9 math::{Transform2, Vector2},
10 Color, ColorChannel, Fill, GradientStop, LinearGradient, Numeric, PathElement, PaxValue,
11 Percent, Property, RadialGradient, Rotation, Size, Stroke, Transform2D,
12};
13
14impl_default_coercion_rule!(bool, PaxValue::Bool);
17
18impl_default_coercion_rule!(u8, PaxValue::Numeric);
19impl_default_coercion_rule!(u16, PaxValue::Numeric);
20impl_default_coercion_rule!(u32, PaxValue::Numeric);
21impl_default_coercion_rule!(u64, PaxValue::Numeric);
22
23impl_default_coercion_rule!(i8, PaxValue::Numeric);
24impl_default_coercion_rule!(i16, PaxValue::Numeric);
25impl_default_coercion_rule!(i32, PaxValue::Numeric);
26impl_default_coercion_rule!(i64, PaxValue::Numeric);
27
28impl_default_coercion_rule!(f32, PaxValue::Numeric);
29impl_default_coercion_rule!(f64, PaxValue::Numeric);
30
31impl_default_coercion_rule!(isize, PaxValue::Numeric);
32impl_default_coercion_rule!(usize, PaxValue::Numeric);
33
34pub trait CoercionRules
35where
36 Self: Sized + 'static,
37{
38 fn try_coerce(value: PaxValue) -> Result<Self, String>;
39}
40
41impl CoercionRules for Color {
84 fn try_coerce(value: PaxValue) -> Result<Self, String> {
85 match value {
86 PaxValue::Color(color) => Ok(*color),
87 _ => return Err(format!("{:?} can't be coerced into a Color", value)),
88 }
89 }
90}
91
92impl CoercionRules for PathElement {
93 fn try_coerce(value: PaxValue) -> Result<Self, String> {
94 let err = format!("{:?} can't be coerced into a PathElement", value);
95 match value {
96 PaxValue::PathElement(path_elem) => Ok(path_elem),
97 PaxValue::Enum(enum_name, enum_variant, values) => {
99 if enum_name == "PathElement" {
100 let mut values_itr = values.into_iter();
101 match enum_variant.as_str() {
102 "Line" => Ok(PathElement::Line),
103 "Close" => Ok(PathElement::Close),
104 "Empty" => Ok(PathElement::Empty),
105 "Point" => Ok(PathElement::Point(
106 Size::try_coerce(values_itr.next().unwrap())?,
107 Size::try_coerce(values_itr.next().unwrap())?,
108 )),
109 "Quadratic" => Ok(PathElement::Quadratic(
110 Size::try_coerce(values_itr.next().unwrap())?,
111 Size::try_coerce(values_itr.next().unwrap())?,
112 )),
113 "Cubic" => Ok(PathElement::Cubic(Box::new((
114 Size::try_coerce(values_itr.next().unwrap())?,
115 Size::try_coerce(values_itr.next().unwrap())?,
116 Size::try_coerce(values_itr.next().unwrap())?,
117 Size::try_coerce(values_itr.next().unwrap())?,
118 )))),
119 _ => return Err(err),
120 }
121 } else {
122 return Err(err);
123 }
124 }
125 _ => return Err(err),
126 }
127 }
128}
129
130impl CoercionRules for Fill {
134 fn try_coerce(pax_value: PaxValue) -> Result<Self, String> {
135 Ok(match pax_value.clone() {
136 PaxValue::Color(color) => Fill::Solid(*color),
137 PaxValue::Enum(_, variant, args) => match variant.as_str() {
138 "Solid" => {
139 let color = Color::try_coerce(args[0].clone())?;
140 Fill::Solid(color)
141 }
142 "LinearGradient" => {
143 let gradient = LinearGradient::try_coerce(args[0].clone())?;
144 Fill::LinearGradient(gradient)
145 }
146 "RadialGradient" => {
147 let gradient = RadialGradient::try_coerce(args[0].clone())?;
148 Fill::RadialGradient(gradient)
149 }
150 _ => return Err(format!("{:?} can't be coerced into a Fill", pax_value)),
151 },
152 PaxValue::Option(mut o) => {
153 if let Some(o) = o.take() {
154 Fill::try_coerce(o)?
155 } else {
156 return Err(format!("{:?} can't be coerced into a Fill", pax_value));
157 }
158 }
159 _ => return Err(format!("{:?} can't be coerced into a Fill", pax_value)),
160 })
161 }
162}
163
164impl CoercionRules for LinearGradient {
165 fn try_coerce(pax_value: PaxValue) -> Result<Self, String> {
166 Ok(match pax_value.clone() {
167 PaxValue::Object(map) => {
168 let start = map
169 .iter()
170 .find_map(|(n, v)| (n == "start").then_some(v))
171 .unwrap()
172 .clone();
173 let (s1, s2) = match start {
174 PaxValue::Vec(vec) => {
175 let s1 = Size::try_coerce(vec[0].clone())?;
176 let s2 = Size::try_coerce(vec[1].clone())?;
177 (s1, s2)
178 }
179 _ => {
180 return Err(format!(
181 "{:?} can't be coerced into a LinearGradient",
182 pax_value
183 ))
184 }
185 };
186
187 let end = map
188 .iter()
189 .find_map(|(n, v)| (n == "end").then_some(v))
190 .unwrap()
191 .clone();
192 let (e1, e2) = match end {
193 PaxValue::Vec(vec) => {
194 let e1 = Size::try_coerce(vec[0].clone())?;
195 let e2 = Size::try_coerce(vec[1].clone())?;
196 (e1, e2)
197 }
198 _ => {
199 return Err(format!(
200 "{:?} can't be coerced into a LinearGradient",
201 pax_value
202 ))
203 }
204 };
205 let stops = Vec::<GradientStop>::try_coerce(
206 map.iter()
207 .find_map(|(n, v)| (n == "stops").then_some(v))
208 .unwrap()
209 .clone(),
210 )?;
211 LinearGradient {
212 start: (s1, s2),
213 end: (e1, e2),
214 stops,
215 }
216 }
217 PaxValue::Option(mut o) => {
218 if let Some(o) = o.take() {
219 LinearGradient::try_coerce(o)?
220 } else {
221 return Err(format!(
222 "{:?} can't be coerced into a LinearGradient",
223 pax_value
224 ));
225 }
226 }
227 _ => {
228 return Err(format!(
229 "{:?} can't be coerced into a LinearGradient",
230 pax_value
231 ))
232 }
233 })
234 }
235}
236
237impl CoercionRules for RadialGradient {
238 fn try_coerce(pax_value: PaxValue) -> Result<Self, String> {
239 Ok(match pax_value.clone() {
240 PaxValue::Object(map) => {
241 let start = map
242 .iter()
243 .find_map(|(n, v)| (n == "start").then_some(v))
244 .unwrap()
245 .clone();
246 let (s1, s2) = match start {
247 PaxValue::Vec(vec) => {
248 let s1 = Size::try_coerce(vec[0].clone())?;
249 let s2 = Size::try_coerce(vec[1].clone())?;
250 (s1, s2)
251 }
252 _ => {
253 return Err(format!(
254 "{:?} can't be coerced into a RadialGradient",
255 pax_value
256 ))
257 }
258 };
259
260 let end = map
261 .iter()
262 .find_map(|(n, v)| (n == "end").then_some(v))
263 .unwrap()
264 .clone();
265 let (e1, e2) = match end {
266 PaxValue::Vec(vec) => {
267 let e1 = Size::try_coerce(vec[0].clone())?;
268 let e2 = Size::try_coerce(vec[1].clone())?;
269 (e1, e2)
270 }
271 _ => {
272 return Err(format!(
273 "{:?} can't be coerced into a RadialGradient",
274 pax_value
275 ))
276 }
277 };
278 let radius = match map
279 .iter()
280 .find_map(|(n, v)| (n == "radius").then_some(v))
281 .unwrap()
282 .clone()
283 {
284 PaxValue::Numeric(n) => n.to_float(),
285 _ => {
286 return Err(format!(
287 "{:?} can't be coerced into a RadialGradient",
288 pax_value
289 ))
290 }
291 };
292 let stops = Vec::<GradientStop>::try_coerce(
293 map.iter()
294 .find_map(|(n, v)| (n == "stops").then_some(v))
295 .unwrap()
296 .clone(),
297 )?;
298 RadialGradient {
299 start: (s1, s2),
300 end: (e1, e2),
301 radius,
302 stops,
303 }
304 }
305 PaxValue::Option(mut o) => {
306 if let Some(o) = o.take() {
307 RadialGradient::try_coerce(o)?
308 } else {
309 return Err(format!(
310 "{:?} can't be coerced into a RadialGradient",
311 pax_value
312 ));
313 }
314 }
315 _ => {
316 return Err(format!(
317 "{:?} can't be coerced into a RadialGradient",
318 pax_value
319 ))
320 }
321 })
322 }
323}
324
325impl CoercionRules for GradientStop {
326 fn try_coerce(pax_value: PaxValue) -> Result<Self, String> {
327 Ok(match pax_value {
328 PaxValue::Object(map) => {
329 let position = Size::try_coerce(
330 map.iter()
331 .find_map(|(n, v)| (n == "position").then_some(v))
332 .unwrap()
333 .clone(),
334 )?;
335 let color = Color::try_coerce(
336 map.iter()
337 .find_map(|(n, v)| (n == "color").then_some(v))
338 .unwrap()
339 .clone(),
340 )?;
341 GradientStop { position, color }
342 }
343 PaxValue::Option(mut o) => {
344 if let Some(o) = o.take() {
345 GradientStop::try_coerce(o)?
346 } else {
347 return Err(format!("None can't be coerced into a GradientStop"));
348 }
349 }
350 _ => {
351 return Err(format!(
352 "{:?} can't be coerced into a GradientStop",
353 pax_value
354 ))
355 }
356 })
357 }
358}
359
360impl CoercionRules for Stroke {
361 fn try_coerce(pax_value: PaxValue) -> Result<Self, String> {
362 Ok(match pax_value {
363 PaxValue::Color(color) => Stroke {
364 color: Property::new(*color),
365 width: Property::new(Size::Pixels(1.into())),
366 },
367 PaxValue::Object(map) => {
368 let color = Property::new(Color::try_coerce(
369 map.iter()
370 .find_map(|(n, v)| (n == "color").then_some(v))
371 .cloned()
372 .unwrap_or_default(),
373 )?);
374 let width = Property::new(Size::try_coerce(
375 map.iter()
376 .find_map(|(n, v)| (n == "width").then_some(v))
377 .cloned()
378 .unwrap_or(PaxValue::Size(Size::Pixels(1.into()))),
379 )?);
380 Stroke { color, width }
381 }
382 PaxValue::Option(mut o) => {
383 if let Some(o) = o.take() {
384 Stroke::try_coerce(o)?
385 } else {
386 return Err(format!("None can't be coerced into a Stroke"));
387 }
388 }
389 _ => return Err(format!("{:?} can't be coerced into a Stroke", pax_value)),
390 })
391 }
392}
393
394impl CoercionRules for ColorChannel {
395 fn try_coerce(value: PaxValue) -> Result<Self, String> {
396 Ok(match value.clone() {
397 PaxValue::Rotation(rot) => ColorChannel::Rotation(rot),
398 PaxValue::Percent(perc) => ColorChannel::Percent(perc.0),
399 PaxValue::Numeric(num) => ColorChannel::Integer(num.to_int().clamp(0, 255) as u8),
400 PaxValue::Enum(_, variant, args) => match variant.as_str() {
401 "Rotation" => {
402 let rot = Rotation::try_coerce(args[0].clone())?;
403 ColorChannel::Rotation(rot)
404 }
405 "Integer" => {
406 let num = Numeric::try_coerce(args[0].clone())?;
407 ColorChannel::Integer(num.to_int().clamp(0, 255) as u8)
408 }
409 "Percent" => {
410 let num = Numeric::try_coerce(args[0].clone())?;
411 ColorChannel::Percent(num)
412 }
413 _ => return Err(format!("{:?} can't be coerced into a ColorChannel", value)),
414 },
415 PaxValue::Option(mut o) => {
416 if let Some(o) = o.take() {
417 ColorChannel::try_coerce(o)?
418 } else {
419 return Err(format!("{:?} can't be coerced into a ColorChannel", value));
420 }
421 }
422 _ => return Err(format!("{:?} can't be coerced into a ColorChannel", value)),
423 })
424 }
425}
426
427impl CoercionRules for Percent {
428 fn try_coerce(pax_value: PaxValue) -> Result<Self, String> {
429 Ok(match pax_value {
430 PaxValue::Percent(p) => p,
431 PaxValue::Numeric(v) => Percent(v),
432 PaxValue::Option(mut opt) => {
433 if let Some(p) = opt.take() {
434 Percent::try_coerce(p)?
435 } else {
436 return Err(format!("None can't be coerced into a Percent"));
437 }
438 }
439 _ => return Err(format!("{:?} can't be coerced into a Percent", pax_value)),
440 })
441 }
442}
443
444impl CoercionRules for Size {
445 fn try_coerce(pax_value: PaxValue) -> Result<Self, String> {
446 Ok(match pax_value {
447 PaxValue::Size(size) => size,
448 PaxValue::Percent(p) => Size::Percent(p.0),
449 PaxValue::Numeric(v) => Size::Pixels(v),
450 PaxValue::Option(mut opt) => {
451 if let Some(p) = opt.take() {
452 Size::try_coerce(p)?
453 } else {
454 return Err(format!("None can't be coerced into a Size"));
455 }
456 }
457 _ => return Err(format!("{:?} can't be coerced into a Size", pax_value)),
458 })
459 }
460}
461
462impl CoercionRules for String {
463 fn try_coerce(pax_value: PaxValue) -> Result<Self, String> {
464 Ok(match pax_value {
465 PaxValue::String(s) => s,
466 PaxValue::Numeric(n) => {
467 if n.is_float() {
468 n.to_float().to_string()
469 } else {
470 n.to_int().to_string()
471 }
472 }
473 PaxValue::Option(mut opt) => {
474 if let Some(p) = opt.take() {
475 String::try_coerce(p)?
476 } else {
477 return Err(format!("None can't be coerced into a String"));
478 }
479 }
480 _ => return Err(format!("{:?} can't be coerced into a String", pax_value)),
481 })
482 }
483}
484
485impl CoercionRules for Rotation {
486 fn try_coerce(pax_value: PaxValue) -> Result<Self, String> {
487 Ok(match pax_value {
488 PaxValue::Rotation(r) => r,
489 PaxValue::Numeric(n) => Rotation::Degrees(n),
490 PaxValue::Percent(p) => Rotation::Percent(p.0),
491 PaxValue::Option(mut opt) => {
492 if let Some(p) = opt.take() {
493 Rotation::try_coerce(p)?
494 } else {
495 return Err(format!("None can't be coerced into a Rotation"));
496 }
497 }
498 _ => return Err(format!("{:?} can't be coerced into a Rotation", pax_value)),
499 })
500 }
501}
502
503impl CoercionRules for Numeric {
504 fn try_coerce(pax_value: PaxValue) -> Result<Self, String> {
505 Ok(match pax_value {
506 PaxValue::Bool(b) => (b as i32).into(),
507 PaxValue::Numeric(n) => n.into(),
508 PaxValue::Size(n) => n.into(),
509 PaxValue::Option(mut opt) => {
510 if let Some(p) = opt.take() {
511 Numeric::try_coerce(p)?
512 } else {
513 return Err(format!("None can't be coerced into a Numeric"));
514 }
515 }
516 _ => return Err(format!("{:?} can't be coerced into a Numeric", pax_value)),
517 })
518 }
519}
520
521impl<T: CoercionRules> CoercionRules for Vec<T> {
522 fn try_coerce(value: PaxValue) -> Result<Self, String> {
523 match value {
524 PaxValue::Vec(vec) => {
525 let res: Result<Vec<T>, _> = vec.into_iter().map(|v| T::try_coerce(v)).collect();
526 res.map_err(|e| format!("couldn't coerce vec, element {:?}", e))
527 }
528 PaxValue::Option(mut opt) => {
529 if let Some(p) = opt.take() {
530 Vec::<T>::try_coerce(p)
531 } else {
532 return Err(format!("None can't be coerced into a Vec"));
533 }
534 }
535 v => Err(format!(
536 "{:?} can't be coerced into {:?}",
537 v,
538 std::any::type_name::<Vec<T>>(),
539 )),
540 }
541 }
542}
543
544impl<T1: CoercionRules, T2: CoercionRules> CoercionRules for (T1, T2) {
545 fn try_coerce(value: PaxValue) -> Result<Self, String> {
546 match value {
547 PaxValue::Vec(vec) => {
548 let res: Result<T1, _> = T1::try_coerce(vec[0].clone());
549 let res2: Result<T2, _> = T2::try_coerce(vec[1].clone());
550 res.and_then(|v1| res2.map(|v2| (v1, v2)))
551 }
552 PaxValue::Option(mut opt) => {
553 if let Some(p) = opt.take() {
554 <(T1, T2)>::try_coerce(p)
555 } else {
556 return Err(format!("None can't be coerced into a Vec"));
557 }
558 }
559 v => Err(format!(
560 "{:?} can't be coerced into {:?}",
561 v,
562 std::any::type_name::<(T1, T2)>(),
563 )),
564 }
565 }
566}
567
568impl<T: CoercionRules> CoercionRules for Option<T> {
569 fn try_coerce(value: PaxValue) -> Result<Self, String> {
570 match value {
571 PaxValue::Option(opt) => {
572 let res: Result<Option<T>, _> = opt.map(|v| T::try_coerce(v)).transpose();
573 res.map_err(|e| format!("couldn't coerce option, element {:?}", e))
574 }
575 v => Some(T::try_coerce(v)).transpose(),
576 }
577 }
578}
579
580impl<T: CoercionRules> CoercionRules for Range<T> {
581 fn try_coerce(value: PaxValue) -> Result<Self, String> {
582 match value {
583 PaxValue::Range(start, end) => {
584 let start = T::try_coerce(*start)?;
585 let end = T::try_coerce(*end)?;
586 Ok(start..end)
587 }
588 PaxValue::Option(mut opt) => {
589 if let Some(p) = opt.take() {
590 Range::<T>::try_coerce(p)
591 } else {
592 return Err(format!("None can't be coerced into a Range"));
593 }
594 }
595 v => Err(format!(
596 "{:?} can't be coerced into {:?}",
597 v,
598 std::any::type_name::<Range<T>>(),
599 )),
600 }
601 }
602}
603
604impl CoercionRules for PaxValue {
605 fn try_coerce(value: PaxValue) -> Result<Self, String> {
606 Ok(value)
607 }
608}
609
610impl<T: CoercionRules> CoercionRules for Box<T> {
611 fn try_coerce(value: PaxValue) -> Result<Self, String> {
612 Ok(Box::new(T::try_coerce(value)?))
613 }
614}
615
616impl CoercionRules for Transform2D {
617 fn try_coerce(value: PaxValue) -> Result<Self, String> {
618 Ok(match value {
619 PaxValue::Option(mut opt) => {
620 if let Some(t) = opt.take() {
621 Transform2D::try_coerce(t)?
622 } else {
623 return Err(format!("None can't be coerced into a Transform2D"));
624 }
625 }
626 PaxValue::Object(map) => {
627 let previous = match map.iter().find_map(|(n, v)| (n == "previous").then_some(v)) {
628 Some(p) => Option::<Box<Transform2D>>::try_coerce(p.clone())?,
629 None => None,
630 };
631 let rotate = match map.iter().find_map(|(n, v)| (n == "rotate").then_some(v)) {
632 Some(r) => Option::<Rotation>::try_coerce(r.clone())?,
633 None => None,
634 };
635 let translate = match map
636 .iter()
637 .find_map(|(n, v)| (n == "translate").then_some(v))
638 {
639 Some(t) => match t.clone() {
640 PaxValue::Option(mut opt) => {
641 if let Some(t) = opt.take() {
642 let t = Vec::<Size>::try_coerce(t.clone())?;
643 if t.len() != 2 {
644 return Err(format!(
645 "expected 2 elements in translate, got {:?}",
646 t.len()
647 ));
648 }
649 Some([t[0], t[1]])
650 } else {
651 None
652 }
653 }
654 _ => {
655 return Err(format!(
656 "translate of {:?} can't be coerced into a Transform2D",
657 t
658 ))
659 }
660 },
661 None => None,
662 };
663 let anchor = match map.iter().find_map(|(n, v)| (n == "anchor").then_some(v)) {
664 Some(a) => match a.clone() {
665 PaxValue::Option(mut opt) => {
666 if let Some(a) = opt.take() {
667 let a = Vec::<Size>::try_coerce(a.clone())?;
668 if a.len() != 2 {
669 return Err(format!(
670 "expected 2 elements in anchor, got {:?}",
671 a.len()
672 ));
673 }
674 Some([a[0], a[1]])
675 } else {
676 None
677 }
678 }
679 _ => {
680 return Err(format!(
681 "anchor of {:?} can't be coerced into a Transform2D",
682 a
683 ))
684 }
685 },
686 None => None,
687 };
688 let scale = match map.iter().find_map(|(n, v)| (n == "scale").then_some(v)) {
689 Some(s) => match s.clone() {
690 PaxValue::Option(mut opt) => {
691 if let Some(s) = opt.take() {
692 let s = Vec::<Size>::try_coerce(s.clone())?;
693 if s.len() != 2 {
694 return Err(format!(
695 "expected 2 elements in scale, got {:?}",
696 s.len()
697 ));
698 }
699 Some([s[0], s[1]])
700 } else {
701 None
702 }
703 }
704 _ => {
705 return Err(format!(
706 "scale of {:?} can't be coerced into a Transform2D",
707 s
708 ))
709 }
710 },
711 None => None,
712 };
713 let skew = match map.iter().find_map(|(n, v)| (n == "skew").then_some(v)) {
714 Some(s) => match s.clone() {
715 PaxValue::Option(mut opt) => {
716 if let Some(s) = opt.take() {
717 let s = Vec::<Rotation>::try_coerce(s.clone())?;
718 if s.len() != 2 {
719 return Err(format!(
720 "expected 2 elements in skew, got {:?}",
721 s.len()
722 ));
723 }
724 Some([s[0], s[1]])
725 } else {
726 None
727 }
728 }
729 _ => {
730 return Err(format!(
731 "skew of {:?} can't be coerced into a Transform2D",
732 s
733 ))
734 }
735 },
736 None => None,
737 };
738 Transform2D {
739 previous,
740 rotate,
741 translate,
742 anchor,
743 scale,
744 skew,
745 }
746 }
747 _ => return Err(format!("{:?} can't be coerced into a Transform2D", value)),
748 })
749 }
750}
751
752impl CoercionRules for Transform2 {
753 fn try_coerce(value: PaxValue) -> Result<Self, String> {
754 Ok(match value {
755 PaxValue::Option(mut opt) => {
756 if let Some(t) = opt.take() {
757 Transform2::try_coerce(t)?
758 } else {
759 return Err(format!("None can't be coerced into a Transform2"));
760 }
761 }
762 PaxValue::Object(map) => {
763 let m = Vec::<f64>::try_coerce(
764 map.iter()
765 .find_map(|(n, v)| (n == "m").then_some(v))
766 .unwrap()
767 .clone(),
768 )?;
769 if m.len() != 6 {
770 return Err(format!("expected 6 elements in coeffs, got {:?}", m.len()));
771 }
772 Transform2::new([m[0], m[1], m[2], m[3], m[4], m[5]])
773 }
774 _ => return Err(format!("{:?} can't be coerced into a Transform2", value)),
775 })
776 }
777}
778
779impl CoercionRules for Vector2 {
780 fn try_coerce(value: PaxValue) -> Result<Self, String> {
781 Ok(match value {
782 PaxValue::Option(mut opt) => {
783 if let Some(t) = opt.take() {
784 Vector2::try_coerce(t)?
785 } else {
786 return Err(format!("None can't be coerced into a Vector2"));
787 }
788 }
789 PaxValue::Object(map) => {
790 let x = f64::try_coerce(
791 map.iter()
792 .find_map(|(n, v)| (n == "x").then_some(v))
793 .unwrap()
794 .clone(),
795 )?;
796 let y = f64::try_coerce(
797 map.iter()
798 .find_map(|(n, v)| (n == "y").then_some(v))
799 .unwrap()
800 .clone(),
801 )?;
802 Vector2::new(x, y)
803 }
804 _ => return Err(format!("{:?} can't be coerced into a Vector2", value)),
805 })
806 }
807}