Skip to main content

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