use core::fmt;
use crate::ffi::OSStatus;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum VTError {
SessionCreateFailed(OSStatus),
SetPropertyFailed { key: String, status: OSStatus },
PrepareFailed(OSStatus),
EncodeFailed(OSStatus),
CompleteFailed(OSStatus),
PixelBufferCreateFailed(i32),
EncoderCallback(OSStatus),
ApiFailed { api: &'static str, status: OSStatus },
UnexpectedSampleCount {
operation: &'static str,
expected: usize,
actual: i64,
},
TimedOut { operation: &'static str },
InvalidArgument(String),
Unsupported {
api: &'static str,
minimum: &'static str,
},
#[cfg(feature = "frame_processor")]
CommandBuffer(apple_metal::CommandBufferError),
}
impl VTError {
#[must_use]
pub const fn status(&self) -> Option<OSStatus> {
match self {
Self::SessionCreateFailed(s)
| Self::SetPropertyFailed { status: s, .. }
| Self::PrepareFailed(s)
| Self::EncodeFailed(s)
| Self::CompleteFailed(s)
| Self::EncoderCallback(s)
| Self::ApiFailed { status: s, .. } => Some(*s),
Self::PixelBufferCreateFailed(_)
| Self::UnexpectedSampleCount { .. }
| Self::TimedOut { .. }
| Self::InvalidArgument(_)
| Self::Unsupported { .. } => None,
#[cfg(feature = "frame_processor")]
Self::CommandBuffer(_) => None,
}
}
}
impl fmt::Display for VTError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SessionCreateFailed(s) => write!(f, "VTCompressionSessionCreate failed: {s}"),
Self::SetPropertyFailed { key, status } => {
write!(f, "VTSessionSetProperty({key:?}) failed: {status}")
}
Self::PrepareFailed(s) => {
write!(f, "VTCompressionSessionPrepareToEncodeFrames failed: {s}")
}
Self::EncodeFailed(s) => write!(f, "VTCompressionSessionEncodeFrame failed: {s}"),
Self::CompleteFailed(s) => write!(f, "VTCompressionSessionCompleteFrames failed: {s}"),
Self::PixelBufferCreateFailed(s) => {
write!(f, "CVPixelBufferCreateWithIOSurface failed: {s}")
}
Self::EncoderCallback(s) => write!(f, "encoder callback reported status {s}"),
Self::ApiFailed { api, status } => write!(f, "{api} failed: {status}"),
Self::UnexpectedSampleCount {
operation,
expected,
actual,
} => write!(
f,
"{operation} requires exactly {expected} sample, but the buffer contains {actual}"
),
Self::TimedOut { operation } => write!(f, "{operation} timed out"),
Self::InvalidArgument(m) => write!(f, "invalid argument: {m}"),
Self::Unsupported { api, minimum } => {
write!(f, "{api} requires macOS {minimum} or later")
}
#[cfg(feature = "frame_processor")]
Self::CommandBuffer(error) => write!(f, "command buffer: {error}"),
}
}
}
impl std::error::Error for VTError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
#[cfg(feature = "frame_processor")]
Self::CommandBuffer(error) => Some(error),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::VTError;
#[test]
fn status_returns_underlying_osstatus_when_available() {
assert_eq!(VTError::SessionCreateFailed(-12903).status(), Some(-12903));
assert_eq!(
VTError::SetPropertyFailed {
key: "RealTime".to_owned(),
status: -50,
}
.status(),
Some(-50)
);
assert_eq!(
VTError::ApiFailed {
api: "VTSessionCopyProperty",
status: -7,
}
.status(),
Some(-7)
);
}
#[test]
fn status_returns_none_for_non_osstatus_variants() {
assert_eq!(VTError::PixelBufferCreateFailed(-666).status(), None);
assert_eq!(
VTError::InvalidArgument("width must be positive".to_owned()).status(),
None
);
assert_eq!(
VTError::UnexpectedSampleCount {
operation: "decode_frame_async",
expected: 1,
actual: 2,
}
.status(),
None
);
assert_eq!(
VTError::TimedOut {
operation: "RAW frame processing",
}
.status(),
None
);
assert_eq!(
VTError::Unsupported {
api: "VTDecompressionSessionDecodeFrameWithOptions",
minimum: "15.0",
}
.status(),
None
);
}
#[test]
fn display_formats_property_and_api_failures() {
assert_eq!(
VTError::SetPropertyFailed {
key: "ProfileLevel".to_owned(),
status: -12902,
}
.to_string(),
"VTSessionSetProperty(\"ProfileLevel\") failed: -12902"
);
assert_eq!(
VTError::ApiFailed {
api: "VTSessionCopyProperty",
status: -7,
}
.to_string(),
"VTSessionCopyProperty failed: -7"
);
}
#[test]
fn display_formats_argument_and_pixel_buffer_failures() {
assert_eq!(
VTError::InvalidArgument("zero width".to_owned()).to_string(),
"invalid argument: zero width"
);
assert_eq!(
VTError::PixelBufferCreateFailed(-666).to_string(),
"CVPixelBufferCreateWithIOSurface failed: -666"
);
assert_eq!(
VTError::EncoderCallback(-8971).to_string(),
"encoder callback reported status -8971"
);
assert_eq!(
VTError::UnexpectedSampleCount {
operation: "DecompressionSession::decode_frame_async",
expected: 1,
actual: 2,
}
.to_string(),
"DecompressionSession::decode_frame_async requires exactly 1 sample, but the buffer contains 2"
);
assert_eq!(
VTError::TimedOut {
operation: "RAW frame processing",
}
.to_string(),
"RAW frame processing timed out"
);
assert_eq!(
VTError::Unsupported {
api: "VTDecompressionSessionDecodeFrameWithOptions",
minimum: "15.0",
}
.to_string(),
"VTDecompressionSessionDecodeFrameWithOptions requires macOS 15.0 or later"
);
}
}