Skip to main content

rdi_core/
error.rs

1//! Error types for the `rdi-core` crate.
2
3use thiserror::Error;
4
5use crate::IconId;
6
7/// Errors returned by desktop-facing operations.
8///
9/// `#[non_exhaustive]` so variants can be added without a breaking
10/// change.
11#[non_exhaustive]
12#[derive(Debug, Error)]
13pub enum DesktopError {
14    /// The underlying backend could not be initialised or is temporarily
15    /// unavailable (e.g. the desktop `IFolderView2` could not be acquired).
16    #[error("desktop backend is unavailable: {0}")]
17    BackendUnavailable(String),
18
19    /// A COM / Win32 call returned a failure `HRESULT`.
20    #[error("COM error (0x{hresult:08X}): {msg}")]
21    Com { hresult: u32, msg: String },
22
23    /// A requested icon was not present in the current enumeration.
24    #[error("icon not found: {0}")]
25    IconNotFound(IconId),
26
27    /// A curve failed validation.
28    #[error("invalid curve: {0}")]
29    InvalidCurve(#[from] CurveError),
30
31    /// A duration policy could not be resolved to a concrete `Duration`.
32    #[error("invalid duration: {0}")]
33    InvalidDuration(String),
34
35    #[error("cannot resolve desktop grid: {0}")]
36    InvalidGrid(String),
37
38    /// Shader source, constants or renderer contract is invalid.
39    #[error("invalid effect: {0}")]
40    InvalidEffect(String),
41
42    /// The current OS has no backend implementation.
43    #[error("this platform is not supported by any compiled backend")]
44    UnsupportedPlatform,
45
46    /// The animation worker thread has stopped unexpectedly.
47    #[error("animation worker crashed: {0}")]
48    WorkerCrashed(String),
49
50    /// A concurrent animation is already in progress and the caller asked
51    /// for exclusive access.
52    #[error("another animation is already running")]
53    AnimationBusy,
54
55    /// The backend cannot open a transparent overlay for animation
56    /// rendering — the platform doesn't support it, or a compatibility
57    /// probe failed. The engine falls back to a direct
58    /// [`DesktopBackend::set_positions`](crate::DesktopBackend::set_positions)
59    /// teleport when it sees this variant (with a loud warning).
60    #[error("overlay renderer unavailable: {0}")]
61    OverlayUnavailable(String),
62
63    /// The overlay renderer aborted the current session because the
64    /// environment underneath it changed in a way that invalidates
65    /// its cached state — typically Explorer restarting, the display
66    /// topology changing, or the shell view HWND vanishing.
67    ///
68    /// The engine treats this as a **graceful stop** (equivalent to
69    /// [`FinishReason::Stopped(StopMode::TeleportToTarget)`](crate::events::FinishReason::Stopped)):
70    /// the real icons were teleported to their final positions on the
71    /// first overlay commit, so the animation ends at a correct steady
72    /// state. Finalisation runs as usual to unhide the real icons and
73    /// destroy the overlay window.
74    #[error("overlay animation cancelled: {0}")]
75    OverlayCancelled(String),
76}
77
78/// Errors raised while constructing or sampling curves.
79#[non_exhaustive]
80#[derive(Debug, Error, PartialEq)]
81pub enum CurveError {
82    #[error("keyframe curve must contain at least 2 keys, got {0}")]
83    TooFewKeys(usize),
84
85    #[error(
86        "keyframe times must be strictly ascending in [0, 1] \
87         (bad key at index {index}: t = {t})"
88    )]
89    InvalidKeyOrder { index: usize, t: f32 },
90
91    #[error("first keyframe must have t = 0.0, got t = {0}")]
92    FirstKeyNotZero(f32),
93
94    #[error("last keyframe must have t = 1.0, got t = {0}")]
95    LastKeyNotOne(f32),
96
97    #[error("keyframe value is not finite (index {index}, t = {t}, v = {v})")]
98    NonFiniteValue { index: usize, t: f32, v: f32 },
99
100    #[error(
101        "sampled function returned an out-of-range or non-finite value at t = {t}: v = {v}"
102    )]
103    SampledValueInvalid { t: f32, v: f32 },
104
105    #[error("sample count out of bounds: got {got}, expected 2..=4096")]
106    InvalidSampleCount { got: u32 },
107
108    #[error(
109        "invalid cubic-Bézier control points: c1x = {c1x}, c2x = {c2x} \
110         (both must lie in [0, 1])"
111    )]
112    InvalidBezier { c1x: f32, c2x: f32 },
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn desktop_error_display_covers_variants() {
121        assert!(format!("{}", DesktopError::UnsupportedPlatform).contains("platform"));
122        assert!(format!("{}", DesktopError::AnimationBusy).contains("already running"));
123        let e = DesktopError::Com {
124            hresult: 0x8000_4005,
125            msg: "boom".into(),
126        };
127        assert!(format!("{e}").contains("0x80004005"));
128    }
129
130    #[test]
131    fn curve_error_into_desktop_error_via_from() {
132        let e: DesktopError = CurveError::TooFewKeys(1).into();
133        assert!(matches!(e, DesktopError::InvalidCurve(_)));
134    }
135}