flipflop/
error.rs

1//! Error types.
2use core::{
3    convert::Infallible,
4    fmt,
5    hash::Hash,
6    mem,
7    ops::{RangeFrom, RangeTo},
8};
9
10use crate::{
11    accum::{Accum, AccumObj, Finished},
12    side::Side,
13    strategy::StrategyObj,
14    Strategy,
15};
16
17/// Error returned when pushing an element to a full accumulator.
18#[non_exhaustive]
19pub struct CapacityExhausted<'a, A: ?Sized + AccumObj> {
20    /// The item that couldn't fit.
21    pub item: A::Item,
22
23    /// Side that item was pushed to.
24    pub side: Side,
25
26    /// Accumulator to which item was pushed.
27    pub accum: &'a A,
28}
29impl<'a, A: Accum> CapacityExhausted<'a, A> {
30    /// Erase the accumulator type, so the error can be more general.
31    pub fn erased_accum(self) -> CapacityExhausted<'a, dyn 'a + AccumObj<Item = A::Item>> {
32        CapacityExhausted {
33            item: self.item,
34            side: self.side,
35            accum: self.accum,
36        }
37    }
38}
39impl<'a, A: ?Sized + AccumObj<Item: Copy>> Copy for CapacityExhausted<'a, A> {}
40impl<'a, A: ?Sized + AccumObj<Item: Clone>> Clone for CapacityExhausted<'a, A> {
41    #[inline]
42    fn clone(&self) -> Self {
43        CapacityExhausted {
44            item: self.item.clone(),
45            side: self.side,
46            accum: self.accum,
47        }
48    }
49}
50impl<'a, A: PartialEq + AccumObj<Item: PartialEq>> PartialEq for CapacityExhausted<'a, A> {
51    #[inline]
52    fn eq(&self, rhs: &CapacityExhausted<'a, A>) -> bool {
53        self.item == rhs.item && self.side == rhs.side && self.accum == rhs.accum
54    }
55}
56impl<'a, A: Eq + AccumObj<Item: Eq>> Eq for CapacityExhausted<'a, A> {}
57impl<'a, A: Hash + AccumObj<Item: Hash>> Hash for CapacityExhausted<'a, A> {
58    #[inline]
59    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
60        (&self.item, self.side, self.accum).hash(state);
61    }
62}
63impl<'a, A: fmt::Debug + AccumObj<Item: fmt::Debug>> fmt::Debug for CapacityExhausted<'a, A> {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        f.debug_struct("CapacityExhausted")
66            .field("item", &self.item)
67            .field("side", &self.side)
68            .field("accum", &self.accum)
69            .finish_non_exhaustive()
70    }
71}
72impl<'a, A: ?Sized + Accum> From<Infallible> for CapacityExhausted<'a, A> {
73    #[inline]
74    fn from(err: Infallible) -> CapacityExhausted<'a, A> {
75        match err {}
76    }
77}
78impl<'a, A: ?Sized + AccumObj<Item: fmt::Debug>> fmt::Display for CapacityExhausted<'a, A> {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        write!(f, "capacity exhausted: ")?;
81        let mut list = f.debug_list();
82        list.entries((0..self.accum.len_forward()).filter_map(|idx| self.accum.get_forward(idx)));
83        if self.side == Side::Backward {
84            list.entry(&format_args!(".."));
85        }
86        list.entry(&format_args!("(no room for {:?})", self.item));
87        if self.side == Side::Forward {
88            list.entry(&format_args!(".."));
89        }
90        list.entries(
91            (0..self.accum.len_backward())
92                .rev()
93                .filter_map(|idx| self.accum.get_backward(idx)),
94        );
95        list.finish()
96    }
97}
98
99/// Error indicating that a strategy failed at a particular position.
100pub struct FailedStrategy<'a, T, S: ?Sized, V> {
101    /// Side where the error occurred.
102    pub(crate) side: Side,
103
104    /// One past forward index reached.
105    pub(crate) forward: usize,
106
107    /// Backward index reached.
108    pub(crate) backward: usize,
109
110    /// Finished accumulator.
111    pub(crate) accum: Finished<'a, T>,
112
113    /// Strategy.
114    pub strategy: &'a S,
115
116    /// Verification error.
117    pub cause: V,
118}
119impl<'a, T, S: Strategy, V> FailedStrategy<'a, T, S, V> {
120    /// Erase the strategy type, so the error can be more general.
121    #[inline]
122    pub fn erased_strategy(self) -> FailedStrategy<'a, T, dyn 'a + StrategyObj, V> {
123        FailedStrategy {
124            side: self.side,
125            forward: self.forward,
126            backward: self.backward,
127            accum: self.accum,
128            strategy: self.strategy,
129            cause: self.cause,
130        }
131    }
132}
133impl<'a, T, S: ?Sized, V> FailedStrategy<'a, T, S, V> {
134    /// Creates an error for an empty result.
135    #[inline]
136    pub fn empty(strategy: &'a S, cause: V) -> FailedStrategy<'a, T, S, V> {
137        FailedStrategy {
138            side: Side::Forward,
139            forward: 0,
140            backward: 0,
141            accum: Finished::empty(),
142            strategy,
143            cause,
144        }
145    }
146
147    /// Applies a function to the cause.
148    #[inline]
149    pub fn map<U, F: FnOnce(V) -> U>(self, f: F) -> FailedStrategy<'a, T, S, U> {
150        FailedStrategy {
151            side: self.side,
152            forward: self.forward,
153            backward: self.backward,
154            accum: self.accum,
155            strategy: self.strategy,
156            cause: f(self.cause),
157        }
158    }
159
160    /// Get the unsigned index of the error.
161    #[inline]
162    pub fn index(&self) -> Option<usize> {
163        match self.side {
164            Side::Forward => self.forward.checked_sub(1),
165            Side::Backward => Some(self.backward),
166        }
167    }
168
169    /// Get the valid values in the forward direction.
170    #[inline]
171    pub fn valid_forward(&self) -> RangeTo<usize> {
172        ..match self.side {
173            Side::Forward => self.forward.saturating_sub(1),
174            Side::Backward => self.forward,
175        }
176    }
177
178    /// Get the valid values in the backward direction.
179    #[inline]
180    pub fn valid_backward(&self) -> RangeFrom<usize> {
181        (match self.side {
182            Side::Forward => self.backward,
183            Side::Backward => self.backward + 1,
184        })..
185    }
186}
187impl<'a, T, S: ?Sized, V: Copy> Copy for FailedStrategy<'a, T, S, V> {}
188impl<'a, T, S: ?Sized, V: Clone> Clone for FailedStrategy<'a, T, S, V> {
189    #[inline]
190    fn clone(&self) -> Self {
191        FailedStrategy {
192            side: self.side,
193            forward: self.forward,
194            backward: self.backward,
195            accum: self.accum,
196            strategy: self.strategy,
197            cause: self.cause.clone(),
198        }
199    }
200}
201impl<'a, T: PartialEq, S: ?Sized + PartialEq, V: PartialEq> PartialEq
202    for FailedStrategy<'a, T, S, V>
203{
204    #[inline]
205    fn eq(&self, rhs: &FailedStrategy<'a, T, S, V>) -> bool {
206        self.side == rhs.side
207            && self.forward == rhs.forward
208            && self.backward == rhs.backward
209            && self.accum == rhs.accum
210            && self.strategy == rhs.strategy
211            && self.cause == rhs.cause
212    }
213}
214impl<'a, T: Eq, S: ?Sized + Eq, V: Eq> Eq for FailedStrategy<'a, T, S, V> {}
215impl<'a, T: Hash, S: ?Sized + Hash, V: Hash> Hash for FailedStrategy<'a, T, S, V> {
216    #[inline]
217    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
218        (
219            self.side,
220            self.forward,
221            self.backward,
222            self.accum,
223            self.strategy,
224            &self.cause,
225        )
226            .hash(state);
227    }
228}
229impl<'a, T: fmt::Debug, S: ?Sized + fmt::Debug, V: fmt::Debug> fmt::Debug
230    for FailedStrategy<'a, T, S, V>
231{
232    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233        f.debug_struct("FailedStrategy")
234            .field("valid_forward", &self.valid_forward())
235            .field("index", &self.index())
236            .field("valid_backward", &self.valid_backward())
237            .field("accum", &self.accum)
238            .field("strategy", &self.strategy)
239            .field("cause", &self.cause)
240            .finish_non_exhaustive()
241    }
242}
243impl<'a, T, S: ?Sized, V> From<Infallible> for FailedStrategy<'a, T, S, V> {
244    #[inline]
245    fn from(err: Infallible) -> FailedStrategy<'a, T, S, V> {
246        match err {}
247    }
248}
249impl<'a, T: fmt::Debug, S: ?Sized + fmt::Display, V: fmt::Display> fmt::Display
250    for FailedStrategy<'a, T, S, V>
251{
252    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253        write!(f, r#"failed strategy "{}": "#, self.strategy)?;
254        let mut list = f.debug_list();
255        let fwd = self.valid_forward();
256        let bwd = self.valid_backward();
257        list.entries(&self.accum.as_slice()[fwd]);
258        if self.side == Side::Backward {
259            list.entry(&..);
260        }
261        if let Some(idx) = self.index() {
262            list.entry(&format_args!(
263                "(unexpected {:?})",
264                self.accum.get_unsigned(idx).unwrap()
265            ));
266        }
267        if self.side == Side::Forward {
268            list.entry(&..);
269        }
270        list.entries(&self.accum.as_slice()[bwd]);
271        list.finish()?;
272        write!(f, " due to verification error: {}", self.cause)
273    }
274}
275
276/// Error when accumulating and verifying a strategy.
277pub enum AccumVerifyError<'a, A: ?Sized + AccumObj, S: ?Sized, V> {
278    /// Error accumualting a value.
279    Accum(CapacityExhausted<'a, A>),
280
281    /// Error verifying a value.
282    Verify(FailedStrategy<'a, A::Item, S, V>),
283}
284impl<'a, A: ?Sized + Accum, S: Strategy, V> AccumVerifyError<'a, A, S, V> {
285    /// Erase the strategy type, so the error can be more general.
286    pub fn erased_strategy(self) -> AccumVerifyError<'a, A, dyn 'a + StrategyObj, V> {
287        match self {
288            AccumVerifyError::Accum(err) => AccumVerifyError::Accum(err),
289            AccumVerifyError::Verify(err) => AccumVerifyError::Verify(err.erased_strategy()),
290        }
291    }
292}
293impl<'a, A: Accum, S: ?Sized + Strategy, V> AccumVerifyError<'a, A, S, V> {
294    /// Erase the accumulator type, so the error can be more general.
295    pub fn erased_accum(self) -> AccumVerifyError<'a, dyn 'a + AccumObj<Item = A::Item>, S, V> {
296        match self {
297            AccumVerifyError::Accum(err) => AccumVerifyError::Accum(err.erased_accum()),
298            AccumVerifyError::Verify(err) => AccumVerifyError::Verify(err),
299        }
300    }
301}
302impl<'a, A: ?Sized + Accum, S: ?Sized + Strategy, V> AccumVerifyError<'a, A, S, V> {
303    /// Applies a function to the cause.
304    #[inline]
305    pub fn map<U, F: FnOnce(V) -> U>(self, f: F) -> AccumVerifyError<'a, A, S, U> {
306        match self {
307            AccumVerifyError::Accum(err) => AccumVerifyError::Accum(err),
308            AccumVerifyError::Verify(err) => AccumVerifyError::Verify(err.map(f)),
309        }
310    }
311}
312impl<'a, A: Copy + Accum<Item: Copy>, S: ?Sized, V: Copy> Copy for AccumVerifyError<'a, A, S, V> {}
313impl<'a, A: ?Sized + Accum<Item: Clone>, S: ?Sized, V: Clone> Clone
314    for AccumVerifyError<'a, A, S, V>
315{
316    fn clone(&self) -> Self {
317        match self {
318            AccumVerifyError::Accum(err) => AccumVerifyError::Accum(err.clone()),
319            AccumVerifyError::Verify(err) => AccumVerifyError::Verify(err.clone()),
320        }
321    }
322}
323impl<'a, A: PartialEq + Accum<Item: PartialEq>, S: ?Sized + PartialEq, V: PartialEq> PartialEq
324    for AccumVerifyError<'a, A, S, V>
325{
326    #[inline]
327    fn eq(&self, rhs: &AccumVerifyError<'a, A, S, V>) -> bool {
328        match (self, rhs) {
329            (AccumVerifyError::Accum(lhs), AccumVerifyError::Accum(rhs)) => lhs == rhs,
330            (AccumVerifyError::Verify(lhs), AccumVerifyError::Verify(rhs)) => lhs == rhs,
331            _ => false,
332        }
333    }
334}
335impl<'a, A: Eq + Accum<Item: Eq>, S: ?Sized + Eq, V: Eq> Eq for AccumVerifyError<'a, A, S, V> {}
336impl<'a, A: Hash + Accum<Item: Hash>, S: ?Sized + Hash, V: Hash> Hash
337    for AccumVerifyError<'a, A, S, V>
338{
339    #[inline]
340    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
341        mem::discriminant(self).hash(state);
342        match self {
343            AccumVerifyError::Accum(err) => err.hash(state),
344            AccumVerifyError::Verify(err) => err.hash(state),
345        }
346    }
347}
348impl<'a, A: fmt::Debug + Accum<Item: fmt::Debug>, S: ?Sized + fmt::Debug, V: fmt::Debug> fmt::Debug
349    for AccumVerifyError<'a, A, S, V>
350{
351    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
352        match self {
353            AccumVerifyError::Accum(err) => fmt::Debug::fmt(err, f),
354            AccumVerifyError::Verify(err) => fmt::Debug::fmt(err, f),
355        }
356    }
357}
358impl<'a, A: Accum<Item: fmt::Debug>, S: ?Sized + fmt::Display, V: fmt::Display> fmt::Display
359    for AccumVerifyError<'a, A, S, V>
360{
361    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
362        match self {
363            AccumVerifyError::Accum(err) => fmt::Display::fmt(err, f),
364            AccumVerifyError::Verify(err) => fmt::Display::fmt(err, f),
365        }
366    }
367}
368impl<'a, A: ?Sized + Accum, S: ?Sized, V> From<Infallible> for AccumVerifyError<'a, A, S, V> {
369    #[inline]
370    fn from(err: Infallible) -> AccumVerifyError<'a, A, S, V> {
371        match err {}
372    }
373}
374impl<'a, A: ?Sized + Accum, S: ?Sized, V> From<CapacityExhausted<'a, A>>
375    for AccumVerifyError<'a, A, S, V>
376{
377    #[inline]
378    fn from(err: CapacityExhausted<'a, A>) -> AccumVerifyError<'a, A, S, V> {
379        AccumVerifyError::Accum(err)
380    }
381}
382impl<'a, A: ?Sized + Accum, S: ?Sized, V> From<FailedStrategy<'a, A::Item, S, V>>
383    for AccumVerifyError<'a, A, S, V>
384{
385    #[inline]
386    fn from(err: FailedStrategy<'a, A::Item, S, V>) -> AccumVerifyError<'a, A, S, V> {
387        AccumVerifyError::Verify(err)
388    }
389}
390
391/// Verification error for an inconsistent iterator.
392#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
393pub enum Inconsistent<'a, T> {
394    /// Nothing was accumulated.
395    Empty,
396
397    /// Values didn't match.
398    Unequal(Unequal<'a, T>),
399}
400impl<'a, T: fmt::Debug> fmt::Display for Inconsistent<'a, T> {
401    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
402        match self {
403            Inconsistent::Empty => write!(f, "no data was returned by iterator"),
404            Inconsistent::Unequal(err) => fmt::Display::fmt(err, f),
405        }
406    }
407}
408
409/// Verification error for two items not being equal.
410#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
411#[non_exhaustive]
412pub struct Unequal<'a, T> {
413    /// Expected value.
414    ///
415    /// `None` indicates out-of-bounds.
416    pub expected: Option<&'a T>,
417
418    /// Actual value.
419    pub actual: &'a T,
420}
421impl<'a, T: fmt::Debug> fmt::Display for Unequal<'a, T> {
422    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
423        let actual = self.actual;
424        match self.expected {
425            Some(expected) => write!(f, "{actual:?} != {expected:?}"),
426            None => write!(f, "extraneous value {actual:?}"),
427        }
428    }
429}
430
431/// Verification error for two items not satisfying a property.
432#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
433pub struct InvalidPair<'a, T> {
434    /// Pair of values.
435    pub pair: (&'a T, &'a T),
436
437    /// Whether the index was negative.
438    pub(crate) index_is_negative: bool,
439}
440impl<'a, T: fmt::Debug> fmt::Display for InvalidPair<'a, T> {
441    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
442        let (lo, hi) = self.pair;
443        if self.index_is_negative {
444            write!(
445                f,
446                "sequence (invalid {lo:?}, {hi:?}) didn't satisfy requirements"
447            )
448        } else {
449            write!(
450                f,
451                "sequence ({lo:?}, invalid {hi:?}) didn't satisfy requirements"
452            )
453        }
454    }
455}
456
457/// Verification error for an item and index not satisfying a property.
458#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
459#[non_exhaustive]
460pub struct InvalidIndex<'a, T> {
461    /// Value.
462    pub item: &'a T,
463
464    /// Index.
465    pub index: usize,
466}
467impl<'a, T: fmt::Debug> fmt::Display for InvalidIndex<'a, T> {
468    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
469        let item = self.item;
470        let index = self.index;
471        write!(
472            f,
473            "item {item:?} at index {index:?} didn't satisfy requirements"
474        )
475    }
476}
477
478#[cfg(test)]
479mod tests {
480    use crate::{
481        accum::{Accum, Bounded, Finished},
482        error::{CapacityExhausted, FailedStrategy},
483        side::Side,
484        strategy::Todo,
485    };
486
487    const SLICE: Finished<'static, u32> = Finished::new(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 5);
488
489    #[test]
490    fn failed_insert_fwd() {
491        let error: CapacityExhausted<Bounded<u32, 10>> = CapacityExhausted {
492            item: 4,
493            side: Side::Forward,
494            accum: &Bounded::with_data(&[1, 2, 3], &[8, 9, 10]),
495        };
496        assert_eq!(
497            error.to_string(),
498            "capacity exhausted: [1, 2, 3, (no room for 4), .., 8, 9, 10]"
499        );
500    }
501
502    #[test]
503    fn failed_insert_bwd() {
504        let error: CapacityExhausted<Bounded<u32, 10>> = CapacityExhausted {
505            item: 7,
506            side: Side::Backward,
507            accum: &Bounded::with_data(&[1, 2, 3], &[8, 9, 10]),
508        };
509        assert_eq!(
510            error.to_string(),
511            "capacity exhausted: [1, 2, 3, .., (no room for 7), 8, 9, 10]"
512        );
513    }
514
515    #[test]
516    fn failed_empty() {
517        let error: FailedStrategy<u32, Todo, &str> = FailedStrategy::empty(&Todo, "bad");
518        assert_eq!(
519            error.to_string(),
520            r#"failed strategy "unimplemented": [..] due to verification error: bad"#
521        );
522    }
523
524    #[test]
525    fn failed_empty_fwd_empty() {
526        let error: FailedStrategy<u32, Todo, &str> = FailedStrategy {
527            side: Side::Forward,
528            forward: 1,
529            backward: 10,
530            cause: "bad",
531            accum: SLICE,
532            strategy: &Todo,
533        };
534        assert_eq!(
535            error.to_string(),
536            r#"failed strategy "unimplemented": [(unexpected 1), ..] due to verification error: bad"#
537        );
538    }
539
540    #[test]
541    fn failed_empty_bwd_empty() {
542        let error: FailedStrategy<u32, Todo, &str> = FailedStrategy {
543            side: Side::Backward,
544            forward: 0,
545            backward: 9,
546            cause: "bad",
547            accum: SLICE,
548            strategy: &Todo,
549        };
550        assert_eq!(
551            error.to_string(),
552            r#"failed strategy "unimplemented": [.., (unexpected 10)] due to verification error: bad"#
553        );
554    }
555
556    #[test]
557    fn failed_nonempty_fwd_empty() {
558        let error: FailedStrategy<u32, Todo, &str> = FailedStrategy {
559            side: Side::Forward,
560            forward: 3,
561            backward: 10,
562            cause: "bad",
563            accum: SLICE,
564            strategy: &Todo,
565        };
566        assert_eq!(
567            error.to_string(),
568            r#"failed strategy "unimplemented": [1, 2, (unexpected 3), ..] due to verification error: bad"#
569        );
570    }
571
572    #[test]
573    fn failed_nonempty_bwd_empty() {
574        let error: FailedStrategy<u32, Todo, &str> = FailedStrategy {
575            side: Side::Backward,
576            forward: 3,
577            backward: 9,
578            cause: "bad",
579            accum: SLICE,
580            strategy: &Todo,
581        };
582        assert_eq!(
583            error.to_string(),
584            r#"failed strategy "unimplemented": [1, 2, 3, .., (unexpected 10)] due to verification error: bad"#
585        );
586    }
587
588    #[test]
589    fn failed_empty_fwd_nonempty() {
590        let error: FailedStrategy<u32, Todo, &str> = FailedStrategy {
591            side: Side::Forward,
592            forward: 1,
593            backward: 7,
594            cause: "bad",
595            accum: SLICE,
596            strategy: &Todo,
597        };
598        assert_eq!(
599            error.to_string(),
600            r#"failed strategy "unimplemented": [(unexpected 1), .., 8, 9, 10] due to verification error: bad"#
601        );
602    }
603
604    #[test]
605    fn failed_empty_bwd_nonempty() {
606        let error: FailedStrategy<u32, Todo, &str> = FailedStrategy {
607            side: Side::Backward,
608            forward: 0,
609            backward: 7,
610            cause: "bad",
611            accum: SLICE,
612            strategy: &Todo,
613        };
614        assert_eq!(
615            error.to_string(),
616            r#"failed strategy "unimplemented": [.., (unexpected 8), 9, 10] due to verification error: bad"#
617        );
618    }
619
620    #[test]
621    fn failed_nonempty_fwd_nonempty() {
622        let error: FailedStrategy<u32, Todo, &str> = FailedStrategy {
623            side: Side::Forward,
624            forward: 3,
625            backward: 7,
626            cause: "bad",
627            accum: SLICE,
628            strategy: &Todo,
629        };
630        assert_eq!(
631            error.to_string(),
632            r#"failed strategy "unimplemented": [1, 2, (unexpected 3), .., 8, 9, 10] due to verification error: bad"#
633        );
634    }
635
636    #[test]
637    fn failed_nonempty_bwd_nonempty() {
638        let error: FailedStrategy<u32, Todo, &str> = FailedStrategy {
639            side: Side::Backward,
640            forward: 3,
641            backward: 7,
642            cause: "bad",
643            accum: SLICE,
644            strategy: &Todo,
645        };
646        assert_eq!(
647            error.to_string(),
648            r#"failed strategy "unimplemented": [1, 2, 3, .., (unexpected 8), 9, 10] due to verification error: bad"#
649        );
650    }
651}