chart_js_rs/objects/
helper_objects.rs

1#![allow(unreachable_patterns)]
2
3use {
4    crate::traits::*,
5    js_sys::{Function, Reflect},
6    serde::{
7        de::{self, DeserializeOwned},
8        Deserialize, Serialize,
9    },
10    std::fmt::{Debug, Display},
11    wasm_bindgen::{JsCast, JsValue},
12};
13
14#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq)]
15#[serde(transparent)]
16pub struct DatasetData(pub serde_json::Value);
17impl DatasetData {
18    pub fn is_empty(&self) -> bool {
19        serde_json::to_value(self)
20            .unwrap()
21            .as_array()
22            .unwrap()
23            .is_empty()
24    }
25
26    pub fn from_single_point_array(iter: impl Iterator<Item = [NumberOrDateString; 1]>) -> Self {
27        DatasetData(serde_json::to_value(iter.collect::<Vec<_>>()).unwrap())
28    }
29
30    pub fn from_minmax_array(iter: impl Iterator<Item = [NumberOrDateString; 2]>) -> Self {
31        DatasetData(serde_json::to_value(iter.collect::<Vec<_>>()).unwrap())
32    }
33}
34impl PartialOrd for DatasetData {
35    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
36        Some(self.cmp(other))
37    }
38}
39impl Ord for DatasetData {
40    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
41        self.0.to_string().cmp(&other.0.to_string())
42    }
43}
44
45#[derive(Debug, Clone, Deserialize, Serialize, Default)]
46pub struct NoDatasets {}
47impl DatasetTrait for NoDatasets {
48    fn labels(self) -> Vec<NumberOrDateString> {
49        Vec::new()
50    }
51}
52
53#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq, PartialOrd, Ord)]
54#[serde(bound = "D: DatasetTrait")]
55#[allow(unreachable_patterns)]
56pub struct Dataset<D: DatasetTrait> {
57    datasets: D,
58    #[serde(skip_serializing_if = "Option::is_none", rename(serialize = "labels"))]
59    forced_labels: Option<Vec<NumberOrDateString>>,
60    #[serde(skip_serializing_if = "option_vec_is_none")]
61    labels: Option<Vec<NumberOrDateString>>,
62}
63impl<D: DatasetTrait> Dataset<D> {
64    pub fn new() -> Self {
65        Self {
66            datasets: D::default(),
67            labels: None,
68            forced_labels: None,
69        }
70    }
71
72    pub fn get_datasets(&mut self) -> &mut D {
73        &mut self.datasets
74    }
75
76    pub fn datasets(mut self, datasets: impl Into<D>) -> Self {
77        self.datasets = datasets.into();
78
79        if self.forced_labels.is_none() {
80            let labels = self.datasets.clone();
81            self._labels(labels.labels())
82        } else {
83            self
84        }
85    }
86
87    pub fn get_labels(&mut self) -> &mut Option<Vec<NumberOrDateString>> {
88        match (&self.labels, &self.forced_labels) {
89            (Some(_), None) => &mut self.labels,
90            _ => &mut self.forced_labels,
91        }
92    }
93
94    fn _labels<T: Into<NumberOrDateString>>(mut self, labels: impl IntoIterator<Item = T>) -> Self {
95        self.labels = Some(labels.into_iter().map(Into::into).collect());
96
97        self
98    }
99
100    pub fn labels<T: Into<NumberOrDateString>>(
101        mut self,
102        labels: impl IntoIterator<Item = T>,
103    ) -> Self {
104        self.forced_labels = Some(labels.into_iter().map(Into::into).collect());
105        self.labels = None;
106
107        self
108    }
109}
110fn option_vec_is_none<T: Default + PartialEq + Clone>(opt: &Option<Vec<T>>) -> bool {
111    match opt {
112        Some(vec) => vec.is_empty() || vec.clone().try_into() == Ok([T::default()]),
113        None => true,
114    }
115}
116#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
117#[serde(untagged)]
118pub enum Any {
119    String(String),
120    Int(isize),
121    Bool(bool),
122    Vec(Vec<()>),
123}
124impl From<bool> for Any {
125    fn from(value: bool) -> Self {
126        Self::Bool(value)
127    }
128}
129impl From<String> for Any {
130    fn from(value: String) -> Self {
131        Self::String(value)
132    }
133}
134impl Any {
135    pub fn is_empty(&self) -> bool {
136        match self {
137            Any::String(s) => s.is_empty(),
138            Any::Int(_i) => false,
139            Any::Bool(_b) => false,
140            Any::Vec(v) => v.is_empty(),
141        }
142    }
143}
144impl Display for Any {
145    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146        match self {
147            Any::String(s) => write!(f, "{s}"),
148            Any::Bool(b) => write!(f, "{b}"),
149            Any::Int(i) => write!(f, "{i}"),
150            Any::Vec(_) => write!(f, ""),
151        }
152    }
153}
154#[derive(Debug, Clone, Default, PartialEq, Eq)]
155pub struct NumberOrDateString(String);
156impl From<NumberString> for NumberOrDateString {
157    fn from(value: NumberString) -> Self {
158        value.0.into()
159    }
160}
161impl NumberOrDateString {
162    pub fn is_empty(&self) -> bool {
163        self.0.is_empty()
164    }
165}
166impl PartialOrd for NumberOrDateString {
167    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
168        Some(self.cmp(other))
169    }
170}
171impl Ord for NumberOrDateString {
172    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
173        if let Some((s, o)) = self
174            .0
175            .parse::<rust_decimal::Decimal>()
176            .ok()
177            .zip(other.0.parse::<rust_decimal::Decimal>().ok())
178        {
179            s.cmp(&o)
180        } else {
181            self.0.cmp(&other.0)
182        }
183    }
184}
185impl<T: Display> From<T> for NumberOrDateString {
186    fn from(s: T) -> Self {
187        Self(s.to_string())
188    }
189}
190#[allow(unknown_lints, clippy::to_string_trait_impl)]
191impl ToString for NumberOrDateString {
192    fn to_string(&self) -> String {
193        self.0.to_string()
194    }
195}
196impl Serialize for NumberOrDateString {
197    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
198    where
199        S: serde::Serializer,
200    {
201        let fnum: Result<f64, _> = self.0.parse();
202        let inum: Result<i64, _> = self.0.parse();
203        match (fnum, inum) {
204            (Ok(_), Ok(inum)) => serializer.serialize_i64(inum),
205            (Ok(fnum), _) => serializer.serialize_f64(fnum),
206            _ => serializer.serialize_str(&self.0),
207        }
208    }
209}
210impl<'de> Deserialize<'de> for NumberOrDateString {
211    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
212    where
213        D: serde::Deserializer<'de>,
214    {
215        Any::deserialize(deserializer).map(|soi| Self(soi.to_string()))
216    }
217}
218
219#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
220pub struct BoolString(String);
221impl BoolString {
222    pub fn opt_true() -> Option<BoolString> {
223        BoolString("true".into()).into()
224    }
225    pub fn opt_false() -> Option<BoolString> {
226        BoolString("false".into()).into()
227    }
228    pub fn _true() -> BoolString {
229        BoolString("true".into())
230    }
231    pub fn _false() -> BoolString {
232        BoolString("false".into())
233    }
234    pub fn is_empty(&self) -> bool {
235        self.0.is_empty()
236    }
237}
238impl Default for BoolString {
239    fn default() -> Self {
240        Self::_false()
241    }
242}
243impl ChartJsRsObject for BoolString {
244    fn is_empty(&self) -> bool {
245        self.is_empty()
246    }
247}
248impl<T: Display> From<T> for BoolString {
249    fn from(s: T) -> Self {
250        Self(s.to_string())
251    }
252}
253impl Serialize for BoolString {
254    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
255    where
256        S: serde::Serializer,
257    {
258        let bool_: Result<bool, _> = self.0.parse();
259        let any: Result<String, _> = self.0.parse();
260        match (bool_, any) {
261            (Ok(bool_), _) => serializer.serialize_bool(bool_),
262            (_, Ok(any)) => serializer.serialize_str(&any),
263            _ => unreachable!(),
264        }
265    }
266}
267impl<'de> Deserialize<'de> for BoolString {
268    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
269    where
270        D: serde::Deserializer<'de>,
271    {
272        Any::deserialize(deserializer).map(|soi| Self(soi.to_string()))
273    }
274}
275
276#[derive(Debug, Deserialize, Serialize)]
277struct JavascriptFunction {
278    args: Vec<String>,
279    body: String,
280    return_value: String,
281    closure_id: Option<String>,
282}
283
284const ALPHABET: [&str; 32] = [
285    "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
286    "t", "u", "v", "w", "x", "y", "z", "aa", "bb", "cc", "dd", "ee", "ff",
287];
288
289#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
290pub struct FnWithArgs<const N: usize> {
291    pub(crate) args: [String; N],
292    pub(crate) body: String,
293    pub(crate) return_value: String,
294    pub(crate) closure_id: Option<String>,
295}
296impl<const N: usize> FnWithArgs<N> {
297    pub fn rationalise_1_level(obj: &JsValue, name: &'static str) {
298        super::rationalise_1_level::<N, Self>(obj, name, |o| {
299            let _ = Reflect::set(obj, &name.into(), &o.build());
300        })
301    }
302    pub fn rationalise_2_levels(obj: &JsValue, name: (&'static str, &'static str)) {
303        super::rationalise_2_levels::<N, Self>(obj, name, |a, o| {
304            let _ = Reflect::set(&a, &name.1.into(), &o.build());
305        })
306    }
307}
308
309impl<const N: usize> Default for FnWithArgs<N> {
310    fn default() -> Self {
311        Self {
312            args: (0..N)
313                .map(|idx| ALPHABET[idx].to_string())
314                .collect::<Vec<_>>()
315                .try_into()
316                .unwrap(),
317            body: Default::default(),
318            return_value: Default::default(),
319            closure_id: None,
320        }
321    }
322}
323impl<'de, const N: usize> Deserialize<'de> for FnWithArgs<N> {
324    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
325    where
326        D: serde::Deserializer<'de>,
327    {
328        let js = JavascriptFunction::deserialize(deserializer)?;
329        Ok(FnWithArgs::<N> {
330            args: js.args.clone().try_into().map_err(|_| {
331                de::Error::custom(format!("Array had length {}, needed {}.", js.args.len(), N))
332            })?,
333            body: js.body,
334            return_value: js.return_value,
335            closure_id: js.closure_id,
336        })
337    }
338}
339impl<const N: usize> Serialize for FnWithArgs<N> {
340    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
341    where
342        S: serde::Serializer,
343    {
344        JavascriptFunction::serialize(
345            &JavascriptFunction {
346                args: self.args.to_vec(),
347                body: self.body.clone(),
348                return_value: self.return_value.clone(),
349                closure_id: self.closure_id.clone(),
350            },
351            serializer,
352        )
353    }
354}
355
356impl<const N: usize> FnWithArgs<N> {
357    pub fn is_empty(&self) -> bool {
358        match self.closure_id {
359            Some(_) => false,
360            None => self.body.is_empty(),
361        }
362    }
363
364    pub fn new() -> Self {
365        Self::default()
366    }
367
368    pub fn args<S: AsRef<str>>(mut self, args: [S; N]) -> Self {
369        self.args = args
370            .into_iter()
371            .enumerate()
372            .map(|(idx, s)| {
373                let arg = s.as_ref();
374                if arg.is_empty() { ALPHABET[idx] } else { arg }.to_string()
375            })
376            .collect::<Vec<_>>()
377            .try_into()
378            .unwrap();
379        self
380    }
381
382    pub fn js_body(mut self, body: &str) -> Self {
383        self.body = format!("{}\n{body}", self.body);
384        self.to_owned()
385    }
386
387    pub fn js_return_value(self, return_value: &str) -> Self {
388        let mut s = if self.body.is_empty() {
389            self.js_body("")
390        } else {
391            self
392        };
393        s.return_value = return_value.to_string();
394        s.to_owned()
395    }
396
397    pub fn build(self) -> Function {
398        if let Some(id) = self.closure_id {
399            let args = self.args.join(", ");
400            Function::new_with_args(&args, &format!("{{ return window['{id}']({args}) }}"))
401        } else {
402            Function::new_with_args(
403                &self.args.join(", "),
404                &format!("{{ {}\nreturn {} }}", self.body, self.return_value),
405            )
406        }
407    }
408}
409
410impl FnWithArgs<1> {
411    pub fn run_rust_fn<A, B, FN: Fn(A) -> B>(mut self, _func: FN) -> Self {
412        let fn_name = std::any::type_name::<FN>()
413            .split("::")
414            .collect::<Vec<_>>()
415            .into_iter()
416            .next_back()
417            .unwrap();
418
419        self.body = format!(
420            "{}\nconst _out_ = window.callbacks.{}({});",
421            self.body,
422            fn_name,
423            self.args.join(", ")
424        );
425        self.js_return_value("_out_")
426    }
427
428    #[track_caller]
429    pub fn rust_closure<F: Fn(JsValue) -> JsValue + 'static>(mut self, closure: F) -> Self {
430        let js_closure = wasm_bindgen::closure::Closure::wrap(
431            Box::new(closure) as Box<dyn Fn(JsValue) -> JsValue>
432        );
433        let js_sys_fn: &js_sys::Function = js_closure.as_ref().unchecked_ref();
434
435        let js_window = gloo_utils::window();
436        let id = uuid::Uuid::new_v4().to_string();
437        Reflect::set(&js_window, &JsValue::from_str(&id), js_sys_fn).unwrap();
438        js_closure.forget();
439
440        gloo_console::debug!(format!(
441            "Closure at {}:{}:{} set at window.['{id}'].",
442            file!(),
443            line!(),
444            column!()
445        ));
446        self.closure_id = Some(id);
447        self
448    }
449}
450
451impl FnWithArgs<2> {
452    pub fn run_rust_fn<A, B, C, FN: Fn(A, B) -> C>(mut self, _func: FN) -> Self {
453        let fn_name = std::any::type_name::<FN>()
454            .split("::")
455            .collect::<Vec<_>>()
456            .into_iter()
457            .next_back()
458            .unwrap();
459
460        self.body = format!(
461            "{}\nconst _out_ = window.callbacks.{}({});",
462            self.body,
463            fn_name,
464            self.args.join(", ")
465        );
466        self.js_return_value("_out_")
467    }
468
469    #[track_caller]
470    pub fn rust_closure<F: Fn(JsValue, JsValue) -> JsValue + 'static>(
471        mut self,
472        closure: F,
473    ) -> Self {
474        let js_closure = wasm_bindgen::closure::Closure::wrap(
475            Box::new(closure) as Box<dyn Fn(JsValue, JsValue) -> JsValue>
476        );
477        let js_sys_fn: &js_sys::Function = js_closure.as_ref().unchecked_ref();
478
479        let js_window = gloo_utils::window();
480        let id = uuid::Uuid::new_v4().to_string();
481        Reflect::set(&js_window, &JsValue::from_str(&id), js_sys_fn).unwrap();
482        js_closure.forget();
483
484        gloo_console::debug!(format!(
485            "Closure at {}:{}:{} set at window.['{id}'].",
486            file!(),
487            line!(),
488            column!()
489        ));
490        self.closure_id = Some(id);
491        self
492    }
493}
494
495impl FnWithArgs<3> {
496    pub fn run_rust_fn<A, B, C, D, FN: Fn(A, B, C) -> D>(mut self, _func: FN) -> Self {
497        let fn_name = std::any::type_name::<FN>()
498            .split("::")
499            .collect::<Vec<_>>()
500            .into_iter()
501            .next_back()
502            .unwrap();
503
504        self.body = format!(
505            "{}\nconst _out_ = window.callbacks.{}({});",
506            self.body,
507            fn_name,
508            self.args.join(", ")
509        );
510        self.js_return_value("_out_")
511    }
512
513    #[track_caller]
514    pub fn rust_closure<F: Fn(JsValue, JsValue, JsValue) -> JsValue + 'static>(
515        mut self,
516        closure: F,
517    ) -> Self {
518        let js_closure = wasm_bindgen::closure::Closure::wrap(
519            Box::new(closure) as Box<dyn Fn(JsValue, JsValue, JsValue) -> JsValue>
520        );
521        let js_sys_fn: &js_sys::Function = js_closure.as_ref().unchecked_ref();
522
523        let js_window = gloo_utils::window();
524        let id = uuid::Uuid::new_v4().to_string();
525        Reflect::set(&js_window, &JsValue::from_str(&id), js_sys_fn).unwrap();
526        js_closure.forget();
527
528        gloo_console::debug!(format!(
529            "Closure at {}:{}:{} set at window.['{id}'].",
530            file!(),
531            line!(),
532            column!()
533        ));
534        self.closure_id = Some(id);
535        self
536    }
537}
538
539impl FnWithArgs<4> {
540    pub fn run_rust_fn<A, B, C, D, E, FN: Fn(A, B, C, D) -> E>(mut self, _func: FN) -> Self {
541        let fn_name = std::any::type_name::<FN>()
542            .split("::")
543            .collect::<Vec<_>>()
544            .into_iter()
545            .next_back()
546            .unwrap();
547
548        self.body = format!(
549            "{}\nconst _out_ = window.callbacks.{}({});",
550            self.body,
551            fn_name,
552            self.args.join(", ")
553        );
554        self.js_return_value("_out_")
555    }
556
557    #[track_caller]
558    pub fn rust_closure<F: Fn(JsValue, JsValue, JsValue, JsValue) -> JsValue + 'static>(
559        mut self,
560        closure: F,
561    ) -> Self {
562        let js_closure = wasm_bindgen::closure::Closure::wrap(
563            Box::new(closure) as Box<dyn Fn(JsValue, JsValue, JsValue, JsValue) -> JsValue>
564        );
565        let js_sys_fn: &js_sys::Function = js_closure.as_ref().unchecked_ref();
566
567        let js_window = gloo_utils::window();
568        let id = uuid::Uuid::new_v4().to_string();
569        Reflect::set(&js_window, &JsValue::from_str(&id), js_sys_fn).unwrap();
570        js_closure.forget();
571
572        gloo_console::debug!(format!(
573            "Closure at {}:{}:{} set at window.['{id}'].",
574            file!(),
575            line!(),
576            column!()
577        ));
578        self.closure_id = Some(id);
579        self
580    }
581}
582
583impl FnWithArgs<5> {
584    pub fn run_rust_fn<A, B, C, D, E, F, FN: Fn(A, B, C, D, E) -> F>(mut self, _func: FN) -> Self {
585        let fn_name = std::any::type_name::<FN>()
586            .split("::")
587            .collect::<Vec<_>>()
588            .into_iter()
589            .next_back()
590            .unwrap();
591
592        self.body = format!(
593            "{}\nconst _out_ = window.callbacks.{}({});",
594            self.body,
595            fn_name,
596            self.args.join(", ")
597        );
598        self.js_return_value("_out_")
599    }
600
601    #[track_caller]
602    pub fn rust_closure<F: Fn(JsValue, JsValue, JsValue, JsValue, JsValue) -> JsValue + 'static>(
603        mut self,
604        closure: F,
605    ) -> Self {
606        let js_closure = wasm_bindgen::closure::Closure::wrap(Box::new(closure)
607            as Box<dyn Fn(JsValue, JsValue, JsValue, JsValue, JsValue) -> JsValue>);
608        let js_sys_fn: &js_sys::Function = js_closure.as_ref().unchecked_ref();
609
610        let js_window = gloo_utils::window();
611        let id = uuid::Uuid::new_v4().to_string();
612        Reflect::set(&js_window, &JsValue::from_str(&id), js_sys_fn).unwrap();
613        js_closure.forget();
614
615        gloo_console::debug!(format!(
616            "Closure at {}:{}:{} set at window.['{id}'].",
617            file!(),
618            line!(),
619            column!()
620        ));
621        self.closure_id = Some(id);
622        self
623    }
624}
625
626impl FnWithArgs<6> {
627    pub fn run_rust_fn<A, B, C, D, E, F, G, FN: Fn(A, B, C, D, E, F) -> G>(
628        mut self,
629        _func: FN,
630    ) -> Self {
631        let fn_name = std::any::type_name::<FN>()
632            .split("::")
633            .collect::<Vec<_>>()
634            .into_iter()
635            .next_back()
636            .unwrap();
637
638        self.body = format!(
639            "{}\nconst _out_ = window.callbacks.{}({});",
640            self.body,
641            fn_name,
642            self.args.join(", ")
643        );
644        self.js_return_value("_out_")
645    }
646
647    #[track_caller]
648    pub fn rust_closure<
649        F: Fn(JsValue, JsValue, JsValue, JsValue, JsValue, JsValue) -> JsValue + 'static,
650    >(
651        mut self,
652        closure: F,
653    ) -> Self {
654        let js_closure = wasm_bindgen::closure::Closure::wrap(Box::new(closure)
655            as Box<dyn Fn(JsValue, JsValue, JsValue, JsValue, JsValue, JsValue) -> JsValue>);
656        let js_sys_fn: &js_sys::Function = js_closure.as_ref().unchecked_ref();
657
658        let js_window = gloo_utils::window();
659        let id = uuid::Uuid::new_v4().to_string();
660        Reflect::set(&js_window, &JsValue::from_str(&id), js_sys_fn).unwrap();
661        js_closure.forget();
662
663        gloo_console::debug!(format!(
664            "Closure at {}:{}:{} set at window.['{id}'].",
665            file!(),
666            line!(),
667            column!()
668        ));
669        self.closure_id = Some(id);
670        self
671    }
672}
673
674// 7 is the maximum wasm_bindgen can handle rn AFAIK
675impl FnWithArgs<7> {
676    pub fn run_rust_fn<A, B, C, D, E, F, G, H, FN: Fn(A, B, C, D, E, F, G) -> H>(
677        mut self,
678        _func: FN,
679    ) -> Self {
680        let fn_name = std::any::type_name::<FN>()
681            .split("::")
682            .collect::<Vec<_>>()
683            .into_iter()
684            .next_back()
685            .unwrap();
686
687        self.body = format!(
688            "{}\nconst _out_ = window.callbacks.{}({});",
689            self.body,
690            fn_name,
691            self.args.join(", ")
692        );
693        self.js_return_value("_out_")
694    }
695
696    #[track_caller]
697    pub fn rust_closure<
698        F: Fn(JsValue, JsValue, JsValue, JsValue, JsValue, JsValue, JsValue) -> JsValue + 'static,
699    >(
700        mut self,
701        closure: F,
702    ) -> Self {
703        let js_closure = wasm_bindgen::closure::Closure::wrap(Box::new(closure)
704            as Box<
705                dyn Fn(JsValue, JsValue, JsValue, JsValue, JsValue, JsValue, JsValue) -> JsValue,
706            >);
707        let js_sys_fn: &js_sys::Function = js_closure.as_ref().unchecked_ref();
708
709        let js_window = gloo_utils::window();
710        let id = uuid::Uuid::new_v4().to_string();
711        Reflect::set(&js_window, &JsValue::from_str(&id), js_sys_fn).unwrap();
712        js_closure.forget();
713
714        gloo_console::debug!(format!(
715            "Closure at {}:{}:{} set at window.['{id}'].",
716            file!(),
717            line!(),
718            column!()
719        ));
720        self.closure_id = Some(id);
721        self
722    }
723}
724
725#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
726#[serde(untagged)]
727pub enum FnWithArgsOrT<const N: usize, T> {
728    T(T),
729    FnWithArgs(FnWithArgs<N>),
730}
731
732impl<const N: usize, T: for<'a> Deserialize<'a>> FnWithArgsOrT<N, T> {
733    pub fn rationalise_1_level(obj: &JsValue, name: &'static str) {
734        super::rationalise_1_level::<N, Self>(obj, name, |o| match o {
735            FnWithArgsOrT::T(_) => (),
736            FnWithArgsOrT::FnWithArgs(fnwa) => {
737                let _ = Reflect::set(obj, &name.into(), &fnwa.build());
738            }
739        })
740    }
741    pub fn rationalise_2_levels(obj: &JsValue, name: (&'static str, &'static str)) {
742        super::rationalise_2_levels::<N, Self>(obj, name, |a, o| match o {
743            FnWithArgsOrT::T(_) => (),
744            FnWithArgsOrT::FnWithArgs(fnwa) => {
745                let _ = Reflect::set(&a, &name.1.into(), &fnwa.build());
746            }
747        })
748    }
749}
750#[allow(private_bounds)]
751impl<const N: usize, T: ChartJsRsObject> FnWithArgsOrT<N, T> {
752    pub fn is_empty(&self) -> bool {
753        match self {
754            FnWithArgsOrT::T(a) => a.is_empty(),
755            FnWithArgsOrT::FnWithArgs(fnwa) => fnwa.is_empty(),
756        }
757    }
758}
759impl<const N: usize, T: Default> Default for FnWithArgsOrT<N, T> {
760    fn default() -> Self {
761        FnWithArgsOrT::T(T::default())
762    }
763}
764impl<const N: usize, T: Into<String>> From<T> for FnWithArgsOrT<N, String> {
765    fn from(s: T) -> Self {
766        Self::T(s.into())
767    }
768}
769impl<const N: usize, T: Into<NumberString>> From<T> for FnWithArgsOrT<N, NumberString> {
770    fn from(ns: T) -> Self {
771        Self::T(ns.into())
772    }
773}
774impl<const N: usize, T: Into<BoolString>> From<T> for FnWithArgsOrT<N, BoolString> {
775    fn from(bs: T) -> Self {
776        Self::T(bs.into())
777    }
778}
779impl<const N: usize, T> From<FnWithArgs<N>> for FnWithArgsOrT<N, T> {
780    fn from(value: FnWithArgs<N>) -> Self {
781        Self::FnWithArgs(value)
782    }
783}
784
785#[derive(Debug, Clone, Default, PartialEq, Eq)]
786pub struct NumberString(String);
787impl From<NumberOrDateString> for NumberString {
788    fn from(value: NumberOrDateString) -> Self {
789        value.0.into()
790    }
791}
792impl NumberString {
793    pub fn is_empty(&self) -> bool {
794        self.0.is_empty()
795    }
796}
797impl ChartJsRsObject for NumberString {
798    fn is_empty(&self) -> bool {
799        self.is_empty()
800    }
801}
802impl PartialOrd for NumberString {
803    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
804        Some(self.cmp(other))
805    }
806}
807impl Ord for NumberString {
808    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
809        if let Some((s, o)) = self
810            .0
811            .parse::<rust_decimal::Decimal>()
812            .ok()
813            .zip(other.0.parse::<rust_decimal::Decimal>().ok())
814        {
815            s.cmp(&o)
816        } else {
817            self.0.cmp(&other.0)
818        }
819    }
820}
821impl<T: Display> From<T> for NumberString {
822    fn from(s: T) -> Self {
823        Self(s.to_string())
824    }
825}
826#[allow(clippy::to_string_trait_impl)]
827impl ToString for NumberString {
828    fn to_string(&self) -> String {
829        self.0.to_string()
830    }
831}
832impl Serialize for NumberString {
833    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
834    where
835        S: serde::Serializer,
836    {
837        let fnum: Result<f64, _> = self.0.parse();
838        let inum: Result<i64, _> = self.0.parse();
839        match (fnum, inum) {
840            (Ok(_), Ok(inum)) => serializer.serialize_i64(inum),
841            (Ok(fnum), _) => serializer.serialize_f64(fnum),
842            _ => serializer.serialize_str(&self.0),
843        }
844    }
845}
846impl<'de> Deserialize<'de> for NumberString {
847    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
848    where
849        D: serde::Deserializer<'de>,
850    {
851        Any::deserialize(deserializer).map(|soi| Self(soi.to_string()))
852    }
853}
854
855#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
856#[serde(untagged)]
857pub enum NumberStringOrT<T: Serialize + DeserializeOwned> {
858    T(T),
859    NumberString(NumberString),
860}
861impl<'de, T: Serialize + DeserializeOwned> Deserialize<'de> for NumberStringOrT<T> {
862    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
863    where
864        D: de::Deserializer<'de>,
865    {
866        // thanks serde :|
867        let value = serde::__private::de::Content::deserialize(deserializer)?;
868        let deserializer = serde::__private::de::ContentRefDeserializer::<D::Error>::new(&value);
869
870        match NumberString::deserialize(deserializer) {
871            Ok(ns) => Ok(Self::NumberString(ns)),
872            Err(_) => T::deserialize(deserializer).map(Self::T),
873        }
874    }
875}
876impl<T: Serialize + DeserializeOwned> NumberStringOrT<T> {
877    pub fn is_empty(&self) -> bool {
878        match self {
879            NumberStringOrT::T(_t) => false,
880            NumberStringOrT::NumberString(ns) => ns.is_empty(),
881        }
882    }
883}
884
885impl<T: Serialize + ChartJsRsObject, U: Serialize + DeserializeOwned> From<T>
886    for NumberStringOrT<U>
887{
888    fn from(value: T) -> Self {
889        serde_json::from_value(serde_json::to_value(value).unwrap()).unwrap()
890    }
891}