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            Function::new_with_args(&args, &format!("{{ return window['{id}']({args}) }}"))
408        } else {
409            Function::new_with_args(
410                &self.args.join(", "),
411                &format!("{{ {}\nreturn {} }}", self.body, self.return_value),
412            )
413        }
414    }
415}
416
417impl FnWithArgs<1> {
418    pub fn run_rust_fn<A, B, FN: Fn(A) -> B>(mut self, _func: FN) -> Self {
419        let fn_name = std::any::type_name::<FN>()
420            .split("::")
421            .collect::<Vec<_>>()
422            .into_iter()
423            .next_back()
424            .unwrap();
425
426        self.body = format!(
427            "{}\nconst _out_ = window.callbacks.{}({});",
428            self.body,
429            fn_name,
430            self.args.join(", ")
431        );
432        self.js_return_value("_out_")
433    }
434
435    #[track_caller]
436    pub fn rust_closure<F: Fn(JsValue) -> JsValue + 'static>(mut self, closure: F) -> Self {
437        let js_closure = wasm_bindgen::closure::Closure::wrap(
438            Box::new(closure) as Box<dyn Fn(JsValue) -> JsValue>
439        );
440        let js_sys_fn: &js_sys::Function = js_closure.as_ref().unchecked_ref();
441
442        let js_window = gloo_utils::window();
443        let id = uuid::Uuid::new_v4().to_string();
444        Reflect::set(&js_window, &JsValue::from_str(&id), js_sys_fn).unwrap();
445        js_closure.forget();
446
447        gloo_console::debug!(format!(
448            "Closure at {}:{}:{} set at window.['{id}'].",
449            file!(),
450            line!(),
451            column!()
452        ));
453        self.closure_id = Some(id);
454        self
455    }
456}
457
458impl FnWithArgs<2> {
459    pub fn run_rust_fn<A, B, C, FN: Fn(A, B) -> C>(mut self, _func: FN) -> Self {
460        let fn_name = std::any::type_name::<FN>()
461            .split("::")
462            .collect::<Vec<_>>()
463            .into_iter()
464            .next_back()
465            .unwrap();
466
467        self.body = format!(
468            "{}\nconst _out_ = window.callbacks.{}({});",
469            self.body,
470            fn_name,
471            self.args.join(", ")
472        );
473        self.js_return_value("_out_")
474    }
475
476    #[track_caller]
477    pub fn rust_closure<F: Fn(JsValue, JsValue) -> JsValue + 'static>(
478        mut self,
479        closure: F,
480    ) -> Self {
481        let js_closure = wasm_bindgen::closure::Closure::wrap(
482            Box::new(closure) as Box<dyn Fn(JsValue, JsValue) -> JsValue>
483        );
484        let js_sys_fn: &js_sys::Function = js_closure.as_ref().unchecked_ref();
485
486        let js_window = gloo_utils::window();
487        let id = uuid::Uuid::new_v4().to_string();
488        Reflect::set(&js_window, &JsValue::from_str(&id), js_sys_fn).unwrap();
489        js_closure.forget();
490
491        gloo_console::debug!(format!(
492            "Closure at {}:{}:{} set at window.['{id}'].",
493            file!(),
494            line!(),
495            column!()
496        ));
497        self.closure_id = Some(id);
498        self
499    }
500}
501
502impl FnWithArgs<3> {
503    pub fn run_rust_fn<A, B, C, D, FN: Fn(A, B, C) -> D>(mut self, _func: FN) -> Self {
504        let fn_name = std::any::type_name::<FN>()
505            .split("::")
506            .collect::<Vec<_>>()
507            .into_iter()
508            .next_back()
509            .unwrap();
510
511        self.body = format!(
512            "{}\nconst _out_ = window.callbacks.{}({});",
513            self.body,
514            fn_name,
515            self.args.join(", ")
516        );
517        self.js_return_value("_out_")
518    }
519
520    #[track_caller]
521    pub fn rust_closure<F: Fn(JsValue, JsValue, JsValue) -> JsValue + 'static>(
522        mut self,
523        closure: F,
524    ) -> Self {
525        let js_closure = wasm_bindgen::closure::Closure::wrap(
526            Box::new(closure) as Box<dyn Fn(JsValue, JsValue, JsValue) -> JsValue>
527        );
528        let js_sys_fn: &js_sys::Function = js_closure.as_ref().unchecked_ref();
529
530        let js_window = gloo_utils::window();
531        let id = uuid::Uuid::new_v4().to_string();
532        Reflect::set(&js_window, &JsValue::from_str(&id), js_sys_fn).unwrap();
533        js_closure.forget();
534
535        gloo_console::debug!(format!(
536            "Closure at {}:{}:{} set at window.['{id}'].",
537            file!(),
538            line!(),
539            column!()
540        ));
541        self.closure_id = Some(id);
542        self
543    }
544}
545
546impl FnWithArgs<4> {
547    pub fn run_rust_fn<A, B, C, D, E, FN: Fn(A, B, C, D) -> E>(mut self, _func: FN) -> Self {
548        let fn_name = std::any::type_name::<FN>()
549            .split("::")
550            .collect::<Vec<_>>()
551            .into_iter()
552            .next_back()
553            .unwrap();
554
555        self.body = format!(
556            "{}\nconst _out_ = window.callbacks.{}({});",
557            self.body,
558            fn_name,
559            self.args.join(", ")
560        );
561        self.js_return_value("_out_")
562    }
563
564    #[track_caller]
565    pub fn rust_closure<F: Fn(JsValue, JsValue, JsValue, JsValue) -> JsValue + 'static>(
566        mut self,
567        closure: F,
568    ) -> Self {
569        let js_closure = wasm_bindgen::closure::Closure::wrap(
570            Box::new(closure) as Box<dyn Fn(JsValue, JsValue, JsValue, JsValue) -> JsValue>
571        );
572        let js_sys_fn: &js_sys::Function = js_closure.as_ref().unchecked_ref();
573
574        let js_window = gloo_utils::window();
575        let id = uuid::Uuid::new_v4().to_string();
576        Reflect::set(&js_window, &JsValue::from_str(&id), js_sys_fn).unwrap();
577        js_closure.forget();
578
579        gloo_console::debug!(format!(
580            "Closure at {}:{}:{} set at window.['{id}'].",
581            file!(),
582            line!(),
583            column!()
584        ));
585        self.closure_id = Some(id);
586        self
587    }
588}
589
590impl FnWithArgs<5> {
591    pub fn run_rust_fn<A, B, C, D, E, F, FN: Fn(A, B, C, D, E) -> F>(mut self, _func: FN) -> Self {
592        let fn_name = std::any::type_name::<FN>()
593            .split("::")
594            .collect::<Vec<_>>()
595            .into_iter()
596            .next_back()
597            .unwrap();
598
599        self.body = format!(
600            "{}\nconst _out_ = window.callbacks.{}({});",
601            self.body,
602            fn_name,
603            self.args.join(", ")
604        );
605        self.js_return_value("_out_")
606    }
607
608    #[track_caller]
609    pub fn rust_closure<F: Fn(JsValue, JsValue, JsValue, JsValue, JsValue) -> JsValue + 'static>(
610        mut self,
611        closure: F,
612    ) -> Self {
613        let js_closure = wasm_bindgen::closure::Closure::wrap(Box::new(closure)
614            as Box<dyn Fn(JsValue, JsValue, JsValue, JsValue, JsValue) -> JsValue>);
615        let js_sys_fn: &js_sys::Function = js_closure.as_ref().unchecked_ref();
616
617        let js_window = gloo_utils::window();
618        let id = uuid::Uuid::new_v4().to_string();
619        Reflect::set(&js_window, &JsValue::from_str(&id), js_sys_fn).unwrap();
620        js_closure.forget();
621
622        gloo_console::debug!(format!(
623            "Closure at {}:{}:{} set at window.['{id}'].",
624            file!(),
625            line!(),
626            column!()
627        ));
628        self.closure_id = Some(id);
629        self
630    }
631}
632
633impl FnWithArgs<6> {
634    pub fn run_rust_fn<A, B, C, D, E, F, G, FN: Fn(A, B, C, D, E, F) -> G>(
635        mut self,
636        _func: FN,
637    ) -> Self {
638        let fn_name = std::any::type_name::<FN>()
639            .split("::")
640            .collect::<Vec<_>>()
641            .into_iter()
642            .next_back()
643            .unwrap();
644
645        self.body = format!(
646            "{}\nconst _out_ = window.callbacks.{}({});",
647            self.body,
648            fn_name,
649            self.args.join(", ")
650        );
651        self.js_return_value("_out_")
652    }
653
654    #[track_caller]
655    pub fn rust_closure<
656        F: Fn(JsValue, JsValue, JsValue, JsValue, JsValue, JsValue) -> JsValue + 'static,
657    >(
658        mut self,
659        closure: F,
660    ) -> Self {
661        let js_closure = wasm_bindgen::closure::Closure::wrap(Box::new(closure)
662            as Box<dyn Fn(JsValue, JsValue, JsValue, JsValue, JsValue, JsValue) -> JsValue>);
663        let js_sys_fn: &js_sys::Function = js_closure.as_ref().unchecked_ref();
664
665        let js_window = gloo_utils::window();
666        let id = uuid::Uuid::new_v4().to_string();
667        Reflect::set(&js_window, &JsValue::from_str(&id), js_sys_fn).unwrap();
668        js_closure.forget();
669
670        gloo_console::debug!(format!(
671            "Closure at {}:{}:{} set at window.['{id}'].",
672            file!(),
673            line!(),
674            column!()
675        ));
676        self.closure_id = Some(id);
677        self
678    }
679}
680
681// 7 is the maximum wasm_bindgen can handle rn AFAIK
682impl FnWithArgs<7> {
683    pub fn run_rust_fn<A, B, C, D, E, F, G, H, FN: Fn(A, B, C, D, E, F, G) -> H>(
684        mut self,
685        _func: FN,
686    ) -> Self {
687        let fn_name = std::any::type_name::<FN>()
688            .split("::")
689            .collect::<Vec<_>>()
690            .into_iter()
691            .next_back()
692            .unwrap();
693
694        self.body = format!(
695            "{}\nconst _out_ = window.callbacks.{}({});",
696            self.body,
697            fn_name,
698            self.args.join(", ")
699        );
700        self.js_return_value("_out_")
701    }
702
703    #[track_caller]
704    pub fn rust_closure<
705        F: Fn(JsValue, JsValue, JsValue, JsValue, JsValue, JsValue, JsValue) -> JsValue + 'static,
706    >(
707        mut self,
708        closure: F,
709    ) -> Self {
710        let js_closure = wasm_bindgen::closure::Closure::wrap(Box::new(closure)
711            as Box<
712                dyn Fn(JsValue, JsValue, JsValue, JsValue, JsValue, JsValue, JsValue) -> JsValue,
713            >);
714        let js_sys_fn: &js_sys::Function = js_closure.as_ref().unchecked_ref();
715
716        let js_window = gloo_utils::window();
717        let id = uuid::Uuid::new_v4().to_string();
718        Reflect::set(&js_window, &JsValue::from_str(&id), js_sys_fn).unwrap();
719        js_closure.forget();
720
721        gloo_console::debug!(format!(
722            "Closure at {}:{}:{} set at window.['{id}'].",
723            file!(),
724            line!(),
725            column!()
726        ));
727        self.closure_id = Some(id);
728        self
729    }
730}
731
732#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
733#[serde(untagged)]
734pub enum FnWithArgsOrT<const N: usize, T> {
735    T(T),
736    FnWithArgs(FnWithArgs<N>),
737}
738
739impl<const N: usize, T: for<'a> Deserialize<'a>> FnWithArgsOrT<N, T> {
740    pub fn rationalise_1_level(obj: &JsValue, name: &'static str) {
741        super::rationalise_1_level::<N, Self>(obj, name, |o| match o {
742            FnWithArgsOrT::T(_) => (),
743            FnWithArgsOrT::FnWithArgs(fnwa) => {
744                let _ = Reflect::set(obj, &name.into(), &fnwa.build());
745            }
746        })
747    }
748    pub fn rationalise_2_levels(obj: &JsValue, name: (&'static str, &'static str)) {
749        super::rationalise_2_levels::<N, Self>(obj, name, |a, o| match o {
750            FnWithArgsOrT::T(_) => (),
751            FnWithArgsOrT::FnWithArgs(fnwa) => {
752                let _ = Reflect::set(&a, &name.1.into(), &fnwa.build());
753            }
754        })
755    }
756}
757#[allow(private_bounds)]
758impl<const N: usize, T: ChartJsRsObject> FnWithArgsOrT<N, T> {
759    pub fn is_empty(&self) -> bool {
760        match self {
761            FnWithArgsOrT::T(a) => a.is_empty(),
762            FnWithArgsOrT::FnWithArgs(fnwa) => fnwa.is_empty(),
763        }
764    }
765}
766impl<const N: usize, T: Default> Default for FnWithArgsOrT<N, T> {
767    fn default() -> Self {
768        FnWithArgsOrT::T(T::default())
769    }
770}
771impl<const N: usize, T: Into<String>> From<T> for FnWithArgsOrT<N, String> {
772    fn from(s: T) -> Self {
773        Self::T(s.into())
774    }
775}
776impl<const N: usize, T: Into<NumberString>> From<T> for FnWithArgsOrT<N, NumberString> {
777    fn from(ns: T) -> Self {
778        Self::T(ns.into())
779    }
780}
781impl<const N: usize, T: Into<BoolString>> From<T> for FnWithArgsOrT<N, BoolString> {
782    fn from(bs: T) -> Self {
783        Self::T(bs.into())
784    }
785}
786impl<const N: usize, T> From<FnWithArgs<N>> for FnWithArgsOrT<N, T> {
787    fn from(value: FnWithArgs<N>) -> Self {
788        Self::FnWithArgs(value)
789    }
790}
791
792#[derive(Debug, Clone, Default, PartialEq, Eq)]
793pub struct NumberString(String);
794impl From<NumberOrDateString> for NumberString {
795    fn from(value: NumberOrDateString) -> Self {
796        value.0.into()
797    }
798}
799impl NumberString {
800    pub fn is_empty(&self) -> bool {
801        self.0.is_empty()
802    }
803}
804impl ChartJsRsObject for NumberString {
805    fn is_empty(&self) -> bool {
806        self.is_empty()
807    }
808}
809impl PartialOrd for NumberString {
810    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
811        Some(self.cmp(other))
812    }
813}
814impl Ord for NumberString {
815    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
816        if let Some((s, o)) = self
817            .0
818            .parse::<rust_decimal::Decimal>()
819            .ok()
820            .zip(other.0.parse::<rust_decimal::Decimal>().ok())
821        {
822            s.cmp(&o)
823        } else {
824            self.0.cmp(&other.0)
825        }
826    }
827}
828impl<T: Display> From<T> for NumberString {
829    fn from(s: T) -> Self {
830        Self(s.to_string())
831    }
832}
833#[allow(clippy::to_string_trait_impl)]
834impl ToString for NumberString {
835    fn to_string(&self) -> String {
836        self.0.to_string()
837    }
838}
839impl Serialize for NumberString {
840    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
841    where
842        S: serde::Serializer,
843    {
844        let fnum: Result<f64, _> = self.0.parse();
845        let inum: Result<i64, _> = self.0.parse();
846        if self.0.eq_ignore_ascii_case("null") {
847            return serializer.serialize_none();
848        }
849        match (fnum, inum) {
850            (Ok(_), Ok(inum)) => serializer.serialize_i64(inum),
851            (Ok(fnum), _) => serializer.serialize_f64(fnum),
852            _ => serializer.serialize_str(&self.0),
853        }
854    }
855}
856impl<'de> Deserialize<'de> for NumberString {
857    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
858    where
859        D: serde::Deserializer<'de>,
860    {
861        Any::deserialize(deserializer).map(|soi| Self(soi.to_string()))
862    }
863}
864
865#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
866#[serde(untagged)]
867pub enum NumberStringOrT<T: Serialize + DeserializeOwned> {
868    T(T),
869    NumberString(NumberString),
870}
871impl<'de, T: Serialize + DeserializeOwned> Deserialize<'de> for NumberStringOrT<T> {
872    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
873    where
874        D: de::Deserializer<'de>,
875    {
876        let value = serde_json::Value::deserialize(deserializer)?;
877
878        match serde_json::from_value::<NumberString>(value.clone()) {
879            Ok(ns) => Ok(Self::NumberString(ns)),
880            Err(_) => serde_json::from_value::<T>(value)
881                .map(Self::T)
882                .map_err(de::Error::custom),
883        }
884    }
885}
886impl<T: Serialize + DeserializeOwned> NumberStringOrT<T> {
887    pub fn is_empty(&self) -> bool {
888        match self {
889            NumberStringOrT::T(_t) => false,
890            NumberStringOrT::NumberString(ns) => ns.is_empty(),
891        }
892    }
893}
894
895impl<T: Serialize + ChartJsRsObject, U: Serialize + DeserializeOwned> From<T>
896    for NumberStringOrT<U>
897{
898    fn from(value: T) -> Self {
899        serde_json::from_value(serde_json::to_value(value).unwrap()).unwrap()
900    }
901}