pax_runtime_api/pax_value/
mod.rs

1use crate::{Color, Interpolatable, PathElement, Percent, Rotation, Size};
2use std::{any::Any, fmt::Display};
3
4use self::numeric::Numeric;
5pub use coercion_impls::CoercionRules;
6use serde::{Deserialize, Serialize};
7
8mod arithmetic;
9mod coercion_impls;
10pub mod functions;
11mod macros;
12pub mod numeric;
13mod to_from_impls;
14
15/// Container for all internal pax types
16/// Two important traits are related to this type:
17/// ToFromPaxValue - responsible for converting to and from specific types (u8,
18/// String, Color, etc)
19/// CoercionRules - responsible for coercing a PaxValue to a specific type
20/// (possibly from multiple different variants)
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
22#[serde(crate = "crate::serde")]
23pub enum PaxValue {
24    Bool(bool),
25    Numeric(Numeric),
26    String(String),
27    Size(Size),
28    Percent(Percent),
29    Color(Box<Color>),
30    Rotation(Rotation),
31    PathElement(PathElement),
32    Option(Box<Option<PaxValue>>),
33    Vec(Vec<PaxValue>),
34    Range(Box<PaxValue>, Box<PaxValue>),
35    Object(Vec<(String, PaxValue)>),
36    Enum(String, String, Vec<PaxValue>),
37}
38
39// Make sure Enum looks like an enum and vec looks like a vec
40impl Display for PaxValue {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        match self {
43            PaxValue::Bool(b) => write!(f, "{}", b),
44            PaxValue::Numeric(n) => write!(f, "{}", n),
45            PaxValue::String(s) => write!(f, "\"{}\"", s),
46            PaxValue::Size(s) => write!(f, "{}", s),
47            PaxValue::Percent(p) => write!(f, "{}", p),
48            PaxValue::Color(c) => write!(f, "{}", c),
49            PaxValue::Rotation(r) => write!(f, "{}", r),
50            PaxValue::PathElement(path_elem) => {
51                write!(f, "PathElement::")?;
52                match path_elem {
53                    PathElement::Empty => write!(f, "Empty"),
54                    PathElement::Point(s1, s2) => write!(f, "Point({}, {})", s1, s2),
55                    PathElement::Line => write!(f, "Line"),
56                    PathElement::Quadratic(s1, s2) => write!(f, "Quadratic({}, {})", s1, s2),
57                    PathElement::Cubic(vals) => {
58                        write!(f, "Cubic({}, {}, {}, {})", vals.0, vals.1, vals.2, vals.3)
59                    }
60                    PathElement::Close => write!(f, "Close"),
61                }?;
62                Ok(())
63            }
64            PaxValue::Option(o) => match o.as_ref() {
65                Some(v) => write!(f, "Some({})", v),
66                None => write!(f, "None"),
67            },
68            PaxValue::Vec(v) => {
69                write!(f, "[")?;
70                for (i, val) in v.iter().enumerate() {
71                    if i != 0 {
72                        write!(f, ", ")?;
73                    }
74                    write!(f, "{}", val)?;
75                }
76                write!(f, "]")
77            }
78            PaxValue::Range(start, end) => write!(f, "{}..{}", start, end),
79            PaxValue::Object(o) => {
80                write!(f, "{{")?;
81                for (i, (key, val)) in o.iter().enumerate() {
82                    if i != 0 {
83                        write!(f, ", ")?;
84                    }
85                    write!(f, "{}: {}", key, val)?;
86                }
87                write!(f, "}}")
88            }
89            PaxValue::Enum(name, variant, values) => {
90                if name == "Color" {
91                    write!(f, "{}", variant)?;
92                } else {
93                    write!(f, "{}::{}", name, variant)?;
94                }
95                if !values.is_empty() {
96                    write!(f, "(")?;
97                    for (i, val) in values.iter().enumerate() {
98                        if i != 0 {
99                            write!(f, ", ")?;
100                        }
101                        write!(f, "{}", val)?;
102                    }
103                    write!(f, ")")?;
104                }
105                Ok(())
106            }
107        }
108    }
109}
110
111impl Default for PaxValue {
112    fn default() -> Self {
113        PaxValue::Numeric(Numeric::F64(0.0))
114    }
115}
116
117/// This type serves a similar purpose as Box<dyn Any>, but allows for special
118/// handling of some types, enabling things like coercion.
119pub enum PaxAny {
120    Builtin(PaxValue),
121    Any(Box<dyn Any>),
122}
123
124impl std::fmt::Debug for PaxAny {
125    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126        write!(f, "PaxAny {{ .. }}")
127    }
128}
129
130impl Interpolatable for PaxValue {}
131
132/// This trait is implemented by all types that has a builtin equivalent
133/// representation (see to_from_impls module) This is NOT responsible for
134/// coercing between types, but returns an err in all cases where the underlying
135/// type is not exactly what is expected
136pub trait ToPaxValue {
137    fn to_pax_value(self) -> PaxValue;
138}
139
140/// Trait that marks a type as being representable as a PaxAny, and provides
141/// the implementation for going to/from that type. For all builtins this
142/// means going to/from a pax value. For others to a Box<dyn Any>. This
143/// is automatically Implemented for PaxValue types through the macro
144/// impl_to_from_pax_value!, and for other types by implementing the marker
145/// trait ImplToFromPaxAny.
146pub trait ToFromPaxAny
147where
148    Self: Sized + 'static,
149{
150    fn to_pax_any(self) -> PaxAny;
151    fn from_pax_any(pax_any: PaxAny) -> Result<Self, String>;
152    fn ref_from_pax_any(pax_any: &PaxAny) -> Result<&Self, String>;
153    fn mut_from_pax_any(pax_any: &mut PaxAny) -> Result<&mut Self, String>;
154}
155
156impl ToFromPaxAny for PaxValue {
157    fn to_pax_any(self) -> PaxAny {
158        PaxAny::Builtin(self)
159    }
160
161    fn from_pax_any(pax_any: PaxAny) -> Result<Self, String> {
162        match pax_any {
163            PaxAny::Builtin(val) => Ok(val),
164            PaxAny::Any(_) => Err("tried to unwrap any as builtin".to_string()),
165        }
166    }
167
168    fn ref_from_pax_any(pax_any: &PaxAny) -> Result<&Self, String> {
169        match pax_any {
170            PaxAny::Builtin(val) => Ok(val),
171            PaxAny::Any(_) => Err("tried to unwrap any as builtin".to_string()),
172        }
173    }
174
175    fn mut_from_pax_any(pax_any: &mut PaxAny) -> Result<&mut Self, String> {
176        match pax_any {
177            PaxAny::Builtin(val) => Ok(val),
178            PaxAny::Any(_) => Err("tried to unwrap any as builtin".to_string()),
179        }
180    }
181}
182
183/// Marker trait. Implement only for types that are not part of PaxValue, but
184/// need to be stored inside a PaxAny. If they are part of pax value, instead
185/// implement CoercionRules manually, or using the default impl macro as seen
186/// in coercion_impls.rs
187pub trait ImplToFromPaxAny: 'static {}
188
189// If a type has marker trait, implement to from
190// pax any automatically by wrapping in Box<dyn Any>
191impl<T: ImplToFromPaxAny> ToFromPaxAny for T {
192    fn to_pax_any(self) -> PaxAny {
193        PaxAny::Any(Box::new(self) as Box<dyn Any>)
194    }
195
196    fn from_pax_any(pax_any: PaxAny) -> Result<Self, String> {
197        match pax_any {
198            PaxAny::Any(v) => Ok(*v
199                .downcast::<Self>()
200                .map_err(|_e| "downcast failed".to_string())?),
201            _ => Err("wasn't any".to_string()),
202        }
203    }
204
205    fn ref_from_pax_any(pax_any: &PaxAny) -> Result<&Self, String> {
206        match pax_any {
207            PaxAny::Any(v) => v
208                .downcast_ref::<Self>()
209                .ok_or_else(|| "downcast failed".to_string()),
210            _ => Err("wasn't any".to_string()),
211        }
212    }
213
214    fn mut_from_pax_any(pax_any: &mut PaxAny) -> Result<&mut Self, String> {
215        match pax_any {
216            PaxAny::Any(v) => v
217                .downcast_mut::<Self>()
218                .ok_or_else(|| "downcast failed".to_string()),
219            _ => Err("wasn't any".to_string()),
220        }
221    }
222}
223
224// PaxAny can turn into PaxAny
225impl ToFromPaxAny for PaxAny {
226    fn to_pax_any(self) -> PaxAny {
227        self
228    }
229
230    fn from_pax_any(pax_any: PaxAny) -> Result<Self, String> {
231        Ok(pax_any)
232    }
233
234    fn ref_from_pax_any(pax_any: &PaxAny) -> Result<&Self, String> {
235        Ok(pax_any)
236    }
237
238    fn mut_from_pax_any(pax_any: &mut PaxAny) -> Result<&mut Self, String> {
239        Ok(pax_any)
240    }
241}