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}