1use core::fmt;
4
5use crate::ffi::OSStatus;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum VTError {
14 SessionCreateFailed(OSStatus),
16 SetPropertyFailed { key: String, status: OSStatus },
18 PrepareFailed(OSStatus),
20 EncodeFailed(OSStatus),
22 CompleteFailed(OSStatus),
24 PixelBufferCreateFailed(i32),
26 EncoderCallback(OSStatus),
29 ApiFailed { api: &'static str, status: OSStatus },
31 UnexpectedSampleCount {
33 operation: &'static str,
34 expected: usize,
35 actual: i64,
36 },
37 TimedOut { operation: &'static str },
39 InvalidArgument(String),
41 Unsupported {
42 api: &'static str,
43 minimum: &'static str,
44 },
45 #[cfg(feature = "frame_processor")]
46 CommandBuffer(apple_metal::CommandBufferError),
47}
48
49impl VTError {
50 #[must_use]
52 pub const fn status(&self) -> Option<OSStatus> {
53 match self {
54 Self::SessionCreateFailed(s)
55 | Self::SetPropertyFailed { status: s, .. }
56 | Self::PrepareFailed(s)
57 | Self::EncodeFailed(s)
58 | Self::CompleteFailed(s)
59 | Self::EncoderCallback(s)
60 | Self::ApiFailed { status: s, .. } => Some(*s),
61 Self::PixelBufferCreateFailed(_)
62 | Self::UnexpectedSampleCount { .. }
63 | Self::TimedOut { .. }
64 | Self::InvalidArgument(_)
65 | Self::Unsupported { .. } => None,
66 #[cfg(feature = "frame_processor")]
67 Self::CommandBuffer(_) => None,
68 }
69 }
70}
71
72impl fmt::Display for VTError {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 match self {
75 Self::SessionCreateFailed(s) => write!(f, "VTCompressionSessionCreate failed: {s}"),
76 Self::SetPropertyFailed { key, status } => {
77 write!(f, "VTSessionSetProperty({key:?}) failed: {status}")
78 }
79 Self::PrepareFailed(s) => {
80 write!(f, "VTCompressionSessionPrepareToEncodeFrames failed: {s}")
81 }
82 Self::EncodeFailed(s) => write!(f, "VTCompressionSessionEncodeFrame failed: {s}"),
83 Self::CompleteFailed(s) => write!(f, "VTCompressionSessionCompleteFrames failed: {s}"),
84 Self::PixelBufferCreateFailed(s) => {
85 write!(f, "CVPixelBufferCreateWithIOSurface failed: {s}")
86 }
87 Self::EncoderCallback(s) => write!(f, "encoder callback reported status {s}"),
88 Self::ApiFailed { api, status } => write!(f, "{api} failed: {status}"),
89 Self::UnexpectedSampleCount {
90 operation,
91 expected,
92 actual,
93 } => write!(
94 f,
95 "{operation} requires exactly {expected} sample, but the buffer contains {actual}"
96 ),
97 Self::TimedOut { operation } => write!(f, "{operation} timed out"),
98 Self::InvalidArgument(m) => write!(f, "invalid argument: {m}"),
99 Self::Unsupported { api, minimum } => {
100 write!(f, "{api} requires macOS {minimum} or later")
101 }
102 #[cfg(feature = "frame_processor")]
103 Self::CommandBuffer(error) => write!(f, "command buffer: {error}"),
104 }
105 }
106}
107
108impl std::error::Error for VTError {
109 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
110 match self {
111 #[cfg(feature = "frame_processor")]
112 Self::CommandBuffer(error) => Some(error),
113 _ => None,
114 }
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::VTError;
121
122 #[test]
123 fn status_returns_underlying_osstatus_when_available() {
124 assert_eq!(VTError::SessionCreateFailed(-12903).status(), Some(-12903));
125 assert_eq!(
126 VTError::SetPropertyFailed {
127 key: "RealTime".to_owned(),
128 status: -50,
129 }
130 .status(),
131 Some(-50)
132 );
133 assert_eq!(
134 VTError::ApiFailed {
135 api: "VTSessionCopyProperty",
136 status: -7,
137 }
138 .status(),
139 Some(-7)
140 );
141 }
142
143 #[test]
144 fn status_returns_none_for_non_osstatus_variants() {
145 assert_eq!(VTError::PixelBufferCreateFailed(-666).status(), None);
146 assert_eq!(
147 VTError::InvalidArgument("width must be positive".to_owned()).status(),
148 None
149 );
150 assert_eq!(
151 VTError::UnexpectedSampleCount {
152 operation: "decode_frame_async",
153 expected: 1,
154 actual: 2,
155 }
156 .status(),
157 None
158 );
159 assert_eq!(
160 VTError::TimedOut {
161 operation: "RAW frame processing",
162 }
163 .status(),
164 None
165 );
166 assert_eq!(
167 VTError::Unsupported {
168 api: "VTDecompressionSessionDecodeFrameWithOptions",
169 minimum: "15.0",
170 }
171 .status(),
172 None
173 );
174 }
175
176 #[test]
177 fn display_formats_property_and_api_failures() {
178 assert_eq!(
179 VTError::SetPropertyFailed {
180 key: "ProfileLevel".to_owned(),
181 status: -12902,
182 }
183 .to_string(),
184 "VTSessionSetProperty(\"ProfileLevel\") failed: -12902"
185 );
186 assert_eq!(
187 VTError::ApiFailed {
188 api: "VTSessionCopyProperty",
189 status: -7,
190 }
191 .to_string(),
192 "VTSessionCopyProperty failed: -7"
193 );
194 }
195
196 #[test]
197 fn display_formats_argument_and_pixel_buffer_failures() {
198 assert_eq!(
199 VTError::InvalidArgument("zero width".to_owned()).to_string(),
200 "invalid argument: zero width"
201 );
202 assert_eq!(
203 VTError::PixelBufferCreateFailed(-666).to_string(),
204 "CVPixelBufferCreateWithIOSurface failed: -666"
205 );
206 assert_eq!(
207 VTError::EncoderCallback(-8971).to_string(),
208 "encoder callback reported status -8971"
209 );
210 assert_eq!(
211 VTError::UnexpectedSampleCount {
212 operation: "DecompressionSession::decode_frame_async",
213 expected: 1,
214 actual: 2,
215 }
216 .to_string(),
217 "DecompressionSession::decode_frame_async requires exactly 1 sample, but the buffer contains 2"
218 );
219 assert_eq!(
220 VTError::TimedOut {
221 operation: "RAW frame processing",
222 }
223 .to_string(),
224 "RAW frame processing timed out"
225 );
226 assert_eq!(
227 VTError::Unsupported {
228 api: "VTDecompressionSessionDecodeFrameWithOptions",
229 minimum: "15.0",
230 }
231 .to_string(),
232 "VTDecompressionSessionDecodeFrameWithOptions requires macOS 15.0 or later"
233 );
234 }
235}