Skip to main content

mdbook_plotly/code_handler/
until.rs

1// Tmp fix
2#![allow(unexpected_cfgs)]
3
4use crate::code_handler::parse_context::ParseContext;
5use crate::preprocessor::config::{MapEvalConfig, MapNamespaceScope};
6use anyhow::{Context, Result, anyhow};
7use fasteval::{Compiler, EvalNamespace, Evaler, Parser, Slab};
8use serde::{Deserialize, Deserializer, Serialize, Serializer, de::DeserializeOwned};
9use serde_json::{Map as JsonMap, Value, value::Index};
10use std::{collections::BTreeMap, fmt::Debug, fmt::Display};
11
12#[cfg(feature = "map-parser-extensions")]
13use chrono::{DateTime, NaiveDate, NaiveDateTime, TimeDelta, Utc};
14#[cfg(feature = "map-parser-extensions")]
15use rand::{Rng, RngExt, SeedableRng, rngs::StdRng};
16
17pub type Map = JsonMap<String, Value>;
18
19type Vars = BTreeMap<String, f64>;
20
21#[derive(Clone, Debug)]
22pub enum DataPack<T> {
23    Data(T),
24    Index(String),
25}
26
27#[allow(dead_code)]
28#[inline]
29pub fn must_translate_from_context<T, N>(
30    obj: &mut Value,
31    context: &ParseContext<'_>,
32    name: N,
33) -> Result<T>
34where
35    T: DeserializeOwned + Serialize + Debug + Clone,
36    N: Index + Display,
37{
38    must_translate_with_config(obj, context.map(), context.map_eval(), name)
39}
40
41#[inline]
42pub fn must_translate_with_config<T, N>(
43    obj: &mut Value,
44    map: &Map,
45    map_eval: &MapEvalConfig,
46    name: N,
47) -> Result<T>
48where
49    T: DeserializeOwned + Serialize + Debug + Clone,
50    N: Index + Display,
51{
52    take_optional(obj, map, map_eval, &name)?.ok_or_else(|| anyhow!("missing `{}` field", name))
53}
54
55#[inline]
56fn take_optional<T, N>(
57    obj: &mut Value,
58    map: &Map,
59    map_eval: &MapEvalConfig,
60    name: &N,
61) -> Result<Option<T>>
62where
63    T: DeserializeOwned + Serialize + Debug + Clone,
64    N: Index + Display,
65{
66    let Some(value) = obj.get_mut(name) else {
67        return Ok(None);
68    };
69
70    serde_json::from_value::<DataPack<T>>(value.take())
71        .with_context(|| format!("failed to deserialize field '{}'", name))?
72        .unwrap(map, map_eval)
73        .with_context(|| format!("failed to unwrap DataPack for field '{}'", name))
74        .map(Some)
75}
76
77#[inline]
78fn try_deser<T: DeserializeOwned>(value: Value, context: &'static str) -> Result<T> {
79    serde_json::from_value(value).context(context)
80}
81
82#[inline]
83fn json_number(value: f64) -> Result<Value> {
84    serde_json::Number::from_f64(value)
85        .map(Value::Number)
86        .ok_or_else(|| anyhow!("failed to create JSON number from {}", value))
87}
88
89#[inline]
90fn usize_count(count: u64, field: &str) -> Result<usize> {
91    usize::try_from(count).with_context(|| format!("{} is too large for this platform", field))
92}
93
94fn value_to_f64(value: &Value) -> Option<f64> {
95    match value {
96        Value::Number(n) => n.as_f64(),
97        Value::Bool(v) => Some(if *v { 1.0 } else { 0.0 }),
98        Value::String(s) => s.parse::<f64>().ok(),
99        _ => None,
100    }
101}
102
103fn lookup_path<'a>(map: &'a Map, name: &str) -> Option<&'a Value> {
104    let path = name.strip_prefix("map.").unwrap_or(name);
105    let mut parts = path.split('.');
106    let first = parts.next()?;
107    let mut value = map.get(first)?;
108
109    for part in parts {
110        match value {
111            Value::Object(obj) => value = obj.get(part)?,
112            Value::Array(arr) => {
113                let idx = part.parse::<usize>().ok()?;
114                value = arr.get(idx)?;
115            }
116            _ => return None,
117        }
118    }
119
120    Some(value)
121}
122
123fn map_value<'a>(map: &'a Map, index: &str) -> Result<&'a Value> {
124    lookup_path(map, index).ok_or_else(|| anyhow!("missing map value `{}`", index))
125}
126
127struct MapNamespace<'a> {
128    map: &'a Map,
129    vars: &'a Vars,
130    scope: &'a MapNamespaceScope,
131}
132
133impl<'a> MapNamespace<'a> {
134    fn new(map: &'a Map, vars: &'a Vars, scope: &'a MapNamespaceScope) -> Self {
135        Self { map, vars, scope }
136    }
137
138    fn lookup_map_value(&self, name: &str) -> Option<f64> {
139        match self.scope {
140            MapNamespaceScope::FullMap => lookup_path(self.map, name).and_then(value_to_f64),
141            MapNamespaceScope::ExportsOnly => {
142                let path = if name.starts_with("map.exports.") {
143                    name.to_owned()
144                } else {
145                    format!("exports.{name}")
146                };
147                lookup_path(self.map, &path).and_then(value_to_f64)
148            }
149        }
150    }
151}
152
153impl EvalNamespace for MapNamespace<'_> {
154    fn lookup(&mut self, name: &str, _args: Vec<f64>, _keybuf: &mut String) -> Option<f64> {
155        self.vars
156            .get(name)
157            .copied()
158            .or_else(|| self.lookup_map_value(name))
159    }
160}
161
162struct EvalContext {
163    parser: Parser,
164    slab: Slab,
165    config: MapEvalConfig,
166}
167
168impl EvalContext {
169    fn new(config: &MapEvalConfig) -> Self {
170        Self {
171            parser: Parser::new(),
172            slab: Slab::new(),
173            config: config.clone(),
174        }
175    }
176
177    fn eval(&mut self, expr: &str, map: &Map, vars: &Vars) -> Result<f64> {
178        let mut namespace = MapNamespace::new(map, vars, &self.config.namespace_scope);
179
180        if !self.config.enabled || !self.config.compile_expressions {
181            let expr_ref = self
182                .parser
183                .parse(expr, &mut self.slab.ps)
184                .with_context(|| format!("failed to parse expression `{}`", expr))?
185                .from(&self.slab.ps);
186
187            return expr_ref
188                .eval(&self.slab, &mut namespace)
189                .with_context(|| format!("failed to evaluate expression `{}`", expr));
190        }
191
192        let expr_ref = self
193            .parser
194            .parse(expr, &mut self.slab.ps)
195            .with_context(|| format!("failed to parse expression `{}`", expr))?
196            .from(&self.slab.ps);
197
198        let compiled = expr_ref.compile(&self.slab.ps, &mut self.slab.cs);
199        Ok(fasteval::eval_compiled!(
200            compiled,
201            &self.slab,
202            &mut namespace
203        ))
204    }
205}
206
207impl<T> DataPack<T>
208where
209    T: DeserializeOwned + Serialize + Debug + Clone,
210{
211    pub fn unwrap(self, map: &Map, map_eval: &MapEvalConfig) -> Result<T> {
212        match self {
213            Self::Data(data) => Ok(data),
214            Self::Index(index) => {
215                let value = map_value(map, &index)?.clone();
216                match serde_json::from_value::<T>(value.clone()) {
217                    Ok(data) => Ok(data),
218                    Err(_) => Self::parse_value(map, value, map_eval)
219                        .with_context(|| format!("failed to resolve map value `{}`", index)),
220                }
221            }
222        }
223    }
224
225    pub fn unwrap_from_context(self, context: &ParseContext<'_>) -> Result<T> {
226        self.unwrap(context.map(), context.map_eval())
227    }
228
229    fn parse_value(map: &Map, value: Value, map_eval: &MapEvalConfig) -> Result<T> {
230        if value.is_object() && value.get("type").is_some() {
231            Self::parse_map(map, value, map_eval)
232        } else {
233            serde_json::from_value(value).context("failed to deserialize value")
234        }
235    }
236
237    fn parse_map(map: &Map, mut value: Value, map_eval: &MapEvalConfig) -> Result<T> {
238        let value_type = value
239            .get("type")
240            .and_then(Value::as_str)
241            .ok_or_else(|| anyhow!("`type` must be a string"))?
242            .to_owned();
243
244        let mut eval = EvalContext::new(map_eval);
245        let vars = Vars::new();
246        let context = ParseContext::new(map, map_eval);
247
248        match value_type.as_str() {
249            "raw" => must_translate_with_config(&mut value, map, map_eval, "data"),
250            "g-number" => parse_g_number(&context, &mut value, &mut eval, &vars),
251            "g-number-list" => parse_g_number_list(&context, &mut value, &mut eval),
252            "g-range" => parse_g_range(&context, &mut value),
253            "g-repeat" => parse_g_repeat(&context, &mut value),
254            "g-linear" => parse_g_linear(&context, &mut value),
255            "if" => parse_if(&context, &mut value, &mut eval, &vars),
256            #[cfg(feature = "map-parser-extensions")]
257            "time" => parse_time(&context, &mut value),
258            #[cfg(feature = "map-parser-extensions")]
259            "g-random" => parse_g_random(&context, &mut value),
260            #[cfg(feature = "map-parser-extensions")]
261            "g-choose" => parse_g_choose(&context, &mut value),
262            "g-env" => parse_g_env(&context, &mut value),
263            "g-join" => parse_g_join(&context, &mut value),
264            _ => Err(anyhow!("unknown type `{}`", value_type)),
265        }
266    }
267}
268
269fn parse_g_number<T>(
270    context: &ParseContext<'_>,
271    value: &mut Value,
272    eval: &mut EvalContext,
273    vars: &Vars,
274) -> Result<T>
275where
276    T: DeserializeOwned,
277{
278    let expr: String = must_translate_from_context(value, context, "expr")?;
279    try_deser(
280        json_number(eval.eval(&expr, context.map(), vars)?)?,
281        "failed to deserialize generated number",
282    )
283}
284
285fn parse_g_number_list<T>(
286    context: &ParseContext<'_>,
287    value: &mut Value,
288    eval: &mut EvalContext,
289) -> Result<T>
290where
291    T: DeserializeOwned,
292{
293    let index_begin: u64 = must_translate_from_context(value, context, "begin")?;
294    let index_end: u64 = must_translate_from_context(value, context, "end")?;
295    let expr: String = must_translate_from_context(value, context, "expr")?;
296
297    let len = index_end.saturating_sub(index_begin);
298    let mut result = Vec::with_capacity(usize_count(len, "g-number-list length")?);
299    let mut vars = Vars::new();
300
301    let compiled = eval
302        .parser
303        .parse(&expr, &mut eval.slab.ps)
304        .with_context(|| format!("failed to parse expression `{}`", expr))?
305        .from(&eval.slab.ps)
306        .compile(&eval.slab.ps, &mut eval.slab.cs);
307
308    for i in index_begin..index_end {
309        vars.insert("i".to_owned(), i as f64);
310        let mut namespace = MapNamespace::new(context.map(), &vars, &eval.config.namespace_scope);
311        let value = if eval.config.enabled && eval.config.compile_expressions {
312            fasteval::eval_compiled!(compiled, &eval.slab, &mut namespace)
313        } else {
314            eval.eval(&expr, context.map(), &vars)?
315        };
316        result.push(json_number(value)?);
317    }
318
319    try_deser(
320        Value::Array(result),
321        "failed to deserialize generated number list",
322    )
323}
324
325fn parse_g_range<T>(context: &ParseContext<'_>, value: &mut Value) -> Result<T>
326where
327    T: DeserializeOwned,
328{
329    let begin: f64 = must_translate_from_context(value, context, "begin")?;
330    let end: f64 = must_translate_from_context(value, context, "end")?;
331    let step: f64 =
332        take_optional(value, context.map(), context.map_eval(), &"step")?.unwrap_or(1.0);
333
334    if step <= 0.0 {
335        return Err(anyhow!("step must be positive"));
336    }
337
338    let capacity = if end > begin {
339        ((end - begin) / step).ceil() as usize
340    } else {
341        0
342    };
343    let mut result = Vec::with_capacity(capacity);
344    let mut current = begin;
345
346    while current < end {
347        result.push(json_number(current)?);
348        current += step;
349    }
350
351    try_deser(
352        Value::Array(result),
353        "failed to deserialize generated range",
354    )
355}
356
357fn parse_g_repeat<T>(context: &ParseContext<'_>, value: &mut Value) -> Result<T>
358where
359    T: DeserializeOwned + Serialize + Debug + Clone,
360{
361    let val: Value = must_translate_from_context(value, context, "value")?;
362    let count: u64 = must_translate_from_context(value, context, "count")?;
363    let count = usize_count(count, "count")?;
364    let result = vec![val; count];
365    try_deser(
366        Value::Array(result),
367        "failed to deserialize repeated values",
368    )
369}
370
371fn parse_g_linear<T>(context: &ParseContext<'_>, value: &mut Value) -> Result<T>
372where
373    T: DeserializeOwned,
374{
375    let begin: f64 = must_translate_from_context(value, context, "begin")?;
376    let end: f64 = must_translate_from_context(value, context, "end")?;
377    let count: u64 = must_translate_from_context(value, context, "count")?;
378
379    if count == 0 {
380        return Err(anyhow!("count must be positive"));
381    }
382
383    let count_usize = usize_count(count, "count")?;
384    let mut result = Vec::with_capacity(count_usize);
385
386    if count == 1 {
387        result.push(json_number(begin)?);
388    } else {
389        let step = (end - begin) / ((count - 1) as f64);
390        for i in 0..count {
391            result.push(json_number(begin + (i as f64) * step)?);
392        }
393    }
394
395    try_deser(
396        Value::Array(result),
397        "failed to deserialize linear spaced values",
398    )
399}
400
401fn parse_if<T>(
402    context: &ParseContext<'_>,
403    value: &mut Value,
404    eval: &mut EvalContext,
405    vars: &Vars,
406) -> Result<T>
407where
408    T: DeserializeOwned + Serialize + Debug + Clone,
409{
410    let condition: String = must_translate_from_context(value, context, "condition")?;
411    let true_val: Value = must_translate_from_context(value, context, "true")?;
412    let false_val: Value = must_translate_from_context(value, context, "false")?;
413    let selected = if eval.eval(&condition, context.map(), vars)? != 0.0 {
414        true_val
415    } else {
416        false_val
417    };
418
419    DataPack::<T>::parse_value(context.map(), selected, &eval.config)
420}
421
422#[cfg(feature = "map-parser-extensions")]
423fn parse_time<T>(context: &ParseContext<'_>, value: &mut Value) -> Result<T>
424where
425    T: DeserializeOwned,
426{
427    let start: String = must_translate_from_context(value, context, "start")?;
428    let end: String = must_translate_from_context(value, context, "end")?;
429    let interval: String = must_translate_from_context(value, context, "interval")?;
430    let format: Option<String> =
431        take_optional(value, context.map(), context.map_eval(), &"format")?;
432
433    let start_dt = parse_time_str(&start)?;
434    let end_dt = parse_time_str(&end)?;
435    let step = parse_duration_str(&interval)?;
436
437    if step <= TimeDelta::zero() {
438        return Err(anyhow!("interval must be positive"));
439    }
440
441    let mut result = Vec::new();
442    let mut current = start_dt;
443
444    while current <= end_dt {
445        let ts = format.as_ref().map_or_else(
446            || current.to_rfc3339(),
447            |fmt| current.format(fmt).to_string(),
448        );
449        result.push(Value::String(ts));
450
451        let Some(next) = current.checked_add_signed(step) else {
452            break;
453        };
454        current = next;
455    }
456
457    try_deser(Value::Array(result), "failed to deserialize time values")
458}
459
460#[cfg(feature = "map-parser-extensions")]
461fn parse_g_random<T>(context: &ParseContext<'_>, value: &mut Value) -> Result<T>
462where
463    T: DeserializeOwned,
464{
465    let min: f64 = must_translate_from_context(value, context, "min")?;
466    let max: f64 = must_translate_from_context(value, context, "max")?;
467
468    if min >= max {
469        return Err(anyhow!("min ({}) must be less than max ({})", min, max));
470    }
471
472    let integer = value
473        .get("integer")
474        .and_then(Value::as_bool)
475        .unwrap_or(false);
476    let seed: Option<u64> = take_optional(value, context.map(), context.map_eval(), &"seed")?;
477    let count: Option<u64> = take_optional(value, context.map(), context.map_eval(), &"count")?;
478
479    let gen_value = |rng: &mut dyn Rng| -> Result<Value> {
480        if integer {
481            if min.fract() != 0.0 || max.fract() != 0.0 {
482                return Err(anyhow!("integer random bounds must be whole numbers"));
483            }
484            Ok(Value::from(rng.random_range(min as i64..max as i64)))
485        } else {
486            json_number(rng.random_range(min..max))
487        }
488    };
489
490    match count {
491        Some(0) => Err(anyhow!("count must be positive")),
492        Some(count) => {
493            let count = usize_count(count, "count")?;
494            let values = with_rng(seed, |rng| {
495                (0..count)
496                    .map(|_| gen_value(rng))
497                    .collect::<Result<Vec<_>>>()
498            })?;
499            try_deser(Value::Array(values), "failed to deserialize g-random array")
500        }
501        None => {
502            let value = with_rng(seed, |rng| gen_value(rng))?;
503            try_deser(value, "failed to deserialize g-random single value")
504        }
505    }
506}
507
508#[cfg(feature = "map-parser-extensions")]
509fn parse_g_choose<T>(context: &ParseContext<'_>, value: &mut Value) -> Result<T>
510where
511    T: DeserializeOwned + Serialize + Debug + Clone,
512{
513    let options: Vec<Value> = must_translate_from_context(value, context, "options")?;
514    if options.is_empty() {
515        return Err(anyhow!("options must not be empty for g-choose"));
516    }
517
518    let seed: Option<u64> = take_optional(value, context.map(), context.map_eval(), &"seed")?;
519    let count: Option<u64> = take_optional(value, context.map(), context.map_eval(), &"count")?;
520
521    match count {
522        Some(0) => Err(anyhow!("count must be positive")),
523        Some(count) => {
524            let count = usize_count(count, "count")?;
525            let selected = with_rng(seed, |rng| {
526                (0..count)
527                    .map(|_| options[rng.random_range(0..options.len())].clone())
528                    .collect::<Vec<_>>()
529            });
530            try_deser(
531                Value::Array(selected),
532                "failed to deserialize g-choose array",
533            )
534        }
535        None => {
536            let picked = with_rng(seed, |rng| {
537                options[rng.random_range(0..options.len())].clone()
538            });
539            try_deser(picked, "failed to deserialize g-choose single value")
540        }
541    }
542}
543
544fn parse_g_env<T>(context: &ParseContext<'_>, value: &mut Value) -> Result<T>
545where
546    T: DeserializeOwned,
547{
548    let name: String = must_translate_from_context(value, context, "name")?;
549    let default: Option<String> =
550        take_optional(value, context.map(), context.map_eval(), &"default")?;
551    let env_val = std::env::var(&name).ok().or(default).ok_or_else(|| {
552        anyhow!(
553            "environment variable '{}' is not set and no default provided",
554            name
555        )
556    })?;
557
558    try_deser(Value::String(env_val), "failed to deserialize env value")
559}
560
561fn parse_g_join<T>(context: &ParseContext<'_>, value: &mut Value) -> Result<T>
562where
563    T: DeserializeOwned,
564{
565    let values: Vec<String> = must_translate_from_context(value, context, "values")?;
566    let separator: String =
567        take_optional(value, context.map(), context.map_eval(), &"separator")?.unwrap_or_default();
568    try_deser(
569        Value::String(values.join(&separator)),
570        "failed to deserialize joined string",
571    )
572}
573
574#[cfg(feature = "map-parser-extensions")]
575fn parse_duration_str(s: &str) -> Result<TimeDelta> {
576    let s = s.trim();
577    if s.is_empty() {
578        return Err(anyhow!("duration string is empty"));
579    }
580
581    let mut total = TimeDelta::zero();
582    let mut num_str = String::new();
583
584    for ch in s.chars() {
585        if ch.is_ascii_digit() || ch == '.' {
586            num_str.push(ch);
587            continue;
588        }
589
590        if ch.is_whitespace() {
591            continue;
592        }
593
594        if !ch.is_alphabetic() {
595            return Err(anyhow!("unexpected character '{}' in duration string", ch));
596        }
597
598        if num_str.is_empty() {
599            return Err(anyhow!("missing number before duration unit '{}'", ch));
600        }
601
602        let num: f64 = num_str
603            .parse()
604            .with_context(|| format!("invalid number in duration: '{}'", num_str))?;
605        if num <= 0.0 {
606            return Err(anyhow!("duration components must be positive"));
607        }
608        num_str.clear();
609
610        let seconds = match ch.to_ascii_lowercase() {
611            's' => num,
612            'm' => num * 60.0,
613            'h' => num * 3_600.0,
614            'd' => num * 86_400.0,
615            'w' => num * 604_800.0,
616            other => return Err(anyhow!("unknown duration unit: '{}'", other)),
617        };
618
619        let delta = TimeDelta::try_seconds(seconds as i64)
620            .ok_or_else(|| anyhow!("duration overflow: {}{}", num, ch))?;
621        total = total
622            .checked_add(&delta)
623            .ok_or_else(|| anyhow!("duration overflow"))?;
624    }
625
626    if !num_str.is_empty() {
627        return Err(anyhow!("trailing number without unit: '{}'", num_str));
628    }
629    if total.is_zero() {
630        return Err(anyhow!("duration must be positive, got: '{}'", s));
631    }
632
633    Ok(total)
634}
635
636#[cfg(feature = "map-parser-extensions")]
637fn parse_time_str(s: &str) -> Result<DateTime<Utc>> {
638    let s = s.trim();
639
640    if let Some(rest) = s.strip_prefix("now") {
641        let base = Utc::now();
642        if rest.is_empty() {
643            return Ok(base);
644        }
645
646        let sign_char = rest
647            .chars()
648            .next()
649            .ok_or_else(|| anyhow!("expected '+' or '-' after 'now'"))?;
650        let duration_str = &rest[sign_char.len_utf8()..];
651        let delta = parse_duration_str(duration_str)?;
652
653        return match sign_char {
654            '+' => base
655                .checked_add_signed(delta)
656                .ok_or_else(|| anyhow!("time overflow for '{}'", s)),
657            '-' => base
658                .checked_sub_signed(delta)
659                .ok_or_else(|| anyhow!("time overflow for '{}'", s)),
660            _ => Err(anyhow!("expected '+' or '-' after 'now', got '{}'", rest)),
661        };
662    }
663
664    if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
665        return Ok(dt.with_timezone(&Utc));
666    }
667
668    for fmt in [
669        "%Y-%m-%dT%H:%M:%S%.f%:z",
670        "%Y-%m-%dT%H:%M:%S%.f",
671        "%Y-%m-%dT%H:%M:%S%:z",
672        "%Y-%m-%dT%H:%M:%S",
673        "%Y-%m-%d %H:%M:%S",
674    ] {
675        if let Ok(dt) = DateTime::parse_from_str(s, fmt) {
676            return Ok(dt.with_timezone(&Utc));
677        }
678        if let Ok(naive) = NaiveDateTime::parse_from_str(s, fmt) {
679            return Ok(naive.and_utc());
680        }
681    }
682
683    if let Ok(date) = NaiveDate::parse_from_str(s, "%Y-%m-%d") {
684        return date
685            .and_hms_opt(0, 0, 0)
686            .map(|s| s.and_utc())
687            .ok_or_else(|| anyhow!("invalid date: '{}'", s));
688    }
689
690    Err(anyhow!(
691        "unable to parse time string: '{}'. Supported formats: RFC 3339, \
692         'YYYY-MM-DDTHH:MM:SS', 'YYYY-MM-DD HH:MM:SS', 'YYYY-MM-DD', \
693         'now', 'now+duration', 'now-duration'",
694        s
695    ))
696}
697
698#[cfg(feature = "map-parser-extensions")]
699fn with_rng<F, R>(seed: Option<u64>, f: F) -> R
700where
701    F: FnOnce(&mut dyn Rng) -> R,
702{
703    if let Some(seed) = seed {
704        let mut rng = StdRng::seed_from_u64(seed);
705        f(&mut rng)
706    } else {
707        let mut rng = rand::rng();
708        f(&mut rng)
709    }
710}
711
712impl<'de, T> Deserialize<'de> for DataPack<T>
713where
714    T: DeserializeOwned + Serialize + Debug + Clone,
715{
716    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
717    where
718        D: Deserializer<'de>,
719    {
720        let value = Value::deserialize(deserializer)?;
721
722        if let Some(index) = value.as_str().and_then(|s| s.strip_prefix("map.")) {
723            return Ok(Self::Index(index.to_owned()));
724        }
725
726        serde_json::from_value::<T>(value)
727            .map(Self::Data)
728            .map_err(serde::de::Error::custom)
729    }
730}
731
732impl<T> Serialize for DataPack<T>
733where
734    T: DeserializeOwned + Serialize + Debug + Clone,
735{
736    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
737    where
738        S: Serializer,
739    {
740        match self {
741            Self::Data(data) => data.serialize(serializer),
742            Self::Index(index) => serializer.serialize_str(&format!("map.{index}")),
743        }
744    }
745}
746
747use plotly::color;
748
749// This is to make Json look clearer when it is written.
750#[allow(clippy::enum_variant_names)]
751#[derive(Clone, Debug, Serialize)]
752#[serde(rename_all = "snake_case")]
753pub enum Color {
754    NamedColor(color::NamedColor),
755    RgbColor(color::Rgb),
756    RgbaColor(color::Rgba),
757}
758
759impl color::Color for Color {}
760
761impl<'de> Deserialize<'de> for Color {
762    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
763    where
764        D: Deserializer<'de>,
765    {
766        let value = Value::deserialize(deserializer)?;
767
768        if let Some(s) = value.as_str()
769            && let Ok(named) = serde_json::from_str::<color::NamedColor>(&format!("\"{s}\""))
770        {
771            return Ok(Self::NamedColor(named));
772        }
773
774        if let Some(s) = value.as_str()
775            && let Some(rgb) = parse_hex_color(s)
776        {
777            return Ok(Self::RgbColor(rgb));
778        }
779
780        if let Ok(rgb) = serde_json::from_value::<color::Rgb>(value.clone()) {
781            return Ok(Self::RgbColor(rgb));
782        }
783
784        if let Ok(rgba) = serde_json::from_value::<color::Rgba>(value) {
785            return Ok(Self::RgbaColor(rgba));
786        }
787
788        Err(serde::de::Error::custom("invalid color format"))
789    }
790}
791
792fn parse_hex_color(value: &str) -> Option<color::Rgb> {
793    let hex = value.strip_prefix('#')?;
794
795    let (r, g, b) = match hex.len() {
796        3 => {
797            let mut chars = hex.chars();
798            let r = chars.next()?;
799            let g = chars.next()?;
800            let b = chars.next()?;
801            let rr = u8::from_str_radix(&format!("{r}{r}"), 16).ok()?;
802            let gg = u8::from_str_radix(&format!("{g}{g}"), 16).ok()?;
803            let bb = u8::from_str_radix(&format!("{b}{b}"), 16).ok()?;
804            (rr, gg, bb)
805        }
806        6 => {
807            let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
808            let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
809            let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
810            (r, g, b)
811        }
812        _ => return None,
813    };
814
815    Some(color::Rgb::new(r, g, b))
816}