libspa/utils/
result.rs

1// Copyright The pipewire-rs Contributors.
2// SPDX-License-Identifier: MIT
3
4//! SPA results and errors.
5
6use std::{convert::TryInto, fmt};
7
8use nix::errno::Errno;
9
10/// A result returned by a SPA method, usually to be converted to
11/// a Rust result using [`SpaResult::into_result`] or [`SpaResult::into_async_result`].
12#[derive(Debug, Eq, PartialEq)]
13pub struct SpaResult(i32);
14
15/// An asynchronous sequence number returned by a SPA component.
16///
17/// Use [`AsyncSeq::seq`] to retrieve the actual sequence number.
18#[derive(PartialEq, Eq, Copy, Clone)]
19pub struct AsyncSeq(i32);
20
21/// A successful result from a SPA method.
22#[derive(Debug, Eq, PartialEq)]
23pub enum SpaSuccess {
24    /// Synchronous success
25    Sync(i32),
26    /// Asynchronous success
27    Async(AsyncSeq),
28}
29
30fn async_seq(res: i32) -> i32 {
31    let mask: i32 = spa_sys::SPA_ASYNC_SEQ_MASK.try_into().unwrap();
32    res & mask
33}
34
35fn is_async(val: i32) -> bool {
36    let bit: i32 = spa_sys::SPA_ASYNC_BIT.try_into().unwrap();
37    (val & spa_sys::SPA_ASYNC_MASK) == bit
38}
39
40impl AsyncSeq {
41    /// The sequence number
42    pub fn seq(&self) -> i32 {
43        async_seq(self.0)
44    }
45
46    /// The raw value, this is the sequence number with the `SPA_ASYNC_BIT` bit set
47    pub fn raw(&self) -> i32 {
48        self.0
49    }
50
51    /// Create a new [`AsyncSeq`] from a sequence number
52    pub fn from_seq(seq: i32) -> Self {
53        let bit: i32 = spa_sys::SPA_ASYNC_BIT.try_into().unwrap();
54        let res = bit | async_seq(seq);
55
56        Self(res)
57    }
58
59    /// Create a new [`AsyncSeq`] from a raw value having the `SPA_ASYNC_BIT` bit set
60    pub fn from_raw(val: i32) -> Self {
61        debug_assert!(is_async(val));
62        Self(val)
63    }
64}
65
66impl fmt::Debug for AsyncSeq {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        write!(f, "AsyncSeq seq: {} raw: {}", &self.seq(), &self.raw())
69    }
70}
71
72impl SpaResult {
73    /// Create a new [`SpaResult`] from an `i32` returned by C SPA method.
74    pub fn from_c(res: i32) -> Self {
75        Self(res)
76    }
77
78    /// Pending return for async operation identified with sequence number `seq`.
79    pub fn new_return_async(seq: i32) -> Self {
80        let seq = AsyncSeq::from_seq(seq);
81        Self::from_c(seq.raw())
82    }
83
84    fn is_async(&self) -> bool {
85        is_async(self.0)
86    }
87
88    /// Convert a [`SpaResult`] into a [`Result`]
89    pub fn into_result(self) -> Result<SpaSuccess, Error> {
90        if self.0 < 0 {
91            Err(Error::new(-self.0))
92        } else if self.is_async() {
93            Ok(SpaSuccess::Async(AsyncSeq::from_raw(self.0)))
94        } else {
95            Ok(SpaSuccess::Sync(self.0))
96        }
97    }
98
99    /// Convert a [`SpaResult`] into either an [`AsyncSeq`] or an [`Error`].
100    ///
101    /// # Panics
102    ///
103    /// This method will panic if the result is a synchronous success.
104    pub fn into_async_result(self) -> Result<AsyncSeq, Error> {
105        let res = self.into_result()?;
106
107        match res {
108            SpaSuccess::Async(res) => Ok(res),
109            SpaSuccess::Sync(_) => panic!("result is synchronous success"),
110        }
111    }
112
113    /// Convert a [`SpaResult`] into either a synchronous success or an [`Error`].
114    ///
115    /// # Panics
116    ///
117    /// This method will panic if the result is an asynchronous success.
118    pub fn into_sync_result(self) -> Result<i32, Error> {
119        let res = self.into_result()?;
120
121        match res {
122            SpaSuccess::Sync(res) => Ok(res),
123            SpaSuccess::Async(_) => panic!("result is an asynchronous success"),
124        }
125    }
126}
127
128/// Error returned from a SPA method.
129#[derive(Debug, Eq, PartialEq)]
130pub struct Error(Errno);
131
132impl Error {
133    fn new(e: i32) -> Self {
134        assert!(e > 0);
135
136        Self(Errno::from_raw(e))
137    }
138}
139
140impl std::error::Error for Error {}
141
142impl fmt::Display for Error {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        write!(f, "{}", self.0)
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    #[test]
153    #[cfg_attr(miri, ignore)]
154    /* the errno crate is calling foreign function __xpg_strerror_r which is not supported by miri */
155    fn spa_result() {
156        assert!(!SpaResult::from_c(0).is_async());
157        assert!(SpaResult::new_return_async(0).is_async());
158        assert_eq!(
159            SpaResult::new_return_async(0).into_async_result(),
160            Ok(AsyncSeq::from_seq(0))
161        );
162
163        assert_eq!(SpaResult::from_c(0).into_result(), Ok(SpaSuccess::Sync(0)));
164        assert_eq!(SpaResult::from_c(1).into_result(), Ok(SpaSuccess::Sync(1)));
165        assert_eq!(SpaResult::from_c(0).into_sync_result(), Ok(0));
166
167        assert_eq!(
168            SpaResult::new_return_async(1).into_result(),
169            Ok(SpaSuccess::Async(AsyncSeq::from_seq(1)))
170        );
171
172        let err = SpaResult::from_c(-libc::EBUSY).into_result().unwrap_err();
173        assert_eq!(format!("{}", err), "EBUSY: Device or resource busy",);
174
175        let res = SpaResult::from_c(-1).into_sync_result();
176        assert!(res.is_err());
177    }
178
179    #[test]
180    fn async_seq() {
181        assert_eq!(AsyncSeq::from_seq(0).seq(), 0);
182        assert_eq!(AsyncSeq::from_seq(1).seq(), 1);
183    }
184
185    #[should_panic]
186    #[test]
187    fn async_seq_panic() {
188        // raw value does not have the SPA_ASYNC_BIT set
189        AsyncSeq::from_raw(1);
190    }
191
192    #[should_panic]
193    #[test]
194    fn spa_async_result_panic() {
195        let _ = SpaResult::from_c(0).into_async_result();
196    }
197
198    #[should_panic]
199    #[test]
200    fn spa_sync_result_panic() {
201        let _ = SpaResult::new_return_async(10).into_sync_result();
202    }
203}