cursive_core/builder/
resolvable.rs

1use super::{Config, Context, Error, Object};
2use crate::style::ConcreteEffects;
3use crate::views::BoxedView;
4
5use std::collections::HashMap;
6use std::str::FromStr;
7use std::sync::Arc;
8
9use std::any::Any;
10
11/// Trait for types that can be resolved from a context.
12///
13/// They can be loaded from a config (yaml), or from a stored value (`Box<Any>`).
14pub trait Resolvable {
15    /// Build from a config (a JSON value).
16    ///
17    /// The default implementation always fails.
18    fn from_config(config: &Config, _context: &Context) -> Result<Self, Error>
19    where
20        Self: Sized,
21    {
22        Err(Error::CouldNotLoad {
23            expected_type: std::any::type_name::<Self>().to_string(),
24            config: config.clone(),
25        })
26    }
27
28    /// Build from an `Any` variable.
29    ///
30    /// Default implementation tries to downcast to `Self`.
31    ///
32    /// Override if you want to try downcasting to other types as well.
33    fn from_any(any: Box<dyn Any>) -> Option<Self>
34    where
35        Self: Sized + Any,
36    {
37        any.downcast().ok().map(|b| *b)
38    }
39}
40
41type Resolver<T> = fn(&Config, &Context) -> Result<T, Error>;
42
43fn try_all<T>(config: &Config, context: &Context, fns: &[Resolver<T>]) -> Result<T, Error> {
44    let mut errors = Vec::new();
45
46    for f in fns {
47        match f(config, context) {
48            Ok(res) => return Ok(res),
49            Err(err) => errors.push(err),
50        }
51    }
52
53    Err(Error::AllVariantsFailed {
54        config: config.clone(),
55        errors,
56    })
57}
58
59fn resolve_from_str<T, F, R>(config: &Config, context: &Context, err_map: F) -> Result<T, Error>
60where
61    T: std::str::FromStr,
62    F: FnOnce(T::Err) -> R,
63    R: Into<String>,
64{
65    let str: String = context.resolve(config)?;
66    str.parse()
67        .map_err(|e| Error::invalid_config(err_map(e).into(), config))
68}
69
70// Implement a trait for Fn(A, B), Fn(&A, B), Fn(A, &B), ...
71// We do this by going down a tree:
72// (D C B A)
73//      (C B A) to handle the case for 3 args
74//          ...
75//      <> [] (D C B A) to start actual work for 4 args
76//          <D> [D]  (C B A)        |
77//          <D: ?Sized> [&D] (C B A)        | Here we branch and recurse
78//          <D: ?Sized> [&mut D] (C B A)    |
79//              ...
80//              <A B C D> [A B C D]  ()          |
81//              ...                              |
82//              <A B C: ?Sized D> [A B &C D] ()  | Final implementations
83//              ...                              |
84//              <A: ?Sized B: ?Sized C: ?Sized D: ?Sized> [&mut A &mut B &mut C &mut D] ()
85macro_rules! impl_fn_from_config {
86    // Here is a graceful end for recursion.
87    (
88        $trait:ident
89        ( )
90    ) => { };
91    (
92        $trait:ident
93        < $($letters:ident $(: ?$unbound:ident)?)* >
94        [ $($args:ty)* ]
95        ( )
96    ) => {
97        // The leaf node is the actual implementation
98        #[allow(coherence_leak_check)]
99        impl<Res, $($letters $(: ?$unbound)?),* > $trait for Arc<dyn Fn($($args),*) -> Res + Send + Sync> {}
100    };
101    (
102        $trait:ident
103        < $($letters:ident $(: ?$unbound:ident)?)* >
104        [ $($args:ty)* ]
105        ( $head:ident $($leftover:ident)* )
106    ) => {
107        // Here we just branch per ref type
108        impl_fn_from_config!(
109            $trait
110            < $head $($letters $(: ?$unbound)?)* >
111            [ $head $($args)* ]
112            ( $($leftover)* )
113        );
114        impl_fn_from_config!(
115            $trait
116            < $head: ?Sized $($letters $(: ?$unbound)?)* >
117            [ & $head $($args)* ]
118            ( $($leftover)* )
119        );
120        impl_fn_from_config!(
121            $trait
122            < $head: ?Sized $($letters $(: ?$unbound)?)* >
123            [ &mut $head $($args)* ]
124            ( $($leftover)* )
125        );
126    };
127    (
128        $trait:ident
129        ( $head:ident $($leftover:ident)* )
130    ) => {
131        // First, branch out both the true implementation and the level below.
132        impl_fn_from_config!(
133            $trait
134            <>
135            []
136            ( $head $($leftover)* )
137        );
138        impl_fn_from_config!(
139            $trait
140            ( $($leftover)* )
141        );
142    };
143}
144
145/// A wrapper around a value that cannot be parsed from config, but can still be stored/retrieved
146/// in a context.
147///
148/// This brings a `Resolvable` implementation that will always fail.
149#[derive(Clone, Debug, Eq, PartialEq)]
150pub struct NoConfig<T>(pub T);
151
152impl<T> NoConfig<T> {
153    /// Return the wrapped object.
154    pub fn into_inner(self) -> T {
155        self.0
156    }
157}
158
159impl<T> From<T> for NoConfig<T> {
160    fn from(t: T) -> Self {
161        NoConfig(t)
162    }
163}
164
165// Implement Resolvable for the wrapper, so we can resolve it.
166impl<T> Resolvable for NoConfig<T> {
167    // We leave from_config as default (it'll always fail).
168    // As stated in the name, this cannot be loaded from a Config.
169
170    // But when loading from a variable, accept an unwrapped value.
171    //
172    // So users can store a `T` and load it as `NoConfig<T>`, without having to implement
173    // `Resolvable` for `T`.
174    fn from_any(any: Box<dyn Any>) -> Option<Self>
175    where
176        Self: Sized + Any,
177    {
178        // First try an actual NoConfig<T>
179        any.downcast()
180            .map(|b| *b)
181            // Then, try a bare T
182            .or_else(|any| any.downcast::<T>().map(|b| NoConfig(*b)))
183            .ok()
184    }
185}
186
187impl<T> Resolvable for Option<T>
188where
189    T: Resolvable,
190{
191    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
192        if let Config::Null = config {
193            Ok(None)
194        } else {
195            Ok(Some(T::from_config(config, context)?))
196        }
197    }
198
199    fn from_any(any: Box<dyn Any>) -> Option<Self>
200    where
201        Self: Sized + Any,
202    {
203        // First try the option, then try bare T.
204        any.downcast::<Self>()
205            .map(|b| *b)
206            .or_else(|any| T::from_any(any).map(|b| Some(b)).ok_or(()))
207            .ok()
208    }
209}
210
211impl Resolvable for Config {
212    fn from_config(config: &Config, _context: &Context) -> Result<Self, Error> {
213        Ok(config.clone())
214    }
215}
216
217impl Resolvable for Object {
218    fn from_config(config: &Config, _context: &Context) -> Result<Self, Error> {
219        config
220            .as_object()
221            .ok_or_else(|| Error::invalid_config("Expected an object", config))
222            .cloned()
223    }
224}
225
226impl Resolvable for Box<dyn crate::view::View> {
227    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
228        let boxed: BoxedView = context.build(config)?;
229        Ok(boxed.unwrap())
230    }
231}
232
233impl Resolvable for BoxedView {
234    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
235        context.build(config)
236    }
237}
238
239impl Resolvable for crate::style::BaseColor {
240    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
241        resolve_from_str(config, context, |_| "Expected base color")
242    }
243}
244
245impl Resolvable for crate::style::Palette {
246    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
247        let mut palette = Self::default();
248
249        let config = config
250            .as_object()
251            .ok_or_else(|| Error::invalid_config("Expected object", config))?;
252
253        for (key, value) in config {
254            if let Ok(value) = context.resolve(value) {
255                palette.set_color(key, value);
256            } else if let Some(value) = value.as_object() {
257                // We don't currently support namespace themes here.
258                // ¯\_(ツ)_/¯
259                log::warn!(
260                    "Namespaces are not currently supported in configs. (When reading color for `{key}`: {value:?}.)"
261                );
262            }
263        }
264
265        Ok(palette)
266    }
267}
268
269impl Resolvable for crate::style::BorderStyle {
270    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
271        let borders: String = context.resolve(config)?;
272
273        Ok(Self::from(&borders))
274    }
275}
276
277impl Resolvable for crate::style::PaletteStyle {
278    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
279    where
280        Self: Sized,
281    {
282        resolve_from_str(config, context, |_| "Expected valid palette style")
283    }
284}
285
286impl Resolvable for crate::style::StyleType {
287    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
288    where
289        Self: Sized,
290    {
291        try_all(
292            config,
293            context,
294            &[
295                |config, context| {
296                    let style = context.resolve::<crate::style::PaletteStyle>(config)?;
297                    Ok(crate::style::StyleType::Palette(style))
298                },
299                |config, context| {
300                    let style = context.resolve::<crate::style::Style>(config)?;
301                    Ok(crate::style::StyleType::Style(style))
302                },
303            ],
304        )
305    }
306}
307
308impl Resolvable for crate::style::Style {
309    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
310    where
311        Self: Sized,
312    {
313        try_all(
314            config,
315            context,
316            &[
317                |config, context| {
318                    // Option A: explicit effects and color
319                    let effects = context.resolve(&config["effects"])?;
320                    let color = context.resolve(&config["color"])?;
321
322                    Ok(crate::style::Style { effects, color })
323                },
324                |config, context| {
325                    // Option B: just a color style
326                    let style = context.resolve::<crate::style::ColorStyle>(config)?;
327                    Ok(crate::style::Style::from(style))
328                },
329                |config, context| {
330                    // Option C: just effect(s)
331                    let effects = context.resolve::<crate::style::Effects>(config)?;
332                    Ok(crate::style::Style::from(effects))
333                },
334            ],
335        )
336    }
337}
338
339impl Resolvable for crate::theme::Theme {
340    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
341        let mut theme = Self::default();
342
343        if let Some(shadow) = context.resolve(&config["shadow"])? {
344            theme.shadow = shadow;
345        }
346
347        if let Some(borders) = context.resolve(&config["borders"])? {
348            theme.borders = borders;
349        }
350
351        if let Some(palette) = context.resolve(&config["palette"])? {
352            theme.palette = palette;
353        }
354
355        Ok(theme)
356    }
357}
358
359// A bunch of `impl From<T: Resolvable>` can easily implement Resolvable
360impl<T> Resolvable for Box<T>
361where
362    T: 'static + Resolvable,
363{
364    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
365        Ok(Box::new(T::from_config(config, context)?))
366    }
367
368    fn from_any(any: Box<dyn Any>) -> Option<Self> {
369        // First try a Box<T>
370        match any.downcast::<Self>().map(|b| *b) {
371            Ok(res) => Some(res),
372            // If it fails, try T::from_any (unboxed stored value)
373            Err(any) => T::from_any(any).map(Into::into),
374        }
375    }
376}
377
378impl<T> Resolvable for Arc<T>
379where
380    T: 'static + Resolvable,
381{
382    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
383        Ok(Arc::new(T::from_config(config, context)?))
384    }
385
386    fn from_any(any: Box<dyn Any>) -> Option<Self> {
387        // First try a Arc<T>
388        match any.downcast::<Self>().map(|b| *b) {
389            Ok(res) => Some(res),
390            Err(any) => T::from_any(any).map(Into::into),
391        }
392    }
393}
394
395impl<K, T> Resolvable for HashMap<K, T>
396where
397    K: 'static + std::str::FromStr + Eq + std::hash::Hash,
398    T: 'static + Resolvable,
399{
400    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
401        let config = match config {
402            Config::Null => return Ok(HashMap::new()),
403            Config::Object(config) => config,
404            // Missing value get an empty vec
405            _ => return Err(Error::invalid_config("Expected object", config)),
406        };
407
408        config
409            .iter()
410            .map(|(k, v)| {
411                context.resolve(v).and_then(|v| {
412                    Ok((
413                        k.parse::<K>()
414                            .map_err(|_| Error::invalid_config("Error parsing key", config))?,
415                        v,
416                    ))
417                })
418            })
419            .collect()
420    }
421}
422
423impl<T> Resolvable for Vec<T>
424where
425    T: 'static + Resolvable,
426{
427    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
428        let config = match config {
429            Config::Array(config) => config,
430            // Missing value get an empty vec
431            Config::Null => return Ok(Vec::new()),
432            _ => return Err(Error::invalid_config("Expected array", config)),
433        };
434
435        config.iter().map(|v| context.resolve(v)).collect()
436    }
437
438    // TODO: Allow loading from `Vec<Box<Any>>` and downcasting one by one?
439}
440
441impl<T, const N: usize> Resolvable for [T; N]
442where
443    T: 'static + Resolvable + Clone,
444{
445    fn from_any(any: Box<dyn Any>) -> Option<Self>
446    where
447        Self: Sized + Any,
448    {
449        // Allow storing a `Vec` with the correct size
450        any.downcast()
451            .map(|b| *b)
452            .or_else(|any| {
453                any.downcast::<Vec<T>>()
454                    .ok()
455                    .and_then(|vec| (*vec).try_into().ok())
456                    .ok_or(())
457            })
458            .ok()
459    }
460
461    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
462        let vec = Vec::<T>::from_config(config, context)?;
463        vec.try_into()
464            .map_err(|_| Error::invalid_config("Expected array of size {N}", config))
465    }
466}
467
468impl Resolvable for crate::style::Rgb<f32> {
469    fn from_any(any: Box<dyn Any>) -> Option<Self> {
470        // Accept both Rgb<u8> and Rgb<f32> as stored values.
471        any.downcast()
472            .map(|b| *b)
473            .or_else(|any| {
474                any.downcast::<crate::style::Rgb<u8>>()
475                    .map(|rgb| rgb.as_f32())
476            })
477            .ok()
478    }
479
480    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
481    where
482        Self: Sized,
483    {
484        // Try as a hex string?
485        if let Ok(rgb) = context.resolve::<String>(config) {
486            if let Ok(rgb) = rgb.parse::<crate::style::Rgb<u8>>() {
487                return Ok(rgb.as_f32());
488            }
489        }
490
491        // Allow storing a list of f32 or a list of u8.
492        if let Ok(rgb) = context.resolve::<[u8; 3]>(config) {
493            // Try u8 first. If it's all u8, trying as f32 would also work.
494            return Ok(crate::style::Rgb::<u8>::from(rgb).as_f32());
495        }
496
497        if let Ok(rgb) = context.resolve::<[f32; 3]>(config) {
498            return Ok(Self::from(rgb));
499        }
500
501        // TODO: Here too, try as u8 first, then again as f32.
502        if let Some(rgb) = config.as_object().and_then(|config| {
503            let r = config.get("r").or_else(|| config.get("R"))?;
504            let g = config.get("g").or_else(|| config.get("G"))?;
505            let b = config.get("b").or_else(|| config.get("B"))?;
506
507            let r = context.resolve(r).ok()?;
508            let g = context.resolve(g).ok()?;
509            let b = context.resolve(b).ok()?;
510
511            Some(Self { r, g, b })
512        }) {
513            return Ok(rgb);
514        }
515
516        Err(Error::invalid_config(
517            "Could not parse as a RGB color.",
518            config,
519        ))
520    }
521}
522
523impl Resolvable for crate::style::Rgb<u8> {
524    fn from_any(any: Box<dyn Any>) -> Option<Self>
525    where
526        Self: Sized + Any,
527    {
528        // Accept both Rgb<u8> and Rgb<f32> as stored values.
529        any.downcast()
530            .map(|b| *b)
531            .or_else(|any| {
532                any.downcast::<crate::style::Rgb<f32>>()
533                    .map(|rgb| rgb.as_u8())
534            })
535            .ok()
536    }
537
538    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
539    where
540        Self: Sized,
541    {
542        // Try as a hex string?
543        if let Ok(rgb) = context.resolve::<String>(config) {
544            if let Ok(rgb) = rgb.parse::<crate::style::Rgb<u8>>() {
545                return Ok(rgb);
546            }
547        }
548
549        // Allow storing a list of f32 or a list of u8.
550        if let Ok(rgb) = context.resolve::<[u8; 3]>(config) {
551            // Try u8 first. If it's all u8, trying as f32 would also work.
552            return Ok(Self::from(rgb));
553        }
554
555        // TODO: Here too, try as u8 first, then again as f32.
556        if let Some(rgb) = config.as_object().and_then(|config| {
557            let r = config.get("r").or_else(|| config.get("R"))?;
558            let g = config.get("g").or_else(|| config.get("G"))?;
559            let b = config.get("b").or_else(|| config.get("B"))?;
560
561            let r = context.resolve(r).ok()?;
562            let g = context.resolve(g).ok()?;
563            let b = context.resolve(b).ok()?;
564
565            Some(Self { r, g, b })
566        }) {
567            return Ok(rgb);
568        }
569
570        let rgb: [f32; 3] = context.resolve(config)?;
571        Ok(crate::style::Rgb::<f32>::from(rgb).as_u8())
572    }
573}
574
575impl Resolvable for crate::style::gradient::Dynterpolator {
576    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
577    where
578        Self: Sized,
579    {
580        let config = config
581            .as_object()
582            .ok_or_else(|| Error::invalid_config("Expected object", config))?;
583        match config
584            .iter()
585            .next()
586            .map(|(key, config)| (key.as_str(), config))
587            .ok_or_else(|| Error::invalid_config("Expected non-empty object", config))?
588        {
589            ("radial", config) => {
590                let radial: crate::style::gradient::Radial = context.resolve(config)?;
591                Ok(Box::new(radial))
592            }
593            ("angled", config) => {
594                let angled: crate::style::gradient::Angled = context.resolve(config)?;
595                Ok(Box::new(angled))
596            }
597            ("bilinear", config) => {
598                let bilinear: crate::style::gradient::Bilinear = context.resolve(config)?;
599                Ok(Box::new(bilinear))
600            }
601            // TODO: Allow external libraries to define their own blueprints to be used here?
602            // Something like a type-map of blueprints?...
603            (key, _) => Err(Error::invalid_config(
604                format!("Received unsupported gradient type {key}."),
605                config,
606            )),
607        }
608    }
609}
610
611impl Resolvable for crate::style::gradient::Bilinear {
612    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
613    where
614        Self: Sized,
615    {
616        let top_left = context.resolve(&config["top_left"])?;
617        let top_right = context.resolve(&config["top_right"])?;
618        let bottom_right = context.resolve(&config["bottom_right"])?;
619        let bottom_left = context.resolve(&config["bottom_left"])?;
620        Ok(Self {
621            top_left,
622            top_right,
623            bottom_right,
624            bottom_left,
625        })
626    }
627}
628
629impl Resolvable for crate::style::gradient::Angled {
630    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
631    where
632        Self: Sized,
633    {
634        let angle_rad = match context.resolve(&config["angle_rad"]) {
635            Ok(angle_rad) => angle_rad,
636            Err(err1) => match context.resolve::<f32>(&config["angle_deg"]) {
637                Ok(angle_deg) => angle_deg * std::f32::consts::PI / 180f32,
638                Err(err2) => {
639                    return Err(Error::AllVariantsFailed {
640                        config: config.clone(),
641                        errors: vec![err1, err2],
642                    })
643                }
644            },
645        };
646        let gradient = context.resolve(&config["gradient"])?;
647        Ok(Self {
648            angle_rad,
649            gradient,
650        })
651    }
652}
653
654impl Resolvable for crate::style::gradient::Radial {
655    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
656    where
657        Self: Sized,
658    {
659        let center = context.resolve(&config["center"])?;
660        let gradient = context.resolve(&config["gradient"])?;
661        Ok(Self { center, gradient })
662    }
663}
664
665impl Resolvable for crate::style::gradient::Linear {
666    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
667    where
668        Self: Sized,
669    {
670        use crate::style::Rgb;
671
672        // Options:
673        // - A list of Rgb (evenly spaced)
674        // - A list of (f32, Rgb)
675        // - An object with start, end, and optionally middle, with a list of (f32, Rgb)
676        // - Some presets strings? Rainbow?
677        match config {
678            Config::Array(array) => {
679                let mut errors = Vec::new();
680
681                match array
682                    .iter()
683                    .map(|config| context.resolve::<Rgb<f32>>(config))
684                    .collect::<Result<Vec<_>, _>>()
685                {
686                    Ok(colors) => return Ok(Self::evenly_spaced(&colors)),
687                    Err(err) => errors.push(err),
688                }
689
690                match array
691                    .iter()
692                    .map(|config| context.resolve(config))
693                    .collect::<Result<Vec<_>, _>>()
694                {
695                    Ok(points) => return Ok(Self::new(points)),
696                    Err(err) => errors.push(err),
697                }
698
699                return Err(Error::AllVariantsFailed {
700                    config: config.clone(),
701                    errors,
702                });
703            }
704            Config::Object(object) => {
705                if let (Some(start), Some(end)) = (object.get("start"), object.get("end")) {
706                    return Ok(Self::simple(
707                        context.resolve::<Rgb<f32>>(start)?,
708                        context.resolve::<Rgb<f32>>(end)?,
709                    ));
710                }
711
712                if let Some(points) = object.get("points") {
713                    let points = points
714                        .as_array()
715                        .ok_or_else(|| Error::invalid_config("Expected array", config))?;
716
717                    let points = points
718                        .iter()
719                        .map(|config| context.resolve(config))
720                        .collect::<Result<Vec<_>, _>>()?;
721
722                    return Ok(Self::new(points));
723                }
724            }
725            Config::String(string) => match string.as_str() {
726                // TODO: Allow external libs to define their own aliases to resolve here?
727                "rainbow" => return Ok(Self::rainbow()),
728                "black_to_white" | "black to white" => return Ok(Self::black_to_white()),
729                _ => (),
730            },
731            _ => (),
732        }
733
734        Err(Error::invalid_config(
735            "Expected array, object or string",
736            config,
737        ))
738    }
739}
740
741// ```yaml
742// color: red
743// color:
744//      dark: red
745// color:
746//      rgb: [1, 2, 4]
747// ```
748impl Resolvable for crate::style::Color {
749    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
750        Ok(match config {
751            Config::String(config) => Self::parse(config)
752                .ok_or_else(|| Error::invalid_config("Could not parse color", config))?,
753            Config::Object(config) => {
754                // Possibly keywords:
755                // - light
756                // - dark
757                // - rgb
758                let (key, value) = config
759                    .iter()
760                    .next()
761                    .ok_or_else(|| Error::invalid_config("", config))?;
762                match key.as_str() {
763                    "light" => Self::Light(context.resolve(value)?),
764                    "dark" => Self::Dark(context.resolve(value)?),
765                    "rgb" => {
766                        let array: [u8; 3] = context.resolve(value)?;
767                        Self::Rgb(array[0], array[1], array[2])
768                    }
769                    _ => return Err(Error::invalid_config("Found unexpected key", config)),
770                }
771            }
772            Config::Array(_) => {
773                // Assume r, g, b
774                let array: [u8; 3] = context.resolve(config)?;
775                Self::Rgb(array[0], array[1], array[2])
776            }
777            _ => return Err(Error::invalid_config("Found unsupported type", config)),
778        })
779    }
780}
781
782impl Resolvable for crate::style::ConcreteStyle {
783    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
784    where
785        Self: Sized,
786    {
787        // We _need_ colors for a concrete style, since "inherit parent" is not an option.
788        try_all(
789            config,
790            context,
791            &[
792                |config, context| {
793                    let color = context.resolve(config)?;
794                    Ok(Self {
795                        effects: ConcreteEffects::empty(),
796                        color,
797                    })
798                },
799                |config, context| {
800                    let effects = context
801                        .resolve::<Option<ConcreteEffects>>(&config["effects"])?
802                        .unwrap_or_else(ConcreteEffects::empty);
803                    let color = context.resolve(&config["color"])?;
804                    Ok(Self { effects, color })
805                },
806            ],
807        )
808    }
809}
810
811impl Resolvable for crate::style::ConcreteEffects {
812    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
813    where
814        Self: Sized,
815    {
816        use crate::style::Effect;
817
818        // Either a single effect string, or a list of effects
819        try_all(
820            config,
821            context,
822            &[
823                |config, context| {
824                    // Option A: a single effect as string
825                    let effect = resolve_from_str(config, context, |_| "Expected valid effect")?;
826                    {
827                        Ok(ConcreteEffects::only(effect))
828                    }
829                },
830                |config, context| {
831                    // Option B: a list of effects
832                    let effects = context.resolve::<Vec<Effect>>(config)?;
833                    Ok(ConcreteEffects::from_iter(effects.iter().copied()))
834                },
835            ],
836        )
837    }
838}
839
840impl Resolvable for crate::style::Effects {
841    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
842    where
843        Self: Sized,
844    {
845        use crate::style::{Effect, EffectStatus, Effects};
846
847        // Option A: a single effect as a string.
848        if let Ok(effect) = resolve_from_str(config, context, |_| "Expected valid effect") {
849            return Ok(Effects::only(effect));
850        }
851
852        // Option B: a list of effects to enable.
853        if let Ok(effects) = context.resolve::<Vec<Effect>>(config) {
854            let mut result = Effects::empty();
855            for effect in effects {
856                result.insert(effect);
857            }
858            return Ok(result);
859        }
860
861        // Option C: a map of effect to status
862        if let Ok(statuses) = context.resolve::<HashMap<Effect, EffectStatus>>(config) {
863            let mut result = Effects::empty();
864            for (key, value) in statuses {
865                result.statuses[key] = value;
866            }
867            return Ok(result);
868        }
869
870        Err(Error::invalid_config(
871            "Expected either an effect, a list of effects, or a map of effect statuses",
872            config,
873        ))
874    }
875}
876
877impl Resolvable for crate::style::EffectStatus {
878    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
879    where
880        Self: Sized,
881    {
882        resolve_from_str(config, context, |_| "Expected valid effect status")
883    }
884}
885
886impl Resolvable for crate::style::Effect {
887    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
888    where
889        Self: Sized,
890    {
891        resolve_from_str(config, context, |_| "Expected valid effect")
892    }
893}
894
895impl Resolvable for crate::style::ColorPair {
896    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
897    where
898        Self: Sized,
899    {
900        if let Ok(config_str) = context.resolve::<String>(config) {
901            if config_str == "terminal_default" {
902                return Ok(Self::terminal_default());
903            }
904
905            return Err(Error::invalid_config(
906                "Expected {front, back} or terminal_default",
907                config,
908            ));
909        }
910
911        let front = context.resolve(&config["front"])?;
912        let back = context.resolve(&config["back"])?;
913        Ok(Self { front, back })
914    }
915}
916
917impl Resolvable for crate::style::PaletteColor {
918    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
919        resolve_from_str(config, context, |_| "Unrecognized palette color")
920    }
921}
922
923impl Resolvable for crate::style::ColorType {
924    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
925        if let Ok(color) = context.resolve(config) {
926            return Ok(Self::Color(color));
927        }
928
929        match config {
930            Config::String(config) => Self::from_str(config)
931                .map_err(|_| Error::invalid_config("Unrecognized color type", config)),
932            Config::Object(config) => {
933                // Either a palette or a color?
934                let (key, value) = config
935                    .iter()
936                    .next()
937                    .ok_or_else(|| Error::invalid_config("Found empty object", config))?;
938                Ok(match key.as_str() {
939                    "palette" => Self::Palette(context.resolve(value)?),
940                    "color" => Self::Color(context.resolve(value)?),
941                    _ => {
942                        return Err(Error::invalid_config(
943                            format!("Found unrecognized key `{key}` in color type config"),
944                            config,
945                        ))
946                    }
947                })
948            }
949            _ => Err(Error::invalid_config("Expected string or object", config)),
950        }
951    }
952}
953
954impl Resolvable for crate::style::ColorStyle {
955    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
956        try_all(
957            config,
958            context,
959            &[
960                |config, context| {
961                    let front = context.resolve(&config["front"])?;
962                    let back = context.resolve(&config["back"])?;
963
964                    Ok(crate::style::ColorStyle { front, back })
965                },
966                |config, context| {
967                    let front = context.resolve::<crate::style::ColorType>(config)?;
968                    Ok(crate::style::ColorStyle::front(front))
969                },
970            ],
971        )
972    }
973}
974
975impl Resolvable for crate::view::ScrollStrategy {
976    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
977    where
978        Self: Sized,
979    {
980        resolve_from_str(config, context, |_| {
981            "Expected one of keep_row, stick_to_top, stick_to_bottom"
982        })
983    }
984}
985
986impl Resolvable for crate::view::Offset {
987    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
988        if let Some("center" | "Center") = config.as_str() {
989            return Ok(Self::Center);
990        }
991
992        let config = config
993            .as_object()
994            .ok_or_else(|| Error::invalid_config("Expected `center` or an object.", config))?;
995
996        let (key, value) = config
997            .iter()
998            .next()
999            .ok_or_else(|| Error::invalid_config("Expected non-empty object.", config))?;
1000
1001        match key.as_str() {
1002            "Absolute" | "absolute" => Ok(Self::Absolute(context.resolve(value)?)),
1003            "Parent" | "parent" => Ok(Self::Parent(context.resolve(value)?)),
1004            _ => Err(Error::invalid_config("Unexpected key `{key}`.", config)),
1005        }
1006    }
1007}
1008
1009impl Resolvable for crate::view::SizeConstraint {
1010    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
1011    where
1012        Self: Sized,
1013    {
1014        Ok(match config {
1015            Config::String(config_str) => match config_str.as_str() {
1016                "Free" | "free" => Self::Free,
1017                "Full" | "full" => Self::Full,
1018                _ => {
1019                    return Err(Error::invalid_config(
1020                        "Expected `free` or `full` string, or object",
1021                        config,
1022                    ))
1023                }
1024            },
1025            Config::Object(config_obj) => {
1026                if config_obj.len() != 1 {
1027                    return Err(Error::invalid_config(
1028                        "Expected object with a single `fixed`, `at_most` or `at_least` key",
1029                        config,
1030                    ));
1031                }
1032
1033                let (key, value) = config_obj.iter().next().unwrap();
1034                let value = context.resolve(value)?;
1035
1036                match key.as_str() {
1037                    "fixed" => Self::Fixed(value),
1038                    "at_most" => Self::AtMost(value),
1039                    "at_least" => Self::AtLeast(value),
1040                    _ => {
1041                        return Err(Error::invalid_config(
1042                            "Expected `fixed`, `at_most` or `at_least` key",
1043                            config,
1044                        ))
1045                    }
1046                }
1047            }
1048            _ => return Err(Error::invalid_config("Expected string or object", config)),
1049        })
1050    }
1051}
1052
1053impl Resolvable for crate::views::LayerPosition {
1054    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
1055    where
1056        Self: Sized,
1057    {
1058        fn option_a(
1059            config: &Config,
1060            context: &Context,
1061        ) -> Result<crate::views::LayerPosition, Error> {
1062            let value = context.resolve(&config["from_back"])?;
1063            Ok(crate::views::LayerPosition::FromBack(value))
1064        }
1065
1066        fn option_b(
1067            config: &Config,
1068            context: &Context,
1069        ) -> Result<crate::views::LayerPosition, Error> {
1070            let value = context.resolve(&config["from_front"])?;
1071            Ok(crate::views::LayerPosition::FromFront(value))
1072        }
1073
1074        option_a(config, context).or_else(|_| option_b(config, context))
1075    }
1076}
1077
1078impl Resolvable for String {
1079    fn from_config(config: &Config, _context: &Context) -> Result<Self, Error> {
1080        match config {
1081            Config::String(config) => Ok(config.into()),
1082            Config::Bool(config) => Ok(format!("{config}")),
1083            Config::Number(n) => Ok(format!("{n}")),
1084            _ => Err(Error::invalid_config("Cannot resolve as string", config)),
1085        }
1086    }
1087
1088    fn from_any(any: Box<dyn Any>) -> Option<Self>
1089    where
1090        Self: Sized + Any,
1091    {
1092        // Allow either String or &'static str (for easy literal insertion)
1093        any.downcast()
1094            .map(|b| *b)
1095            .or_else(|any| any.downcast::<&'static str>().map(|str| String::from(*str)))
1096            .ok()
1097    }
1098}
1099
1100impl<A, B> Resolvable for (A, B)
1101where
1102    A: Resolvable + 'static,
1103    B: Resolvable + 'static,
1104{
1105    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
1106    where
1107        Self: Sized,
1108    {
1109        let config = config
1110            .as_array()
1111            .ok_or_else(|| Error::invalid_config("Expected array", config))?;
1112
1113        Ok((context.resolve(&config[0])?, context.resolve(&config[1])?))
1114    }
1115}
1116
1117impl<A, B, C> Resolvable for (A, B, C)
1118where
1119    A: Resolvable + 'static,
1120    B: Resolvable + 'static,
1121    C: Resolvable + 'static,
1122{
1123    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
1124    where
1125        Self: Sized,
1126    {
1127        let config = config
1128            .as_array()
1129            .ok_or_else(|| Error::invalid_config("Expected array", config))?;
1130
1131        Ok((
1132            context.resolve(&config[0])?,
1133            context.resolve(&config[1])?,
1134            context.resolve(&config[2])?,
1135        ))
1136    }
1137}
1138
1139impl Resolvable for crate::utils::markup::StyledString {
1140    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
1141    where
1142        Self: Sized,
1143    {
1144        let text: String = context.resolve(config)?;
1145        Ok(Self::plain(text))
1146    }
1147
1148    fn from_any(any: Box<dyn Any>) -> Option<Self>
1149    where
1150        Self: Sized + Any,
1151    {
1152        let any = match any.downcast::<Self>().map(|b| *b) {
1153            Ok(res) => return Some(res),
1154            Err(any) => any,
1155        };
1156
1157        any.downcast::<String>().map(|b| Self::plain(*b)).ok()
1158    }
1159}
1160
1161impl Resolvable for bool {
1162    fn from_config(config: &Config, _context: &Context) -> Result<Self, Error> {
1163        config
1164            .as_bool()
1165            .ok_or_else(|| Error::invalid_config("Expected bool type", config))
1166    }
1167}
1168
1169macro_rules! resolve_float {
1170    ($ty:ty) => {
1171        impl Resolvable for $ty {
1172            fn from_any(any: Box<dyn Any>) -> Option<Self>
1173            {
1174                // Accept both f32 and f64, just cast between them.
1175                any.downcast::<f32>()
1176                    .map(|b| *b as Self)
1177                    .or_else(|any| {
1178                        any.downcast::<f64>()
1179                            .map(|b| *b as Self)
1180                    })
1181                    .ok()
1182            }
1183
1184            fn from_config(config: &Config, _context: &Context) -> Result<Self, Error> {
1185                config
1186                    .as_f64()  // This already handles converting from integers
1187                    .map(|config| config as Self)
1188                    .ok_or_else(|| Error::invalid_config(format!("Expected float value"), config))
1189            }
1190        }
1191    };
1192}
1193
1194macro_rules! resolve_unsigned {
1195    ($ty:ty) => {
1196        impl Resolvable for $ty {
1197            fn from_any(any: Box<dyn Any>) -> Option<Self> {
1198                // Accept any signed or unsigned integer, as long as it fits.
1199                any.downcast::<u8>()
1200                    .map(|b| (*b).try_into().ok())
1201                    .or_else(|any| any.downcast::<u16>().map(|b| (*b).try_into().ok()))
1202                    .or_else(|any| any.downcast::<u32>().map(|b| (*b).try_into().ok()))
1203                    .or_else(|any| any.downcast::<u64>().map(|b| (*b).try_into().ok()))
1204                    .or_else(|any| any.downcast::<u64>().map(|b| (*b).try_into().ok()))
1205                    .or_else(|any| any.downcast::<u128>().map(|b| (*b).try_into().ok()))
1206                    .or_else(|any| any.downcast::<usize>().map(|b| (*b).try_into().ok()))
1207                    .or_else(|any| any.downcast::<i8>().map(|b| (*b).try_into().ok()))
1208                    .or_else(|any| any.downcast::<i16>().map(|b| (*b).try_into().ok()))
1209                    .or_else(|any| any.downcast::<i32>().map(|b| (*b).try_into().ok()))
1210                    .or_else(|any| any.downcast::<i64>().map(|b| (*b).try_into().ok()))
1211                    .or_else(|any| any.downcast::<i128>().map(|b| (*b).try_into().ok()))
1212                    .or_else(|any| any.downcast::<isize>().map(|b| (*b).try_into().ok()))
1213                    .ok()?
1214            }
1215
1216            fn from_config(config: &Config, _context: &Context) -> Result<Self, Error> {
1217                config
1218                    .as_u64()
1219                    .and_then(|config| Self::try_from(config).ok())
1220                    .ok_or_else(|| {
1221                        Error::invalid_config(format!("Expected unsigned <= {}", Self::MAX), config)
1222                    })
1223            }
1224        }
1225    };
1226}
1227
1228macro_rules! resolve_signed {
1229    ($ty:ty) => {
1230        impl Resolvable for $ty {
1231            fn from_any(any: Box<dyn Any>) -> Option<Self> {
1232                // Accept any signed or unsigned integer, as long as it fits.
1233                any.downcast::<u8>()
1234                    .map(|b| (*b).try_into().ok())
1235                    .or_else(|any| any.downcast::<u16>().map(|b| (*b).try_into().ok()))
1236                    .or_else(|any| any.downcast::<u32>().map(|b| (*b).try_into().ok()))
1237                    .or_else(|any| any.downcast::<u64>().map(|b| (*b).try_into().ok()))
1238                    .or_else(|any| any.downcast::<u64>().map(|b| (*b).try_into().ok()))
1239                    .or_else(|any| any.downcast::<u128>().map(|b| (*b).try_into().ok()))
1240                    .or_else(|any| any.downcast::<usize>().map(|b| (*b).try_into().ok()))
1241                    .or_else(|any| any.downcast::<i8>().map(|b| (*b).try_into().ok()))
1242                    .or_else(|any| any.downcast::<i16>().map(|b| (*b).try_into().ok()))
1243                    .or_else(|any| any.downcast::<i32>().map(|b| (*b).try_into().ok()))
1244                    .or_else(|any| any.downcast::<i64>().map(|b| (*b).try_into().ok()))
1245                    .or_else(|any| any.downcast::<i128>().map(|b| (*b).try_into().ok()))
1246                    .or_else(|any| any.downcast::<isize>().map(|b| (*b).try_into().ok()))
1247                    .ok()?
1248            }
1249
1250            fn from_config(config: &Config, _context: &Context) -> Result<Self, Error> {
1251                config
1252                    .as_i64()
1253                    .and_then(|config| Self::try_from(config).ok())
1254                    .ok_or_else(|| {
1255                        Error::invalid_config(
1256                            format!("Expected {} <= unsigned <= {}", Self::MIN, Self::MAX,),
1257                            config,
1258                        )
1259                    })
1260            }
1261        }
1262    };
1263}
1264
1265resolve_float!(f32);
1266resolve_float!(f64);
1267
1268resolve_unsigned!(u8);
1269resolve_unsigned!(u16);
1270resolve_unsigned!(u32);
1271resolve_unsigned!(u64);
1272resolve_unsigned!(u128);
1273resolve_unsigned!(usize);
1274
1275resolve_signed!(i8);
1276resolve_signed!(i16);
1277resolve_signed!(i32);
1278resolve_signed!(i64);
1279resolve_signed!(i128);
1280resolve_signed!(isize);
1281
1282impl<T: Resolvable + 'static> Resolvable for crate::XY<T> {
1283    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
1284        Ok(match config {
1285            Config::Array(config) if config.len() == 2 => {
1286                let x = context.resolve(&config[0])?;
1287                let y = context.resolve(&config[1])?;
1288                crate::XY::new(x, y)
1289            }
1290            Config::Object(config) => {
1291                let x = context.resolve(&config["x"])?;
1292                let y = context.resolve(&config["y"])?;
1293                crate::XY::new(x, y)
1294            }
1295            // That one would require specialization?
1296            // Config::String(config) if config == "zero" => crate::Vec2::zero(),
1297            Config::String(config) if config == "zero" => {
1298                let zero = Config::from(0);
1299                let x = context.resolve(&zero)?;
1300                let y = context.resolve(&zero)?;
1301                crate::XY::new(x, y)
1302            }
1303            config => {
1304                return Err(Error::invalid_config(
1305                    "Expected Array of length 2, object, or 'zero'.",
1306                    config,
1307                ))
1308            }
1309        })
1310    }
1311}
1312
1313impl Resolvable for crate::Rect {
1314    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
1315    where
1316        Self: Sized,
1317    {
1318        // Option A: (top_left, bottom_right)
1319        fn option_a(config: &Config, context: &Context) -> Result<crate::Rect, Error> {
1320            let top_left: crate::Vec2 = context.resolve(&config["top_left"])?;
1321            let bottom_right: crate::Vec2 = context.resolve(&config["bottom_right"])?;
1322            Ok(crate::Rect::from_corners(top_left, bottom_right))
1323        }
1324
1325        // Option B: (top_left, size)
1326        fn option_b(config: &Config, context: &Context) -> Result<crate::Rect, Error> {
1327            let top_left: crate::Vec2 = context.resolve(&config["top_left"])?;
1328            let size: crate::Vec2 = context.resolve(&config["size"])?;
1329            Ok(crate::Rect::from_size(top_left, size))
1330        }
1331
1332        // Option C: (corners)
1333        fn option_c(config: &Config, context: &Context) -> Result<crate::Rect, Error> {
1334            let corners: [crate::Vec2; 2] = context.resolve(&config["corners"])?;
1335            Ok(crate::Rect::from_corners(corners[0], corners[1]))
1336        }
1337
1338        // Option D: (point)
1339        fn option_d(config: &Config, context: &Context) -> Result<crate::Rect, Error> {
1340            let point: crate::Vec2 = context.resolve(&config["point"])?;
1341            Ok(crate::Rect::from_point(point))
1342        }
1343
1344        // TODO: automate trying several options, and return AllVariantsFailed
1345        option_a(config, context)
1346            .or_else(|_| option_b(config, context))
1347            .or_else(|_| option_c(config, context))
1348            .or_else(|_| option_d(config, context))
1349    }
1350}
1351
1352impl Resolvable for crate::direction::Orientation {
1353    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
1354        resolve_from_str(config, context, |_| "Expected horizontal or vertical")
1355    }
1356}
1357
1358impl Resolvable for crate::direction::Direction {
1359    fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
1360    where
1361        Self: Sized,
1362    {
1363        resolve_from_str(config, context, |_| "Expected a valid direction")
1364    }
1365}
1366
1367impl Resolvable for crate::direction::Relative {
1368    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
1369        resolve_from_str(config, context, |_| "Expected a relative direction")
1370    }
1371}
1372
1373impl Resolvable for crate::direction::Absolute {
1374    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
1375        resolve_from_str(config, context, |_| "Expected an absolute direction")
1376    }
1377}
1378
1379impl Resolvable for crate::view::Margins {
1380    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
1381        Ok(match config {
1382            Config::Object(config) => Self::lrtb(
1383                context.resolve(&config["left"])?,
1384                context.resolve(&config["right"])?,
1385                context.resolve(&config["top"])?,
1386                context.resolve(&config["bottom"])?,
1387            ),
1388            Config::Number(_) => {
1389                let n = context.resolve(config)?;
1390                Self::lrtb(n, n, n, n)
1391            }
1392            _ => return Err(Error::invalid_config("Expected object or number", config)),
1393        })
1394    }
1395}
1396
1397impl Resolvable for crate::align::Align {
1398    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
1399        // Try a string shortcut
1400        let option_a =
1401            |config, context| resolve_from_str(config, context, |_| "Unexpected align string");
1402
1403        let option_b = |config: &Config, context: &Context| {
1404            let h = context.resolve(&config["h"])?;
1405            let v = context.resolve(&config["v"])?;
1406
1407            Ok(Self { h, v })
1408        };
1409
1410        option_a(config, context).or_else(|_| option_b(config, context))
1411    }
1412}
1413
1414impl Resolvable for crate::align::VAlign {
1415    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
1416        resolve_from_str(config, context, |_| "Expected top, center or bottom")
1417    }
1418}
1419
1420impl Resolvable for crate::align::HAlign {
1421    fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
1422        resolve_from_str(config, context, |_| "Expected left, center or right")
1423    }
1424}
1425
1426// TODO: This could be solved with NoConfig instead.
1427// Implement Resolvable for all functions taking 4 or less arguments.
1428// (They will all fail to deserialize, but at least we can call resolve() on them)
1429// We could consider increasing that? It would probably increase compilation time, and clutter the
1430// Resolvable doc page. Maybe behind a feature if people really need it?
1431// (Ideally we wouldn't need it and we'd have a blanket implementation instead, but that may
1432// require specialization.)
1433impl_fn_from_config!(Resolvable (D C B A));
1434
1435#[cfg(test)]
1436mod tests {
1437    use crate::{
1438        builder::{Config, Context},
1439        utils::markup::StyledString,
1440    };
1441
1442    use serde_json::json;
1443
1444    use super::Resolvable;
1445
1446    fn check_resolves_from_conf<R>(config: Config, result: R)
1447    where
1448        R: Resolvable + PartialEq + std::fmt::Debug + 'static,
1449    {
1450        let context = Context::new();
1451        assert_eq!(result, context.resolve::<R>(&config).unwrap());
1452    }
1453
1454    fn check_resolves_from_any<T, R>(value: T, result: R)
1455    where
1456        T: Clone + Send + Sync + 'static,
1457        R: Resolvable + PartialEq + std::fmt::Debug + 'static,
1458    {
1459        let mut context = Context::new();
1460        context.store("foo", value);
1461        let config = Config::String("$foo".into());
1462        assert_eq!(result, context.resolve::<R>(&config).unwrap());
1463    }
1464
1465    #[test]
1466    fn test_strings() {
1467        check_resolves_from_conf(json!("1"), String::from("1"));
1468        check_resolves_from_conf(json!(1), String::from("1"));
1469        check_resolves_from_conf(json!(true), String::from("true"));
1470    }
1471
1472    #[test]
1473    fn test_integers() {
1474        fn check_integer_types<T>(result: T)
1475        where
1476            T: Clone + Send + Sync + 'static + std::fmt::Debug + PartialEq + Resolvable,
1477        {
1478            check_resolves_from_any(1usize, result.clone());
1479            check_resolves_from_any(1u8, result.clone());
1480            check_resolves_from_any(1u16, result.clone());
1481            check_resolves_from_any(1u32, result.clone());
1482            check_resolves_from_any(1u64, result.clone());
1483            check_resolves_from_any(1u128, result.clone());
1484            check_resolves_from_any(1isize, result.clone());
1485            check_resolves_from_any(1i8, result.clone());
1486            check_resolves_from_any(1i16, result.clone());
1487            check_resolves_from_any(1i32, result.clone());
1488            check_resolves_from_any(1i64, result.clone());
1489            check_resolves_from_any(1i128, result.clone());
1490
1491            check_resolves_from_conf(json!(1), result.clone());
1492        }
1493
1494        check_integer_types(1usize);
1495        check_integer_types(1u8);
1496        check_integer_types(1u16);
1497        check_integer_types(1u32);
1498        check_integer_types(1u64);
1499        check_integer_types(1u128);
1500        check_integer_types(1isize);
1501        check_integer_types(1i8);
1502        check_integer_types(1i16);
1503        check_integer_types(1i32);
1504        check_integer_types(1i64);
1505        check_integer_types(1i128);
1506
1507        check_resolves_from_conf(json!(-1), -1i32);
1508        check_resolves_from_conf(json!(-1), -1isize);
1509
1510        check_resolves_from_conf(json!(1), 1u32);
1511        check_resolves_from_conf(json!(1), 1u64);
1512        check_resolves_from_conf(json!(1), 1usize);
1513
1514        check_resolves_from_conf(json!(0), 0u32);
1515        check_resolves_from_conf(json!(0), 0u64);
1516    }
1517
1518    #[test]
1519    fn test_floats() {
1520        check_resolves_from_any(1.0f32, 1.0f32);
1521        check_resolves_from_any(1.0f32, 1.0f64);
1522        check_resolves_from_any(1.0f64, 1.0f32);
1523        check_resolves_from_any(1.0f64, 1.0f64);
1524        check_resolves_from_conf(json!(1), 1.0f32);
1525        check_resolves_from_conf(json!(1), 1.0f64);
1526        check_resolves_from_conf(json!(1.0), 1.0f32);
1527        check_resolves_from_conf(json!(1.0), 1.0f64);
1528    }
1529
1530    #[test]
1531    fn test_vec() {
1532        // Vec to Vec
1533        check_resolves_from_any(vec![1u32, 2, 3], vec![1u32, 2, 3]);
1534        // Vec to Array
1535        check_resolves_from_any(vec![1u32, 2, 3], [1u32, 2, 3]);
1536        // Array to Array
1537        check_resolves_from_any([1u32, 2, 3], [1u32, 2, 3]);
1538
1539        // Both array and Vec can resolve from a config array.
1540        check_resolves_from_conf(json!([1, 2, 3]), [1u32, 2, 3]);
1541        check_resolves_from_conf(json!([1, 2, 3]), vec![1u32, 2, 3]);
1542    }
1543
1544    #[test]
1545    fn test_option() {
1546        check_resolves_from_any(Some(42u32), Some(42u32));
1547        check_resolves_from_any(42u32, Some(42u32));
1548        check_resolves_from_conf(json!(42), Some(42u32));
1549        check_resolves_from_conf(json!(null), None::<u32>);
1550    }
1551
1552    #[test]
1553    fn test_box() {
1554        check_resolves_from_any(Box::new(42u32), Box::new(42u32));
1555        check_resolves_from_any(42u32, Box::new(42u32));
1556        check_resolves_from_conf(json!(42), Box::new(42u32));
1557    }
1558
1559    #[test]
1560    fn test_arc() {
1561        use std::sync::Arc;
1562        check_resolves_from_any(Arc::new(42u32), Arc::new(42u32));
1563        check_resolves_from_any(42u32, Arc::new(42u32));
1564        check_resolves_from_conf(json!(42), Arc::new(42u32));
1565    }
1566
1567    #[test]
1568    fn test_rgb() {
1569        use crate::style::Rgb;
1570        // We can resolve both u8 and f32 from either u8 or f32 stored.
1571        check_resolves_from_any(Rgb::new(0u8, 0u8, 255u8), Rgb::new(0u8, 0u8, 255u8));
1572        check_resolves_from_any(Rgb::new(0f32, 0f32, 1f32), Rgb::new(0u8, 0u8, 255u8));
1573        check_resolves_from_any(Rgb::new(0u8, 0u8, 255u8), Rgb::new(0f32, 0f32, 1f32));
1574        check_resolves_from_any(Rgb::new(0f32, 0f32, 1f32), Rgb::new(0f32, 0f32, 1f32));
1575
1576        // We can resolve both u8 and f32 from either integers or floats in json.
1577        check_resolves_from_conf(json!([0, 0, 255]), Rgb::new(0u8, 0u8, 255u8));
1578        check_resolves_from_conf(json!([0, 0, 255]), Rgb::new(0f32, 0f32, 1f32));
1579        check_resolves_from_conf(json!([0, 0, 1.0]), Rgb::new(0f32, 0f32, 1f32));
1580        check_resolves_from_conf(json!([0, 0, 1.0]), Rgb::new(0u8, 0u8, 255u8));
1581
1582        check_resolves_from_conf(json!("blue"), Rgb::blue());
1583        check_resolves_from_conf(json!("#0000FF"), Rgb::blue());
1584        check_resolves_from_conf(json!("0x0000FF"), Rgb::blue());
1585        check_resolves_from_conf(json!({"r": 0, "g": 0, "b": 255}), Rgb::blue());
1586    }
1587
1588    #[test]
1589    fn test_colors() {
1590        use crate::style::{BaseColor, Color, ColorPair, ColorStyle, ColorType};
1591
1592        check_resolves_from_conf(json!("green"), BaseColor::Green);
1593
1594        check_resolves_from_conf(json!("dark red"), Color::Dark(BaseColor::Red));
1595
1596        check_resolves_from_conf(json!("terminal_default"), ColorPair::terminal_default());
1597
1598        check_resolves_from_conf(
1599            json!({"front": "red", "back": "light blue"}),
1600            ColorPair {
1601                front: Color::Dark(BaseColor::Red),
1602                back: Color::Light(BaseColor::Blue),
1603            },
1604        );
1605
1606        check_resolves_from_conf(json!("inherit_parent"), ColorType::InheritParent);
1607
1608        check_resolves_from_conf(
1609            json!({
1610                "front": "inherit_parent",
1611                "back": "white",
1612            }),
1613            ColorStyle {
1614                front: ColorType::InheritParent,
1615                back: ColorType::Color(Color::Dark(BaseColor::White)),
1616            },
1617        );
1618    }
1619
1620    #[test]
1621    fn test_align() {
1622        use crate::align::{Align, HAlign, VAlign};
1623
1624        check_resolves_from_conf(json!("left"), HAlign::Left);
1625        check_resolves_from_conf(json!("center"), HAlign::Center);
1626        check_resolves_from_conf(json!("right"), HAlign::Right);
1627
1628        check_resolves_from_conf(json!("top"), VAlign::Top);
1629        check_resolves_from_conf(json!("center"), VAlign::Center);
1630        check_resolves_from_conf(json!("bottom"), VAlign::Bottom);
1631
1632        check_resolves_from_conf(json!("top_center"), Align::top_center());
1633        check_resolves_from_conf(json!({"v": "top", "h": "center"}), Align::top_center());
1634    }
1635
1636    #[test]
1637    fn test_directions() {
1638        use crate::direction::{Absolute, Direction, Orientation, Relative};
1639
1640        check_resolves_from_conf(json!("front"), Relative::Front);
1641        check_resolves_from_conf(json!("back"), Relative::Back);
1642
1643        check_resolves_from_conf(json!("up"), Absolute::Up);
1644        check_resolves_from_conf(json!("down"), Absolute::Down);
1645        check_resolves_from_conf(json!("left"), Absolute::Left);
1646        check_resolves_from_conf(json!("right"), Absolute::Right);
1647
1648        check_resolves_from_conf(json!("front"), Direction::Rel(Relative::Front));
1649        check_resolves_from_conf(json!("down"), Direction::Abs(Absolute::Down));
1650        check_resolves_from_conf(json!("none"), Direction::Abs(Absolute::None));
1651
1652        check_resolves_from_conf(json!("horizontal"), Orientation::Horizontal);
1653        check_resolves_from_conf(json!("vertical"), Orientation::Vertical);
1654    }
1655
1656    #[test]
1657    fn test_rect() {
1658        use crate::Rect;
1659
1660        check_resolves_from_conf(
1661            json!({"top_left": [0,0], "bottom_right": [4,2]}),
1662            Rect::from_corners((0, 0), (4, 2)),
1663        );
1664
1665        check_resolves_from_conf(
1666            json!({"top_left": [1,1], "size": [4,2]}),
1667            Rect::from_size((1, 1), (4, 2)),
1668        );
1669
1670        check_resolves_from_conf(
1671            json!({"corners": [[1,5], [4,2]]}),
1672            Rect::from_corners((1, 5), (4, 2)),
1673        );
1674
1675        check_resolves_from_conf(json!({"point": [4,2]}), Rect::from_point((4, 2)));
1676    }
1677
1678    #[test]
1679    fn test_xy() {
1680        use crate::{Vec2, XY};
1681
1682        // Resolve zeroes
1683        check_resolves_from_conf(json!("zero"), Vec2::zero());
1684        check_resolves_from_conf(json!("zero"), XY::<isize>::zero());
1685        check_resolves_from_conf(json!("zero"), XY::new(0f32, 0f32));
1686
1687        // Resolve array and object versions
1688        check_resolves_from_conf(json!([4, 2]), Vec2::new(4, 2));
1689        check_resolves_from_conf(json!({"x": 4, "y": 2}), Vec2::new(4, 2));
1690
1691        // Resolve strings
1692        check_resolves_from_conf(
1693            json!(["foo", "bar"]),
1694            XY::new(String::from("foo"), String::from("bar")),
1695        );
1696        check_resolves_from_conf(
1697            json!({"x": "foo", "y": "bar"}),
1698            XY::new(String::from("foo"), String::from("bar")),
1699        );
1700    }
1701
1702    #[test]
1703    fn test_borderstyle() {
1704        use crate::style::BorderStyle;
1705        check_resolves_from_conf(json!("none"), BorderStyle::None);
1706        check_resolves_from_conf(json!("simple"), BorderStyle::Simple);
1707        check_resolves_from_conf(json!("outset"), BorderStyle::Outset);
1708    }
1709
1710    #[test]
1711    fn test_styled_string() {
1712        check_resolves_from_any(String::from("foo"), StyledString::plain("foo"));
1713        check_resolves_from_any(StyledString::plain("foo"), StyledString::plain("foo"));
1714        check_resolves_from_conf(json!("foo"), StyledString::plain("foo"));
1715    }
1716
1717    #[test]
1718    fn test_no_config() {
1719        use super::NoConfig;
1720
1721        // Test how NoConfig lets you store and resolve types that are not `Resolvable`.
1722
1723        #[derive(Clone, PartialEq, Eq, Debug)]
1724        struct Foo(i32);
1725
1726        check_resolves_from_any(Foo(42), NoConfig(Foo(42)));
1727    }
1728}