1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
use crate::error::{StError, Warning};
use core::convert::TryFrom;

/// An alternative to the `Ok` variant of `Result` that allows for handling warnings.
#[derive(Debug, Default)]
#[cfg_attr(feature = "ufmt", derive(ufmt::derive::uDebug))]
#[must_use = "this `Outcome` may contain a warning, which should be handled"]
pub struct Outcome<T> {
    value: T,
    pub warning: Option<Warning>,
}

impl<T> Outcome<T> {
    /// Create a new `Outcome` instance.
    pub fn new(value: T, warning: Option<Warning>) -> Self {
        Self { value, warning }
    }

    /// An outcome with no warning.
    pub fn ok(value: T) -> Self {
        Self::new(value, None)
    }

    /// An outcome with a warning.
    pub fn warn(value: T, warning: Warning) -> Self {
        Self::new(value, Some(warning))
    }

    /// Map the outcome to a new type with the given function.
    pub fn map<F, U>(self, f: F) -> Outcome<U>
    where
        F: FnOnce(T) -> U,
    {
        let Outcome { value, warning } = self;
        let value = f(value);
        Outcome { value, warning }
    }

    /// Returns `Ok` if the warning was in the set of warnings to be ignored, otherwise returns the
    /// `Warning` as an `Err`.
    pub fn ignore_warnings(self, to_ignore: &[Warning]) -> Result<T, Warning> {
        match self.warning {
            Some(warning) => match to_ignore.iter().any(|&w| warning == w) {
                true => Ok(self.value),
                false => Err(warning),
            },
            None => Ok(self.value),
        }
    }

    /// Ignore warnings if any were emitted and return the value.
    pub fn ignore_all_warnings(self) -> T {
        self.value
    }

    /// Return an error in the case that a warning was produced.
    pub fn err_on_warning(self) -> Result<T, Warning> {
        match self.warning {
            Some(warning) => Err(warning),
            None => Ok(self.value),
        }
    }
}

impl<T, E> From<Outcome<T>> for Result<Outcome<T>, E> {
    fn from(outcome: Outcome<T>) -> Self {
        Ok(outcome)
    }
}

/// Produce a `Result` from an error value from the original API.
pub fn from_st(status: i8) -> Result<Outcome<()>, StError> {
    // Check for OK.
    if status == 0 {
        return Outcome::ok(()).into();
    }

    // Check for warning.
    let res = Warning::try_from(status).map(|w| Outcome::warn((), w));
    if let Ok(outcome) = res {
        return Ok(outcome);
    }

    // Check for error.
    match StError::try_from(status) {
        Ok(err) => return Err(err),
        // TODO: Not in original, but at least indicates unhandled error?
        _ => Err(StError::NOT_IMPLEMENTED),
    }
}