Skip to main content

egui_charts/studies/
error.rs

1//! Why a study, its inputs or its settings were refused.
2
3use super::Indicator;
4#[cfg(feature = "studies")]
5use super::StudyInput;
6use std::fmt;
7use std::sync::{Mutex, PoisonError};
8
9/// Why a study was not built, edited, given a source or added.
10#[derive(Debug)]
11#[non_exhaustive]
12pub enum StudyError {
13    /// The factory knows no study of this name.
14    UnknownStudy(String),
15    /// No study on the chart has this id.
16    UnknownStudyId(usize),
17    /// The study has no input with this id.
18    UnknownInput(String),
19    /// The study refused the value of an input.
20    InvalidInput {
21        /// The id of the input.
22        id: String,
23        /// Why, completing the sentence "study input `id` ...".
24        reason: String,
25    },
26    /// A declared input has an empty id.
27    EmptyInputId {
28        /// The position of the input in the declared list, counted from 0.
29        index: usize,
30    },
31    /// The study edits its presentation only inside a `ConfiguredStudy`.
32    NotConfigured,
33    /// The precision is above 12 decimals.
34    InvalidPrecision(usize),
35    /// A plot width is not finite or not between 0.5 and 10.
36    InvalidPlotWidth {
37        /// The plot, counted from 0.
38        plot: usize,
39        /// The refused width.
40        width: f32,
41    },
42    /// A horizontal level value is NaN or infinite.
43    InvalidLevelValue {
44        /// The level, counted from 0.
45        level: usize,
46        /// The refused value.
47        value: f64,
48    },
49    /// The settings hold another number of plot styles than the study draws.
50    PlotCount {
51        /// The plots the study draws.
52        expected: usize,
53        /// The plot styles given.
54        found: usize,
55    },
56    /// The source names a study the chart does not have.
57    UnknownSourceStudy(usize),
58    /// The source names a plot its study does not draw.
59    UnknownSourcePlot {
60        /// The id of the source study.
61        id: usize,
62        /// The plot, counted from 0.
63        plot: usize,
64    },
65    /// The study would read its own output, directly or through other studies.
66    CyclicSource,
67    /// The chart holds as many studies as its study cap allows. The refused study comes back
68    /// unchanged.
69    StudyLimitReached(RefusedStudy),
70    /// The text is not study clipboard text.
71    NotClipboard,
72    /// The copied study cannot be read.
73    Clipboard(serde_json::Error),
74}
75
76impl StudyError {
77    /// The error for a value an input's schema refuses.
78    pub(crate) fn invalid_input(id: &str) -> Self {
79        Self::InvalidInput {
80            id: id.to_owned(),
81            reason: "is outside its accepted values".into(),
82        }
83    }
84
85    /// `UnknownInput` when `inputs` has no input `id`, otherwise [`Self::invalid_input`].
86    #[cfg(feature = "studies")]
87    pub(crate) fn rejected(inputs: &[StudyInput], id: &str) -> Self {
88        if inputs.iter().any(|input| input.id == id) {
89            Self::invalid_input(id)
90        } else {
91            Self::UnknownInput(id.to_owned())
92        }
93    }
94}
95
96/// A study the chart refused at its study cap, handed back unchanged by
97/// [`into_inner`](Self::into_inner).
98// The lock only makes the error `Sync`, which `Indicator` is not; nothing else locks it.
99pub struct RefusedStudy(Mutex<Box<dyn Indicator>>);
100
101impl RefusedStudy {
102    #[cfg(feature = "widget")]
103    pub(crate) fn new(study: Box<dyn Indicator>) -> Self {
104        Self(Mutex::new(study))
105    }
106
107    /// The refused study.
108    pub fn into_inner(self) -> Box<dyn Indicator> {
109        self.0.into_inner().unwrap_or_else(PoisonError::into_inner)
110    }
111}
112
113impl fmt::Debug for RefusedStudy {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        let study = self.0.lock().unwrap_or_else(PoisonError::into_inner);
116        f.debug_tuple("RefusedStudy").field(&study.name()).finish()
117    }
118}
119
120impl fmt::Display for StudyError {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        match self {
123            Self::UnknownStudy(name) => write!(f, "unknown study {name:?}"),
124            Self::UnknownStudyId(id) => write!(f, "no study has id {id}"),
125            Self::UnknownInput(id) => write!(f, "unknown study input {id:?}"),
126            Self::InvalidInput { id, reason } => write!(f, "study input {id:?} {reason}"),
127            Self::EmptyInputId { index } => write!(f, "study input {index} has an empty id"),
128            Self::NotConfigured => {
129                f.write_str("wrap the study in ConfiguredStudy to edit its presentation")
130            }
131            Self::InvalidPrecision(p) => {
132                write!(f, "study precision must be at most 12 decimals, got {p}")
133            }
134            Self::InvalidPlotWidth { plot, width } => write!(
135                f,
136                "plot {plot} width must be between 0.5 and 10, got {width:?}"
137            ),
138            Self::InvalidLevelValue { level, value } => {
139                write!(f, "level {level} value must be finite, got {value:?}")
140            }
141            Self::PlotCount { expected, found } => write!(
142                f,
143                "the study draws {expected} plots, got {found} plot styles"
144            ),
145            Self::UnknownSourceStudy(id) => write!(f, "no study has id {id} to read as a source"),
146            Self::UnknownSourcePlot { id, plot } => write!(f, "study {id} has no plot {plot}"),
147            Self::CyclicSource => f.write_str("the study would read its own output"),
148            Self::StudyLimitReached(_) => f.write_str("study limit reached"),
149            Self::NotClipboard => f.write_str("not a study clipboard"),
150            Self::Clipboard(error) => write!(f, "cannot read the copied study: {error}"),
151        }
152    }
153}
154impl std::error::Error for StudyError {
155    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
156        match self {
157            Self::Clipboard(error) => Some(error),
158            _ => None,
159        }
160    }
161}