Skip to main content

raylib/core/
error.rs

1//! Definitions for error types used throughout the crate
2
3use thiserror::Error;
4
5/// Errors returned when initializing the raylib audio device.
6///
7/// # Examples
8///
9/// ```no_run
10/// use raylib::core::audio::RaylibAudio;
11/// use raylib::core::error::AudioInitError;
12///
13/// match RaylibAudio::init_audio_device() {
14///     Ok(_audio) => { /* use audio */ }
15///     Err(AudioInitError::DoubleInit) => {
16///         eprintln!("audio device is already initialized");
17///     }
18///     Err(AudioInitError::InitFailed) => {
19///         eprintln!("audio device backend rejected initialization");
20///     }
21/// }
22/// ```
23#[derive(Error, Debug)]
24pub enum AudioInitError {
25    /// The audio device has already been initialized by a live `RaylibAudio` instance.
26    ///
27    /// **Cause:** [`ffi::IsAudioDeviceReady`](crate::ffi::IsAudioDeviceReady) returned true
28    /// before [`ffi::InitAudioDevice`](crate::ffi::InitAudioDevice) was called, meaning a
29    /// previous `RaylibAudio` handle is still alive.
30    ///
31    /// **Recovery:** Drop the existing `RaylibAudio` handle before initializing a new one.
32    /// Only one audio device may be active at a time.
33    #[error("RaylibAudio cannot be instantiated more then once at a time")]
34    DoubleInit,
35    /// The audio backend reported failure when bringing the device online.
36    ///
37    /// **Cause:** After [`ffi::InitAudioDevice`](crate::ffi::InitAudioDevice) ran,
38    /// [`ffi::IsAudioDeviceReady`](crate::ffi::IsAudioDeviceReady) still returned false —
39    /// usually missing audio drivers, a busy device, or a permissions failure.
40    ///
41    /// **Recovery:** Check OS audio configuration and permissions; on headless CI you may
42    /// need a dummy audio device. No retry will succeed without a backend change.
43    #[error("failed to initialize audio device")]
44    InitFailed,
45}
46
47/// Errors returned when exporting wave data to a file.
48///
49/// # Examples
50///
51/// ```no_run
52/// use raylib::core::error::ExportWaveError;
53///
54/// fn handle(e: ExportWaveError) {
55///     match e {
56///         ExportWaveError::QoaBadSamples(bits) => {
57///             eprintln!("QOA requires 16-bit samples (got {bits})");
58///         }
59///         ExportWaveError::ExportFailed => {
60///             eprintln!("raylib refused to write the wave file");
61///         }
62///     }
63/// }
64/// ```
65#[derive(Error, Debug)]
66pub enum ExportWaveError {
67    /// The wave's sample depth is not 16 bits, which is the only depth QOA encoding accepts.
68    ///
69    /// **Cause:** The caller asked to export as QOA but the [`ffi::Wave`](crate::ffi::Wave)'s
70    /// `sampleSize` field is not 16. raylib's QOA exporter only handles 16-bit PCM.
71    ///
72    /// **Recovery:** Convert the wave to 16-bit (e.g. via [`ffi::WaveFormat`](crate::ffi::WaveFormat))
73    /// before exporting, or choose a different output format such as WAV.
74    #[error("wave data must be 16 bit per sample for QOA format export (actual: {0})")]
75    QoaBadSamples(i32),
76    /// raylib's [`ffi::ExportWave`](crate::ffi::ExportWave) reported failure when writing the file.
77    ///
78    /// **Cause:** The underlying file write failed — usually a missing parent directory,
79    /// unwritable path, or an unsupported file extension.
80    ///
81    /// **Recovery:** Verify the path is writable, the parent directory exists, and the
82    /// extension matches one of raylib's supported wave formats (`.wav`, `.qoa`, `.raw`).
83    #[error("failed to export wave data")]
84    ExportFailed,
85}
86
87/// Errors returned when loading a `Sound` or `Music` resource.
88///
89/// # Examples
90///
91/// ```no_run
92/// use raylib::core::error::LoadSoundError;
93///
94/// fn handle(e: LoadSoundError) {
95///     match e {
96///         LoadSoundError::LoadFailed { path } => eprintln!("sound load failed: {path}"),
97///         LoadSoundError::LoadFromWaveFailed => eprintln!("sound from wave failed"),
98///         LoadSoundError::LoadWaveFromFileFailed { path } => eprintln!("wave load failed: {path}"),
99///         LoadSoundError::Null => eprintln!("wave buffer was null"),
100///         LoadSoundError::LoadMusicFromFileFailed { path } => eprintln!("music load failed: {path}"),
101///         LoadSoundError::MusicNull => eprintln!("music buffer was null"),
102///     }
103/// }
104/// ```
105#[derive(Error, Debug)]
106pub enum LoadSoundError {
107    /// raylib's [`ffi::LoadSound`](crate::ffi::LoadSound) did not return a ready sound.
108    ///
109    /// **Cause:** The audio file at `path` was missing, unreadable, or in an unsupported
110    /// format; the resulting [`ffi::Sound`](crate::ffi::Sound) reported a null buffer.
111    ///
112    /// **Recovery:** Confirm the file exists (`std::fs::metadata(&path)`) and that its
113    /// extension is one of raylib's supported audio formats (`.wav`, `.ogg`, `.mp3`,
114    /// `.flac`, `.qoa`, `.xm`, `.mod`).
115    #[error("failed to load sound\npath: {path:?}")]
116    LoadFailed {
117        /// Path to the audio file raylib was unable to load.
118        path: String,
119    },
120    /// Loading a sound from an in-memory [`ffi::Wave`](crate::ffi::Wave) produced an empty sound.
121    ///
122    /// **Cause:** [`ffi::LoadSoundFromWave`](crate::ffi::LoadSoundFromWave) returned a sound
123    /// with a null backing buffer, usually because the source `Wave` was itself invalid or empty.
124    ///
125    /// **Recovery:** Validate the source `Wave` (non-zero frame count, non-null `data` pointer)
126    /// before converting to `Sound`.
127    #[error("failed to load sound from wave")]
128    LoadFromWaveFailed,
129    /// raylib's [`ffi::LoadWave`](crate::ffi::LoadWave) did not return a ready wave.
130    ///
131    /// **Cause:** The file at `path` was missing, unreadable, or in a format raylib's wave
132    /// decoder does not support; the resulting `Wave` had a null `data` pointer.
133    ///
134    /// **Recovery:** Verify the path exists and uses one of `.wav`, `.ogg`, `.mp3`, `.flac`,
135    /// or `.qoa`. Convert other formats up front.
136    #[error("cannot load wave\npath: {path:?}")]
137    LoadWaveFromFileFailed {
138        /// Path to the wave file raylib was unable to decode.
139        path: String,
140    },
141    /// raylib returned a [`ffi::Wave`](crate::ffi::Wave) whose `data` pointer is null.
142    ///
143    /// **Cause:** The buffer that was supposed to back the wave came back as null, indicating
144    /// either an allocation failure or an empty source.
145    ///
146    /// **Recovery:** Check the source buffer's length and contents; retry with a non-empty,
147    /// well-formed payload.
148    #[error("wave data is null, check provided buffer data")]
149    Null,
150    /// raylib's [`ffi::LoadMusicStream`](crate::ffi::LoadMusicStream) did not return a ready stream.
151    ///
152    /// **Cause:** The music file at `path` was missing, unreadable, or in an unsupported
153    /// format; the resulting [`ffi::Music`](crate::ffi::Music) reported a null buffer.
154    ///
155    /// **Recovery:** Confirm the file exists and uses one of raylib's supported music
156    /// formats; large streaming formats (`.mp3`, `.ogg`, `.flac`) usually work.
157    #[error("music could not be loaded from file\npath: {path:?}")]
158    LoadMusicFromFileFailed {
159        /// Path to the music file raylib was unable to open.
160        path: String,
161    },
162    /// raylib returned an [`ffi::Music`](crate::ffi::Music) whose backing buffer pointer is null.
163    ///
164    /// **Cause:** The streaming buffer raylib allocated for the music came back as null,
165    /// indicating either an allocation failure or a corrupt source.
166    ///
167    /// **Recovery:** Validate the source buffer and retry; if the source is in-memory data,
168    /// confirm the format hint matches the bytes provided.
169    #[error("music's buffer data is null, check provided buffer data")]
170    MusicNull,
171}
172
173/// Errors that can occur when pushing new audio data into a `Sound` or `AudioStream`.
174/// **Notes** (iann): if raylib upstream discussion introduces any of these checks, we might simplify these to avoid any redundancy i think
175/// 1. `SampleSizeMismatch` is raylib-rs only, raylib does not do sampleSize matching checks.
176/// 2. `TooManyFrames` comes from the WARNING behavior in raylib `UpdateAudioStreamInLockedState`: <https://github.com/raysan5/raylib/blob/master/src/raudio.c#L2662>
177///
178/// # Examples
179///
180/// ```no_run
181/// use raylib::core::error::UpdateAudioStreamError;
182///
183/// fn handle(e: UpdateAudioStreamError) {
184///     match e {
185///         UpdateAudioStreamError::SampleSizeMismatch { expected, provided } => {
186///             eprintln!("stream wants {expected}-bit samples, got {provided}-bit");
187///         }
188///         UpdateAudioStreamError::TooManyFrames { max, provided } => {
189///             eprintln!("buffer holds {max} frames, caller tried to write {provided}");
190///         }
191///         UpdateAudioStreamError::CallbackSlotBusy => {
192///             eprintln!("clear the existing callback first");
193///         }
194///     }
195/// }
196/// ```
197#[derive(Error, Debug)]
198pub enum UpdateAudioStreamError {
199    /// The provided sample type's bit depth does not match the stream's configured `sampleSize`.
200    ///
201    /// **Cause:** raylib-rs compared `size_of::<T>() * 8` against the audio stream's
202    /// `sampleSize` field and they disagreed. raylib itself does not perform this check;
203    /// it would `memcpy` silently and produce garbled audio.
204    ///
205    /// **Recovery:** Pass samples of the type the stream was created with (typically `i16`
206    /// for 16-bit streams or `f32` for 32-bit streams, which is the default for `Sound`).
207    #[error("update data format must match sound: expected {expected} bits, got {provided} bits")]
208    SampleSizeMismatch {
209        /// Sample bit depth the audio stream was configured with.
210        expected: usize,
211        /// Sample bit depth derived from the type the caller passed in.
212        provided: usize,
213    },
214    /// The caller asked to write more frames than the audio stream's buffer can hold.
215    ///
216    /// **Cause:** raylib's `UpdateAudioStreamInLockedState` logs a WARNING and truncates;
217    /// raylib-rs rejects the call instead so the caller does not silently lose frames.
218    ///
219    /// **Recovery:** Split the data into chunks no larger than `max` frames, or grow the
220    /// stream's buffer size at creation time.
221    #[error("Attempting to write too many frames to buffer: provided {provided}, max {max}")]
222    TooManyFrames {
223        /// Maximum number of frames the stream's internal buffer can accept in one write.
224        max: usize,
225        /// Number of frames the caller attempted to write.
226        provided: usize,
227    },
228    /// The audio stream already has a callback installed in the requested slot.
229    ///
230    /// **Cause:** raylib-rs tracks ownership of the stream's processor callback and refuses
231    /// to overwrite a live one.
232    ///
233    /// **Recovery:** Call `unset_audio_stream_callback()` (or drop the stream) before
234    /// installing a new callback.
235    #[error(
236        "AudioStream's callback slot is already in use; call unset_audio_stream_callback() to clear it"
237    )]
238    CallbackSlotBusy,
239}
240
241/// Errors returned when a memory allocation via raylib's allocator fails.
242///
243/// # Examples
244///
245/// ```no_run
246/// use raylib::core::error::AllocationError;
247///
248/// fn handle(e: AllocationError) {
249///     match e {
250///         AllocationError::NullAlloc => eprintln!("allocator returned null"),
251///         AllocationError::IntoUIntFailed => eprintln!("requested allocation too large"),
252///         AllocationError::ZeroBytes => eprintln!("zero-byte allocation requested"),
253///     }
254/// }
255/// ```
256#[derive(Error, Debug)]
257pub enum AllocationError {
258    /// [`MemAlloc`](crate::ffi::MemAlloc) returned null.
259    ///
260    /// **Cause:** raylib's internal allocator (or any custom allocator registered via
261    /// [`SetTraceLogCallback`](crate::ffi::SetTraceLogCallback)'s family) returned a null
262    /// pointer for the requested byte count — typically out-of-memory at the process or
263    /// platform level.
264    ///
265    /// **Recovery:** Free unused resources (drop unused `Image`/`Mesh`/`Texture` handles)
266    /// and retry, or report unrecoverable allocation failure to the caller.
267    #[error("memory request exceeds capacity")]
268    NullAlloc,
269    /// The size of `[T; count]` in bytes exceeds [`u32::MAX`]
270    /// (the largest value [`MemAlloc`](crate::ffi::MemAlloc) can be passed).
271    ///
272    /// **Cause:** The caller asked to allocate a slice whose total byte size exceeds
273    /// `u32::MAX` (~4 GiB). raylib's `MemAlloc` accepts `unsigned int` so larger
274    /// requests cannot be expressed through the FFI.
275    ///
276    /// **Recovery:** Split the allocation into smaller chunks (e.g. stream image data
277    /// in tiles), or use a Rust-side allocator (`Box`/`Vec`) for buffers raylib does
278    /// not need to own.
279    #[error("memory request in bytes exceeds unsigned integer maximum")]
280    IntoUIntFailed,
281    /// Attempted to pass 0 to [`MemAlloc`](crate::ffi::MemAlloc).
282    ///
283    /// **Cause:** Zero-byte allocations are not meaningful and raylib's allocator
284    /// rejects them; raylib-rs catches the request before the FFI call.
285    ///
286    /// **Recovery:** Skip the call entirely for empty containers, or use a sentinel
287    /// (e.g. `Option<Box<[T]>>`) to represent "no allocation needed".
288    #[error("requested zero bytes of memory")]
289    ZeroBytes,
290}
291
292/// Errors returned when validating a `Mesh` before upload or generation.
293///
294/// # Examples
295///
296/// ```no_run
297/// use raylib::core::error::InvalidMeshError;
298///
299/// fn handle(e: InvalidMeshError) {
300///     match e {
301///         InvalidMeshError::TrianglePointMiscount => eprintln!("vertex/index count is not a multiple of 3"),
302///         InvalidMeshError::IndexOutOfBounds => eprintln!("an index refers to a vertex past the end of the buffer"),
303///         InvalidMeshError::VertexUnindexible(_) => eprintln!("more than u16::MAX vertices"),
304///         InvalidMeshError::TexcoordsMiscount => eprintln!("texcoord count != vertex count"),
305///         InvalidMeshError::Texcoords2Miscount => eprintln!("texcoords2 count != vertex count"),
306///         InvalidMeshError::NormalsMiscount => eprintln!("normal count != vertex count"),
307///         InvalidMeshError::TangentsMiscount => eprintln!("tangent count != vertex count"),
308///         InvalidMeshError::ColorsMiscount => eprintln!("color count != vertex count"),
309///     }
310/// }
311/// ```
312#[derive(Error, Debug)]
313pub enum InvalidMeshError {
314    /// The mesh's vertex or index count is not divisible by three.
315    ///
316    /// **Cause:** raylib expects triangle lists, so the buffer length modulo three must be
317    /// zero. Indexed meshes must have an index count divisible by three; non-indexed meshes
318    /// must have a vertex count divisible by three.
319    ///
320    /// **Recovery:** Pad or trim the buffer so its length is a multiple of three, or fix
321    /// the geometry generator that produced the wrong count.
322    #[error("mesh should have 3 indices/vertices for each triangle")]
323    TrianglePointMiscount,
324    /// One or more indices reference a vertex position past the end of the vertex buffer.
325    ///
326    /// **Cause:** An entry in the indices slice is `>= vertex_count`, so dereferencing it
327    /// at draw time would read past the vertex buffer.
328    ///
329    /// **Recovery:** Clamp or regenerate the indices so every value is in `0..vertex_count`.
330    #[error("indices should be within the number of vertices")]
331    IndexOutOfBounds,
332    /// The vertex count cannot be represented as `u16`, so the mesh cannot be indexed.
333    ///
334    /// **Cause:** raylib's index buffer uses `u16`, so meshes with more than `u16::MAX`
335    /// vertices cannot be referenced by index.
336    ///
337    /// **Recovery:** Split the mesh into multiple sub-meshes, each with at most `u16::MAX`
338    /// vertices, or upload a non-indexed mesh.
339    #[error("mesh with indices should not exceed u16::MAX vertices")]
340    VertexUnindexible(std::num::TryFromIntError),
341    /// The primary texcoord buffer length does not match the vertex count.
342    ///
343    /// **Cause:** raylib expects one `Vector2` UV per vertex; the caller supplied a
344    /// different count.
345    ///
346    /// **Recovery:** Resize the texcoord buffer to match the vertex count, or omit the
347    /// buffer entirely (`None`) if UVs are not needed.
348    #[error("mesh should have one texcoord per vertex")]
349    TexcoordsMiscount,
350    /// The secondary texcoord buffer length does not match the vertex count.
351    ///
352    /// **Cause:** The optional `texcoords2` slice was supplied with a length different from
353    /// the vertex count.
354    ///
355    /// **Recovery:** Resize `texcoords2` to match the vertex count, or pass `None` to
356    /// disable the secondary UV channel.
357    #[error("mesh with texcoords2 should have one per vertex")]
358    Texcoords2Miscount,
359    /// The normals buffer length does not match the vertex count.
360    ///
361    /// **Cause:** raylib expects one normal per vertex; the slice supplied was a different
362    /// length.
363    ///
364    /// **Recovery:** Resize the normals buffer to match the vertex count, or pass `None`
365    /// to skip per-vertex normals.
366    #[error("mesh with normals should have one per vertex")]
367    NormalsMiscount,
368    /// The tangents buffer length does not match the vertex count.
369    ///
370    /// **Cause:** raylib expects one tangent per vertex; the slice supplied was a different
371    /// length.
372    ///
373    /// **Recovery:** Resize the tangents buffer to match the vertex count, or pass `None`
374    /// to skip per-vertex tangents.
375    #[error("mesh with tangents should have one per vertex")]
376    TangentsMiscount,
377    /// The colors buffer length does not match the vertex count.
378    ///
379    /// **Cause:** raylib expects one color per vertex; the slice supplied was a different
380    /// length.
381    ///
382    /// **Recovery:** Resize the colors buffer to match the vertex count, or pass `None`
383    /// to skip per-vertex colors.
384    #[error("mesh with colors should have one per vertex")]
385    ColorsMiscount,
386}
387
388/// Errors returned when procedurally generating a mesh.
389///
390/// # Examples
391///
392/// ```no_run
393/// use raylib::core::error::GenMeshError;
394///
395/// fn handle(e: GenMeshError) {
396///     match e {
397///         GenMeshError::InvalidMesh(inner) => eprintln!("mesh validation failed: {inner}"),
398///         GenMeshError::Allocation(inner) => eprintln!("mesh allocation failed: {inner}"),
399///     }
400/// }
401/// ```
402#[derive(Error, Debug)]
403pub enum GenMeshError {
404    /// The caller-supplied mesh data did not pass [`InvalidMeshError`] validation.
405    ///
406    /// **Cause:** One of the per-buffer length or index-range checks in [`InvalidMeshError`]
407    /// returned a concrete failure; this variant transports that inner error.
408    ///
409    /// **Recovery:** Inspect the inner [`InvalidMeshError`] for the specific buffer that
410    /// failed and fix the offending input.
411    #[error("provided mesh data does not correspond to a valid mesh")]
412    InvalidMesh(#[from] InvalidMeshError),
413    /// raylib's allocator failed while building the mesh's GPU-side buffers.
414    ///
415    /// **Cause:** [`ffi::MemAlloc`](crate::ffi::MemAlloc) returned null or rejected the
416    /// requested byte count when raylib-rs tried to copy mesh data into raylib-owned storage.
417    ///
418    /// **Recovery:** Reduce the mesh size, free unused resources, or inspect the inner
419    /// [`AllocationError`] for the specific failure mode.
420    #[error("could not allocate memory for the mesh data")]
421    Allocation(#[from] AllocationError),
422}
423
424/// Errors returned when compressing data.
425///
426/// # Examples
427///
428/// ```no_run
429/// use raylib::core::error::CompressionError;
430///
431/// match raylib::core::data::compress_data(b"hello") {
432///     Ok(_buf) => {}
433///     Err(CompressionError::CompressionFailed) => eprintln!("compression failed"),
434/// }
435/// ```
436#[derive(Error, Debug)]
437pub enum CompressionError {
438    /// raylib's compression helper returned a null or zero-length buffer.
439    ///
440    /// **Cause:** [`ffi::CompressData`](crate::ffi::CompressData) or
441    /// [`ffi::DecompressData`](crate::ffi::DecompressData) failed internally — usually
442    /// invalid or truncated input data, or an allocator failure.
443    ///
444    /// **Recovery:** Verify the input is non-empty and (for decompression) a complete
445    /// DEFLATE payload produced by raylib's matching compress call.
446    #[error("could not compress data")]
447    CompressionFailed,
448}
449
450/// Errors returned when encoding or decoding Base64 data.
451///
452/// # Examples
453///
454/// ```no_run
455/// use raylib::core::error::Base64Error;
456///
457/// match raylib::core::data::decode_data_base64(b"AAAA") {
458///     Ok(_buf) => {}
459///     Err(Base64Error::DecodeFailed) => eprintln!("not valid base64"),
460///     Err(Base64Error::EncodeFailed) => eprintln!("encode failed"),
461/// }
462/// ```
463#[derive(Error, Debug)]
464pub enum Base64Error {
465    /// raylib's [`ffi::DecodeDataBase64`](crate::ffi::DecodeDataBase64) failed.
466    ///
467    /// **Cause:** The input contained characters outside the Base64 alphabet, was missing
468    /// padding, or its length was inconsistent.
469    ///
470    /// **Recovery:** Re-validate the input is well-formed Base64 (matching `[A-Za-z0-9+/=]`)
471    /// and re-attempt.
472    #[error("could not decode base64 data")]
473    DecodeFailed,
474    /// raylib's [`ffi::EncodeDataBase64`](crate::ffi::EncodeDataBase64) failed.
475    ///
476    /// **Cause:** The encoder returned a null buffer, typically because the input length
477    /// could not be expressed in the FFI's integer type or because the allocator failed.
478    ///
479    /// **Recovery:** Shrink the input, or pre-allocate sufficient memory; very large
480    /// payloads should be encoded in chunks.
481    #[error("could not encode base64 data")]
482    EncodeFailed,
483}
484
485/// Errors returned when loading a `Model` resource.
486///
487/// # Examples
488///
489/// ```no_run
490/// use raylib::core::error::LoadModelError;
491///
492/// fn handle(e: LoadModelError) {
493///     match e {
494///         LoadModelError::LoadFromFileFailed { path } => eprintln!("model load failed: {path}"),
495///         LoadModelError::LoadFromMeshFailed => eprintln!("model from mesh failed"),
496///     }
497/// }
498/// ```
499#[derive(Error, Debug)]
500pub enum LoadModelError {
501    /// raylib's [`ffi::LoadModel`](crate::ffi::LoadModel) returned a model with no meshes,
502    /// materials, or skeleton.
503    ///
504    /// **Cause:** The file at `path` is missing, unreadable, or in a format raylib does
505    /// not support; the resulting [`ffi::Model`](crate::ffi::Model) had all of its core
506    /// pointers null.
507    ///
508    /// **Recovery:** Confirm the file exists and uses one of raylib's supported model
509    /// formats (`.obj`, `.iqm`, `.gltf`, `.glb`, `.vox`, `.m3d`).
510    #[error("could not load model\npath: {path:?}")]
511    LoadFromFileFailed {
512        /// Path to the model file raylib was unable to open.
513        path: String,
514    },
515    /// raylib's [`ffi::LoadModelFromMesh`](crate::ffi::LoadModelFromMesh) returned a model
516    /// with null mesh or material pointers.
517    ///
518    /// **Cause:** The supplied [`ffi::Mesh`](crate::ffi::Mesh) was invalid, or raylib's
519    /// allocator failed while building the wrapping model.
520    ///
521    /// **Recovery:** Validate the mesh via [`InvalidMeshError`] before wrapping, and retry
522    /// once the mesh passes validation.
523    #[error("could not load model from mesh")]
524    LoadFromMeshFailed,
525}
526
527/// Errors returned when loading model animations from a file.
528///
529/// # Examples
530///
531/// ```no_run
532/// use raylib::core::error::LoadModelAnimError;
533///
534/// fn handle(e: LoadModelAnimError) {
535///     match e {
536///         LoadModelAnimError::NoAnimationsLoaded { path } => {
537///             eprintln!("no animations in {path}");
538///         }
539///     }
540/// }
541/// ```
542#[derive(Error, Debug)]
543pub enum LoadModelAnimError {
544    /// raylib's [`ffi::LoadModelAnimations`](crate::ffi::LoadModelAnimations) reported zero
545    /// animations or a null array pointer.
546    ///
547    /// **Cause:** The file at `path` does not contain skeletal animations, or its format
548    /// is not one raylib's animation loader understands (typically `.iqm`, `.gltf`, `.glb`,
549    /// or `.m3d`).
550    ///
551    /// **Recovery:** Re-export the asset with an animation track baked in, or load the
552    /// model without expecting animation data.
553    #[error("no model animations loaded\npath: {path:?}")]
554    NoAnimationsLoaded {
555        /// Path to the model file that contained no animation tracks.
556        path: String,
557    },
558}
559
560/// Errors returned when assigning a material to a mesh slot on a model.
561///
562/// # Examples
563///
564/// ```no_run
565/// use raylib::core::error::SetMaterialError;
566///
567/// fn handle(e: SetMaterialError) {
568///     match e {
569///         SetMaterialError::MeshIdOutOfBounds => eprintln!("mesh_id past end of model"),
570///         SetMaterialError::MaterialIdOutOfBounds => eprintln!("material_id past end of model"),
571///     }
572/// }
573/// ```
574#[derive(Error, Debug)]
575pub enum SetMaterialError {
576    /// The caller's `mesh_id` is `>= model.meshCount`.
577    ///
578    /// **Cause:** raylib-rs bounds-checks the mesh index against the [`ffi::Model`](crate::ffi::Model)'s
579    /// `meshCount` field before forwarding to
580    /// [`ffi::SetModelMeshMaterial`](crate::ffi::SetModelMeshMaterial), which would
581    /// otherwise read garbage.
582    ///
583    /// **Recovery:** Use a `mesh_id` in `0..model.meshCount`.
584    #[error("mesh_id greater than mesh count")]
585    MeshIdOutOfBounds,
586    /// The caller's `material_id` is `>= model.materialCount`.
587    ///
588    /// **Cause:** raylib-rs bounds-checks the material index against the [`ffi::Model`](crate::ffi::Model)'s
589    /// `materialCount` field before forwarding to
590    /// [`ffi::SetModelMeshMaterial`](crate::ffi::SetModelMeshMaterial).
591    ///
592    /// **Recovery:** Use a `material_id` in `0..model.materialCount`.
593    #[error("material_id greater than material count")]
594    MaterialIdOutOfBounds,
595}
596
597/// Errors returned when loading materials from a file.
598///
599/// # Examples
600///
601/// ```no_run
602/// use raylib::core::models::Material;
603/// use raylib::core::error::LoadMaterialError;
604///
605/// match Material::load_materials("assets/scene.mtl") {
606///     Ok(materials) => { /* use materials */ }
607///     Err(LoadMaterialError::NoneLoaded { path }) => {
608///         eprintln!("no materials found in {path}");
609///     }
610/// }
611/// ```
612#[derive(Error, Debug)]
613pub enum LoadMaterialError {
614    /// raylib's [`ffi::LoadMaterials`](crate::ffi::LoadMaterials) returned zero materials.
615    ///
616    /// **Cause:** The file at `path` does not contain any material definitions raylib's
617    /// material loader recognized — typically a malformed `.mtl` or a model file with no
618    /// embedded materials.
619    ///
620    /// **Recovery:** Verify the `.mtl` (or model) file lists at least one material, or
621    /// fall back to a default material constructed via [`ffi::LoadMaterialDefault`](crate::ffi::LoadMaterialDefault).
622    #[error("no materials loaded\npath: {path:?}")]
623    NoneLoaded {
624        /// Path to the material source file that yielded no materials.
625        path: String,
626    },
627}
628
629/// Errors returned when loading a `Font` resource.
630///
631/// # Examples
632///
633/// ```no_run
634/// use raylib::core::error::LoadFontError;
635///
636/// fn handle(e: LoadFontError) {
637///     match e {
638///         LoadFontError::LoadFromFileFailed { path } => eprintln!("font file load failed: {path}"),
639///         LoadFontError::LoadFromImageFailed => eprintln!("font from image failed"),
640///         LoadFontError::LoadFromMemoryFailed => eprintln!("font from memory failed"),
641///     }
642/// }
643/// ```
644#[derive(Error, Debug)]
645pub enum LoadFontError {
646    /// raylib's [`ffi::LoadFont`](crate::ffi::LoadFont) returned a font whose glyph or
647    /// texture data was empty.
648    ///
649    /// **Cause:** The font file at `path` was missing, unreadable, or in a format raylib
650    /// does not support.
651    ///
652    /// **Recovery:** Confirm the file exists and uses one of `.ttf`, `.otf`, `.fnt`, or
653    /// `.png` (for bitmap fonts).
654    #[error(
655        "error loading font; check if the file exists and if it's the right type\npath: {path:?}"
656    )]
657    LoadFromFileFailed {
658        /// Path to the font file raylib was unable to open.
659        path: String,
660    },
661    /// raylib's [`ffi::LoadFontFromImage`](crate::ffi::LoadFontFromImage) returned an empty
662    /// font.
663    ///
664    /// **Cause:** The supplied image did not contain a recognizable glyph atlas — the key
665    /// color was missing or the layout did not produce any cells.
666    ///
667    /// **Recovery:** Confirm the image follows raylib's bitmap-font atlas layout and that
668    /// the chosen key color appears on the separators.
669    #[error("error loading font from image")]
670    LoadFromImageFailed,
671    /// raylib's [`ffi::LoadFontFromMemory`](crate::ffi::LoadFontFromMemory) returned an
672    /// empty font.
673    ///
674    /// **Cause:** The byte buffer was not a complete font file, or the supplied file-type
675    /// hint (e.g. `".ttf"`) did not match the bytes.
676    ///
677    /// **Recovery:** Verify the buffer holds a complete font file and that the file-type
678    /// extension hint matches its actual format.
679    #[error("error loading font from memory; check if the file's type is correct")]
680    LoadFromMemoryFailed,
681}
682
683/// Errors returned when an `Image` is invalid or cannot be processed.
684///
685/// # Examples
686///
687/// ```no_run
688/// use raylib::core::texture::Image;
689/// use raylib::core::error::InvalidImageError;
690///
691/// match Image::load_image("assets/sprite.png") {
692///     Ok(img) => { /* use img */ }
693///     Err(InvalidImageError::NullDataFromFile) => {
694///         eprintln!("file missing or unsupported format");
695///     }
696///     Err(InvalidImageError::InvalidFile) => {
697///         eprintln!("file data malformed");
698///     }
699///     Err(_) => eprintln!("other image-load error"),
700/// }
701/// ```
702#[derive(Error, Debug)]
703pub enum InvalidImageError {
704    /// The image's `width` field is zero.
705    ///
706    /// **Cause:** A zero-width image cannot be drawn, sampled, or uploaded as a texture;
707    /// raylib-rs rejects it before any FFI call that would dereference its pixel buffer.
708    ///
709    /// **Recovery:** Re-generate or re-load the image with a positive width.
710    #[error("invalid image: width is 0")]
711    ZeroWidth,
712    /// The image's `height` field is zero.
713    ///
714    /// **Cause:** A zero-height image cannot be drawn, sampled, or uploaded as a texture;
715    /// raylib-rs rejects it before any FFI call that would dereference its pixel buffer.
716    ///
717    /// **Recovery:** Re-generate or re-load the image with a positive height.
718    #[error("invalid image: height is 0")]
719    ZeroHeight,
720    /// The image's `data` pointer is null.
721    ///
722    /// **Cause:** The pixel buffer pointer is null even though width and height are
723    /// non-zero — typically a partially-constructed image.
724    ///
725    /// **Recovery:** Re-load or re-generate the image from a known-valid source.
726    #[error("invalid image: data is null")]
727    NullData,
728    /// raylib's [`ffi::LoadImage`](crate::ffi::LoadImage) returned an image with a null
729    /// data pointer.
730    ///
731    /// **Cause:** The file does not exist, is unreadable, or is in a format raylib's image
732    /// loaders do not support.
733    ///
734    /// **Recovery:** Confirm the file exists and uses one of `.png`, `.bmp`, `.tga`,
735    /// `.jpg`, `.gif`, `.qoi`, `.psd`, `.dds`, `.hdr`, `.ktx`, `.astc`, `.pkm`, `.pvr`.
736    #[error("image data is null, either the file doesnt exist or the image type is unsupported")]
737    NullDataFromFile,
738    /// raylib reported the file contents could not be parsed as an image.
739    ///
740    /// **Cause:** The file's header is missing, truncated, or otherwise malformed for its
741    /// declared format.
742    ///
743    /// **Recovery:** Re-export the image from the source program, or open it in a viewer
744    /// to confirm it is not corrupt.
745    #[error("invalid file data")]
746    InvalidFile,
747    /// raylib's [`ffi::LoadImageFromMemory`](crate::ffi::LoadImageFromMemory) returned an
748    /// image with a null data pointer.
749    ///
750    /// **Cause:** The supplied byte buffer was empty, truncated, or did not match the
751    /// file-type extension hint.
752    ///
753    /// **Recovery:** Verify the buffer holds a complete image file and that the
754    /// file-extension hint matches its actual format.
755    #[error("image data is null, check provided buffer data")]
756    NullDataFromMemory,
757    /// raylib's [`ffi::LoadImageFromTexture`](crate::ffi::LoadImageFromTexture) returned an
758    /// image with a null data pointer.
759    ///
760    /// **Cause:** The GPU readback from the source texture failed — usually an unsupported
761    /// pixel format or a context-lost situation.
762    ///
763    /// **Recovery:** Confirm the source texture uses a CPU-readable
764    /// [`ffi::PixelFormat`](crate::ffi::PixelFormat) (e.g. `PIXELFORMAT_UNCOMPRESSED_R8G8B8A8`)
765    /// and that the GPU context is still current.
766    #[error("failed to retrieve pixel data")]
767    NullDataFromTexture,
768    /// The image's pixel format is not supported by the requested operation.
769    ///
770    /// **Cause:** raylib's image processing helpers only accept a subset of pixel formats;
771    /// the supplied image uses one outside that set (commonly the compressed formats).
772    ///
773    /// **Recovery:** Convert the image to a supported
774    /// [`ffi::PixelFormat`](crate::ffi::PixelFormat) (typically `PIXELFORMAT_UNCOMPRESSED_R8G8B8A8`)
775    /// via [`ffi::ImageFormat`](crate::ffi::ImageFormat) before re-attempting.
776    #[error("unsupported format")]
777    UnsupportedFormat,
778    /// The convolution kernel's width and height differ.
779    ///
780    /// **Cause:** raylib's convolution helper requires a square kernel; raylib-rs
781    /// bounds-checks `kernel.len()` against an integer square root before calling
782    /// [`ffi::ImageKernelConvolution`](crate::ffi::ImageKernelConvolution).
783    ///
784    /// **Recovery:** Resize the kernel so its length is a perfect square (`9`, `16`,
785    /// `25`, ...).
786    #[error("convolution kernel must be square to be applied")]
787    NonSquareKernel,
788}
789
790/// Errors returned when updating texture data on the GPU.
791///
792/// # Examples
793///
794/// ```no_run
795/// use raylib::core::error::UpdateTextureError;
796///
797/// fn handle(e: UpdateTextureError) {
798///     match e {
799///         UpdateTextureError::WrongDataSize { expect, actual } => {
800///             eprintln!("texture expects {expect} bytes, got {actual}");
801///         }
802///         UpdateTextureError::OutOfBounds => eprintln!("region exceeds texture bounds"),
803///         UpdateTextureError::NegativeSize => eprintln!("region has negative extents"),
804///     }
805/// }
806/// ```
807#[derive(Error, Debug)]
808pub enum UpdateTextureError {
809    /// The pixel-buffer slice does not match the byte size of the texture region.
810    ///
811    /// **Cause:** raylib-rs computed `width * height * bytes_per_pixel` for the target
812    /// region and the supplied slice was a different length; passing it to
813    /// [`ffi::UpdateTexture`](crate::ffi::UpdateTexture) would read out of bounds.
814    ///
815    /// **Recovery:** Size the input buffer to exactly `expect` bytes (region width times
816    /// height times bytes per pixel for the texture's format).
817    #[error("data is wrong size (expected {expect} bytes, got {actual})")]
818    WrongDataSize {
819        /// Byte count raylib-rs derived from the texture region's dimensions and format.
820        expect: usize,
821        /// Byte count of the slice the caller actually supplied.
822        actual: usize,
823    },
824    /// The destination rectangle extends past the texture's `width` or `height`.
825    ///
826    /// **Cause:** The rectangle's `x + width` or `y + height` exceeds the texture
827    /// dimensions; raylib-rs blocks this before [`ffi::UpdateTextureRec`](crate::ffi::UpdateTextureRec)
828    /// would otherwise overflow.
829    ///
830    /// **Recovery:** Clamp the rectangle so it lies entirely within the texture's bounds.
831    #[error("destination rectangle cannot exceed texture bounds")]
832    OutOfBounds,
833    /// The destination rectangle has a negative width or height.
834    ///
835    /// **Cause:** raylib treats the rectangle's `width`/`height` as unsigned at the FFI
836    /// boundary; raylib-rs rejects negative values up front so they cannot wrap into very
837    /// large positive sizes.
838    ///
839    /// **Recovery:** Pass non-negative extents; swap the corners if the caller's
840    /// coordinates were inverted.
841    #[error("destination rectangle cannot have negative extents")]
842    NegativeSize,
843}
844
845/// Errors returned when loading a `Texture` resource.
846///
847/// # Examples
848///
849/// ```no_run
850/// use raylib::core::error::LoadTextureError;
851///
852/// fn handle(e: LoadTextureError) {
853///     match e {
854///         LoadTextureError::TextureFromFileFailed { path } => eprintln!("texture file load failed: {path}"),
855///         LoadTextureError::CubemapFromImageFailed => eprintln!("cubemap from image failed"),
856///         LoadTextureError::TextureFromImageFailed => eprintln!("texture from image failed"),
857///         LoadTextureError::CreateRenderTextureFailed => eprintln!("render texture creation failed"),
858///         LoadTextureError::InvalidData => eprintln!("texture data invalid"),
859///     }
860/// }
861/// ```
862#[derive(Error, Debug)]
863pub enum LoadTextureError {
864    /// raylib's [`ffi::LoadTexture`](crate::ffi::LoadTexture) returned a texture with id 0.
865    ///
866    /// **Cause:** The file at `path` was missing, unreadable, or in a format raylib's
867    /// image loaders do not support; the resulting GPU texture id was the sentinel 0.
868    ///
869    /// **Recovery:** Confirm the file exists and uses a supported format; for compressed
870    /// formats (`.dds`, `.ktx`, `.astc`), confirm the GPU supports the encoding.
871    #[error("failed to load the texture\npath: {path:?}")]
872    TextureFromFileFailed {
873        /// Path to the image file raylib was unable to upload as a texture.
874        path: String,
875    },
876    /// raylib's [`ffi::LoadTextureCubemap`](crate::ffi::LoadTextureCubemap) returned a
877    /// cubemap with id 0.
878    ///
879    /// **Cause:** The source image did not match one of raylib's recognized cubemap
880    /// layouts (line/cross/panorama), or its dimensions could not be divided into six
881    /// square faces.
882    ///
883    /// **Recovery:** Re-export the image in a supported cubemap layout, or supply six
884    /// individual face images.
885    #[error("failed to load image as a texture cubemap")]
886    CubemapFromImageFailed,
887    /// raylib's [`ffi::LoadTextureFromImage`](crate::ffi::LoadTextureFromImage) returned a
888    /// texture with id 0.
889    ///
890    /// **Cause:** The supplied [`ffi::Image`](crate::ffi::Image) was invalid (null data,
891    /// zero extents) or its pixel format is not supported by the active GL backend.
892    ///
893    /// **Recovery:** Validate the image via [`InvalidImageError`] checks and, if needed,
894    /// convert it to a GL-uploadable pixel format before re-attempting.
895    #[error("failed to load image as a texture")]
896    TextureFromImageFailed,
897    /// raylib's [`ffi::LoadRenderTexture`](crate::ffi::LoadRenderTexture) returned a
898    /// render texture whose backing framebuffer id is 0.
899    ///
900    /// **Cause:** The GL backend rejected the framebuffer attachment — typically because
901    /// the requested dimensions exceed `GL_MAX_RENDERBUFFER_SIZE` or the active context
902    /// is lost.
903    ///
904    /// **Recovery:** Lower the framebuffer dimensions, or verify the GL context is current
905    /// and not exhausted.
906    #[error("failed to create render texture")]
907    CreateRenderTextureFailed,
908    /// The supplied data is not a recognized texture payload.
909    ///
910    /// **Cause:** raylib-rs validated the input (typically a raw pixel buffer) and found
911    /// it inconsistent with the requested width/height/format before invoking the FFI.
912    ///
913    /// **Recovery:** Ensure the buffer length matches `width * height * bytes_per_pixel`
914    /// for the requested pixel format.
915    #[error("data is not valid to load texture")]
916    InvalidData,
917}
918
919/// Top-level error type that aggregates all raylib-rs domain errors.
920///
921/// Marked `#[non_exhaustive]`: new domain errors may gain variants here in minor releases,
922/// so matches must include a wildcard arm.
923///
924/// # Examples
925///
926/// ```no_run
927/// use raylib::core::error::RaylibError;
928///
929/// fn handle(e: RaylibError) {
930///     match e {
931///         RaylibError::AudioInit(_) => eprintln!("audio device failed to initialize"),
932///         RaylibError::LoadSound(_) => eprintln!("sound load failed"),
933///         RaylibError::LoadModel(_) => eprintln!("model load failed"),
934///         RaylibError::LoadFont(_) => eprintln!("font load failed"),
935///         RaylibError::LoadTexture(_) => eprintln!("texture load failed"),
936///         RaylibError::SetCallback(_) => eprintln!("callback slot already occupied"),
937///         other => eprintln!("raylib error: {other}"),
938///     }
939/// }
940/// ```
941#[derive(Error, Debug)]
942#[non_exhaustive]
943pub enum RaylibError {
944    /// Wraps an [`AudioInitError`] surfaced through `?` from an audio init call site.
945    ///
946    /// **Cause:** A call to `RaylibAudio::init_audio_device()` (or another audio-init
947    /// entry point) propagated up an [`AudioInitError`].
948    ///
949    /// **Recovery:** Inspect the inner [`AudioInitError`] for the specific reason
950    /// (`DoubleInit` vs `InitFailed`) and apply that variant's recovery.
951    #[error("audio initialization error")]
952    AudioInit(#[from] AudioInitError),
953    /// Wraps an [`ExportWaveError`] surfaced through `?` from a wave-export call site.
954    ///
955    /// **Cause:** A wave-export call (typically `Wave::export`) propagated up an
956    /// [`ExportWaveError`].
957    ///
958    /// **Recovery:** Inspect the inner [`ExportWaveError`] for the specific reason and
959    /// apply that variant's recovery.
960    #[error("wave export error")]
961    ExportWave(#[from] ExportWaveError),
962    /// Wraps a [`LoadSoundError`] surfaced through `?` from a sound or music load site.
963    ///
964    /// **Cause:** A sound or music load call (e.g. `RaylibAudio::new_sound`,
965    /// `RaylibAudio::new_music`) propagated up a [`LoadSoundError`].
966    ///
967    /// **Recovery:** Inspect the inner [`LoadSoundError`] variant and apply its recovery.
968    #[error("sound loading error")]
969    LoadSound(#[from] LoadSoundError),
970    /// Wraps an [`AllocationError`] surfaced through `?` from any raylib-allocator call site.
971    ///
972    /// **Cause:** A path that calls [`ffi::MemAlloc`](crate::ffi::MemAlloc) (mesh upload,
973    /// data buffer construction, etc.) propagated up an [`AllocationError`].
974    ///
975    /// **Recovery:** Inspect the inner [`AllocationError`] variant and apply its recovery
976    /// (usually: reduce the request size).
977    #[error("allocation error")]
978    Allocation(#[from] AllocationError),
979    /// Wraps a [`CompressionError`] surfaced through `?` from a data-compression call site.
980    ///
981    /// **Cause:** A `compress_data` or `decompress_data` call propagated up a
982    /// [`CompressionError`].
983    ///
984    /// **Recovery:** Inspect the inner [`CompressionError`] variant and apply its recovery.
985    #[error("compression error")]
986    Compression(#[from] CompressionError),
987    /// Wraps a [`LoadModelError`] surfaced through `?` from a model-load call site.
988    ///
989    /// **Cause:** A `load_model` or `load_model_from_mesh` call propagated up a
990    /// [`LoadModelError`].
991    ///
992    /// **Recovery:** Inspect the inner [`LoadModelError`] variant and apply its recovery.
993    #[error("model loading error")]
994    LoadModel(#[from] LoadModelError),
995    /// Wraps a [`LoadModelAnimError`] surfaced through `?` from a model-animation load site.
996    ///
997    /// **Cause:** A `load_model_animations` call propagated up a [`LoadModelAnimError`].
998    ///
999    /// **Recovery:** Inspect the inner [`LoadModelAnimError`] variant and apply its
1000    /// recovery.
1001    #[error("model animation loading error")]
1002    LoadModelAnim(#[from] LoadModelAnimError),
1003    /// Wraps a [`SetMaterialError`] surfaced through `?` from a material-assignment site.
1004    ///
1005    /// **Cause:** A `set_model_mesh_material` call propagated up a [`SetMaterialError`].
1006    ///
1007    /// **Recovery:** Inspect the inner [`SetMaterialError`] variant and apply its recovery
1008    /// (usually: clamp the mesh or material id).
1009    #[error("material update error")]
1010    SetMaterial(#[from] SetMaterialError),
1011    /// Wraps a [`LoadMaterialError`] surfaced through `?` from a material-load call site.
1012    ///
1013    /// **Cause:** A `load_materials` call propagated up a [`LoadMaterialError`].
1014    ///
1015    /// **Recovery:** Inspect the inner [`LoadMaterialError`] variant and apply its
1016    /// recovery.
1017    #[error("material loading error")]
1018    LoadMaterial(#[from] LoadMaterialError),
1019    /// Wraps a [`LoadFontError`] surfaced through `?` from a font-load call site.
1020    ///
1021    /// **Cause:** A `load_font`, `load_font_from_image`, or `load_font_from_memory` call
1022    /// propagated up a [`LoadFontError`].
1023    ///
1024    /// **Recovery:** Inspect the inner [`LoadFontError`] variant and apply its recovery.
1025    #[error("font loading error")]
1026    LoadFont(#[from] LoadFontError),
1027    /// Wraps an [`InvalidImageError`] surfaced through `?` from an image-processing site.
1028    ///
1029    /// **Cause:** An image load, conversion, or processing call propagated up an
1030    /// [`InvalidImageError`].
1031    ///
1032    /// **Recovery:** Inspect the inner [`InvalidImageError`] variant and apply its
1033    /// recovery.
1034    #[error("image error")]
1035    InvalidImage(#[from] InvalidImageError),
1036    /// Wraps an [`UpdateTextureError`] surfaced through `?` from a texture-update site.
1037    ///
1038    /// **Cause:** An `update_texture` or `update_texture_rec` call propagated up an
1039    /// [`UpdateTextureError`].
1040    ///
1041    /// **Recovery:** Inspect the inner [`UpdateTextureError`] variant and apply its
1042    /// recovery.
1043    #[error("texture update error")]
1044    UpdateTexture(#[from] UpdateTextureError),
1045    /// Wraps a [`LoadTextureError`] surfaced through `?` from a texture-load call site.
1046    ///
1047    /// **Cause:** A `load_texture`, `load_texture_from_image`, `load_texture_cubemap`, or
1048    /// `load_render_texture` call propagated up a [`LoadTextureError`].
1049    ///
1050    /// **Recovery:** Inspect the inner [`LoadTextureError`] variant and apply its recovery.
1051    #[error("texture loading error")]
1052    LoadTexture(#[from] LoadTextureError),
1053    /// Wraps an [`UpdateAudioStreamError`] surfaced through `?` from an audio-stream update
1054    /// or callback-install site.
1055    ///
1056    /// **Cause:** An `AudioStream::update` or `set_audio_stream_callback` call propagated up
1057    /// an [`UpdateAudioStreamError`].
1058    ///
1059    /// **Recovery:** Inspect the inner [`UpdateAudioStreamError`] variant and apply its
1060    /// recovery.
1061    #[error("audio stream update error")]
1062    UpdateAudioStream(#[from] UpdateAudioStreamError),
1063    /// Wraps an [`InvalidMeshError`] surfaced through `?` from a mesh-validation site.
1064    ///
1065    /// **Cause:** A mesh accessor or builder validated a [`crate::ffi::Mesh`] and found its
1066    /// buffers inconsistent.
1067    ///
1068    /// **Recovery:** Inspect the inner [`InvalidMeshError`] variant and apply its recovery.
1069    #[error("invalid mesh error")]
1070    InvalidMesh(#[from] InvalidMeshError),
1071    /// Wraps a [`GenMeshError`] surfaced through `?` from a mesh-generation site.
1072    ///
1073    /// **Cause:** A `MeshBuilder::build` (or other mesh-generation) call propagated up a
1074    /// [`GenMeshError`].
1075    ///
1076    /// **Recovery:** Inspect the inner [`GenMeshError`] variant and apply its recovery.
1077    #[error("mesh generation error")]
1078    GenMesh(#[from] GenMeshError),
1079    /// Wraps a [`Base64Error`] surfaced through `?` from a base64 encode/decode site.
1080    ///
1081    /// **Cause:** An `encode_data_base64` or `decode_data_base64` call propagated up a
1082    /// [`Base64Error`].
1083    ///
1084    /// **Recovery:** Inspect the inner [`Base64Error`] variant and apply its recovery.
1085    #[error("base64 error")]
1086    Base64(#[from] Base64Error),
1087    /// Wraps a [`LoadIconsError`] surfaced through `?` from a raygui icons-load site.
1088    ///
1089    /// **Cause:** A `gui_load_icons`/`gui_load_icons_from_memory` call propagated up a
1090    /// [`LoadIconsError`].
1091    ///
1092    /// **Recovery:** Inspect the inner [`LoadIconsError`] variant and apply its recovery.
1093    #[error("icons loading error")]
1094    LoadIcons(#[from] LoadIconsError),
1095    /// Wraps a [`LoadStyleFromMemoryError`] surfaced through `?` from a raygui style-load site.
1096    ///
1097    /// **Cause:** A `gui_load_style_from_memory` call propagated up a
1098    /// [`LoadStyleFromMemoryError`].
1099    ///
1100    /// **Recovery:** Inspect the inner [`LoadStyleFromMemoryError`] variant and apply its
1101    /// recovery.
1102    #[error("style loading error")]
1103    LoadStyleFromMemory(#[from] LoadStyleFromMemoryError),
1104    /// Wraps a [`crate::core::pixel::PixelColorError`] surfaced through `?` from a
1105    /// pixel-level color read/write site.
1106    ///
1107    /// **Cause:** A `get_pixel_color`/`set_pixel_color` call propagated up a
1108    /// [`crate::core::pixel::PixelColorError`].
1109    ///
1110    /// **Recovery:** Inspect the inner error variant and apply its recovery.
1111    #[error("pixel color error")]
1112    PixelColor(#[from] crate::core::pixel::PixelColorError),
1113    /// Wraps a [`SetCallbackError`] surfaced through `?` from a callback-install site.
1114    ///
1115    /// **Cause:** A `set_*_callback` call found its global slot already occupied.
1116    ///
1117    /// **Recovery:** Keep a single registration site per callback kind.
1118    #[error("callback registration error")]
1119    SetCallback(#[from] SetCallbackError),
1120}
1121
1122/// Errors that can occur while loading a raygui `.rgi` icons file.
1123///
1124/// raygui's own `GuiLoadIcons` returns silently on failure (NULL is also
1125/// returned when names aren't requested), so this enum's variants are
1126/// surfaced by pre-validation in Rust before the FFI call. The
1127/// `_from_memory` variants share the same set minus [`Self::FileNotFound`]
1128/// and [`Self::Io`].
1129#[derive(Debug, thiserror::Error)]
1130pub enum LoadIconsError {
1131    /// The icons file at `path` does not exist.
1132    ///
1133    /// **Cause:** A pre-FFI file-existence check reported the path as missing
1134    /// (i.e., a `std::fs` lookup returned `io::ErrorKind::NotFound`). Other
1135    /// `io::Error` kinds (permission-denied, mid-read failures) surface as
1136    /// [`Self::Io`] instead.
1137    ///
1138    /// **Recovery:** Verify the path; check working directory; confirm the file
1139    /// has the `.rgi` extension and exists.
1140    #[error("icons file not found: {0:?}")]
1141    FileNotFound(std::path::PathBuf),
1142
1143    /// The icons file or memory buffer is shorter than raygui's 12-byte header
1144    /// (4-byte signature + 2-byte version + 2-byte reserved + 2-byte icon count
1145    /// + 2-byte icon size).
1146    ///
1147    /// **Cause:** A truncated download, a non-`.rgi` file accidentally renamed,
1148    /// or a programmatically-constructed buffer that was never fully populated.
1149    ///
1150    /// **Recovery:** Confirm the source data is a complete raygui icons payload.
1151    #[error("icons file too short: need >=12 bytes for header, got {0}")]
1152    HeaderTruncated(usize),
1153
1154    /// The icons payload's first 4 bytes are not `b"rGI "`.
1155    ///
1156    /// **Cause:** The file is not a raygui `.rgi` icons file, or it has been
1157    /// corrupted.
1158    ///
1159    /// **Recovery:** Confirm the file was produced by the `rGuiIcons` tool or
1160    /// a compatible writer.
1161    #[error("invalid .rgi signature: expected 'rGI ', got {0:?}")]
1162    InvalidSignature([u8; 4]),
1163
1164    /// The icons payload declares an icon size other than raygui's compile-time
1165    /// `RAYGUI_ICON_SIZE` (= 16).
1166    ///
1167    /// **Cause:** The file targets a raygui build with a different icon-size
1168    /// configuration; raygui's `GuiDrawIcon` hard-codes 16x16, so loading
1169    /// non-16 icons would render garbage.
1170    ///
1171    /// **Recovery:** Re-export the icons at 16x16, or rebuild raygui with a
1172    /// matching `RAYGUI_ICON_SIZE`.
1173    #[error("unsupported icon size: expected {expected}, got {actual}")]
1174    UnsupportedIconSize {
1175        /// raygui's compile-time icon size (currently 16).
1176        expected: u16,
1177        /// The icon size declared in the loaded file.
1178        actual: u16,
1179    },
1180
1181    /// The icons payload declares more than raygui's compile-time
1182    /// `RAYGUI_ICON_MAX_ICONS` (= 256) icons.
1183    ///
1184    /// **Cause:** A `.rgi` file targeting a raygui build with a higher icon-count
1185    /// limit.
1186    ///
1187    /// **Recovery:** Trim the icons file to ≤256 entries, or rebuild raygui with
1188    /// a higher limit.
1189    #[error("too many icons: max {max}, got {actual}")]
1190    TooManyIcons {
1191        /// raygui's compile-time max (currently 256).
1192        max: u16,
1193        /// The icon count declared in the loaded file.
1194        actual: u16,
1195    },
1196
1197    /// The in-memory data length doesn't fit in `i32` (raygui's parameter type).
1198    ///
1199    /// **Cause:** A buffer larger than ~2 GiB was passed to a `_from_memory` variant.
1200    ///
1201    /// **Recovery:** Slice the buffer to a reasonable size; `.rgi` payloads are
1202    /// kilobytes in practice.
1203    #[error("data length {0} overflows i32")]
1204    LengthOverflow(usize),
1205
1206    /// An I/O error occurred while reading the icons file.
1207    ///
1208    /// **Cause:** Permission denied, mid-read failure, etc. — anything `std::io::Error`
1209    /// reports beyond `NotFound` (which surfaces as [`Self::FileNotFound`]).
1210    ///
1211    /// **Recovery:** Inspect the wrapped error.
1212    #[error(transparent)]
1213    Io(#[from] std::io::Error),
1214}
1215
1216/// Errors that can occur while loading a raygui `.rgs` style file from memory.
1217///
1218/// raygui's `GuiLoadStyleFromMemory` does not signal parse errors — only the
1219/// i32 length-overflow check is surfaced here. Bad style payloads silently
1220/// no-op (same upstream wart as the file-based `gui_load_style`).
1221#[derive(Debug, thiserror::Error)]
1222pub enum LoadStyleFromMemoryError {
1223    /// The in-memory data length doesn't fit in `i32` (raygui's parameter type).
1224    ///
1225    /// **Cause:** A buffer larger than ~2 GiB.
1226    ///
1227    /// **Recovery:** Slice the buffer; `.rgs` style payloads are kilobytes.
1228    #[error("data length {0} overflows i32")]
1229    LengthOverflow(usize),
1230}
1231
1232/// Error returned when installing a process-global callback whose slot is already occupied.
1233///
1234/// Each callback slot in [`crate::core::callbacks`] ([`set_save_file_data_callback`],
1235/// [`set_load_file_data_callback`], [`set_save_file_text_callback`],
1236/// [`set_load_file_text_callback`]) holds at most one function at a time. Calling a setter
1237/// while its slot is occupied returns this error rather than silently overwriting. The inner
1238/// `&'static str` names which callback kind was already set (e.g. `"save file data"`).
1239///
1240/// **Cause:** A previous call to the same setter installed a callback that has not been
1241/// removed.
1242///
1243/// **Recovery:** Keep a single registration site per callback kind, or remove the existing
1244/// callback (where an unset function exists) before installing a new one.
1245///
1246/// # Examples
1247///
1248/// ```no_run
1249/// use raylib::prelude::*;
1250/// use raylib::core::callbacks::set_save_file_data_callback;
1251///
1252/// fn writer(_path: &str, _bytes: &[u8]) -> bool { true }
1253/// set_save_file_data_callback(writer).expect("first install");
1254/// match set_save_file_data_callback(writer) {
1255///     Err(e) => eprintln!("{e}"), // "there is a save file data callback already set"
1256///     Ok(()) => unreachable!(),
1257/// }
1258/// ```
1259///
1260/// [`set_save_file_data_callback`]: crate::core::callbacks::set_save_file_data_callback
1261/// [`set_load_file_data_callback`]: crate::core::callbacks::set_load_file_data_callback
1262/// [`set_save_file_text_callback`]: crate::core::callbacks::set_save_file_text_callback
1263/// [`set_load_file_text_callback`]: crate::core::callbacks::set_load_file_text_callback
1264#[derive(Error, Debug)]
1265#[error("there is a {0} callback already set")]
1266pub struct SetCallbackError(pub(crate) &'static str);
1267
1268#[cfg(test)]
1269mod load_icons_error_tests {
1270    use super::*;
1271    use std::path::PathBuf;
1272
1273    #[test]
1274    fn load_icons_error_variants_display() {
1275        let err = LoadIconsError::FileNotFound(PathBuf::from("a.rgi"));
1276        assert!(err.to_string().contains("a.rgi"));
1277        let err = LoadIconsError::HeaderTruncated(7);
1278        assert_eq!(
1279            err.to_string(),
1280            "icons file too short: need >=12 bytes for header, got 7"
1281        );
1282        let err = LoadIconsError::InvalidSignature(*b"XXXX");
1283        assert!(err.to_string().contains("expected 'rGI '"));
1284        let err = LoadIconsError::UnsupportedIconSize {
1285            expected: 16,
1286            actual: 32,
1287        };
1288        assert_eq!(
1289            err.to_string(),
1290            "unsupported icon size: expected 16, got 32"
1291        );
1292        let err = LoadIconsError::TooManyIcons {
1293            max: 256,
1294            actual: 300,
1295        };
1296        assert_eq!(err.to_string(), "too many icons: max 256, got 300");
1297        let err = LoadIconsError::LengthOverflow(usize::MAX);
1298        assert!(err.to_string().contains("overflows i32"));
1299    }
1300
1301    #[test]
1302    fn load_style_from_memory_error_display() {
1303        let err = LoadStyleFromMemoryError::LengthOverflow(usize::MAX);
1304        assert!(err.to_string().contains("overflows i32"));
1305    }
1306
1307    #[test]
1308    fn load_icons_error_io_from_impl() {
1309        let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "denied");
1310        let err: LoadIconsError = io_err.into();
1311        assert!(err.to_string().contains("denied"));
1312        assert!(matches!(err, LoadIconsError::Io(_)));
1313    }
1314}
1315
1316#[cfg(test)]
1317mod set_callback_error_tests {
1318    use super::*;
1319
1320    #[test]
1321    fn display_names_the_occupied_slot() {
1322        let e = SetCallbackError("save file data");
1323        assert_eq!(
1324            e.to_string(),
1325            "there is a save file data callback already set"
1326        );
1327    }
1328}
1329
1330#[cfg(test)]
1331mod raylib_error_from_tests {
1332    use super::*;
1333    use crate::consts::PixelFormat;
1334    use crate::core::pixel::PixelColorError;
1335
1336    #[test]
1337    fn all_new_leaf_errors_convert_via_from() {
1338        assert!(matches!(
1339            RaylibError::from(UpdateAudioStreamError::CallbackSlotBusy),
1340            RaylibError::UpdateAudioStream(_)
1341        ));
1342        assert!(matches!(
1343            RaylibError::from(InvalidMeshError::TrianglePointMiscount),
1344            RaylibError::InvalidMesh(_)
1345        ));
1346        assert!(matches!(
1347            RaylibError::from(GenMeshError::InvalidMesh(
1348                InvalidMeshError::TrianglePointMiscount
1349            )),
1350            RaylibError::GenMesh(_)
1351        ));
1352        assert!(matches!(
1353            RaylibError::from(Base64Error::DecodeFailed),
1354            RaylibError::Base64(_)
1355        ));
1356        assert!(matches!(
1357            RaylibError::from(LoadIconsError::HeaderTruncated(3)),
1358            RaylibError::LoadIcons(_)
1359        ));
1360        assert!(matches!(
1361            RaylibError::from(LoadStyleFromMemoryError::LengthOverflow(5)),
1362            RaylibError::LoadStyleFromMemory(_)
1363        ));
1364        assert!(matches!(
1365            RaylibError::from(PixelColorError::CompressedFormat(
1366                PixelFormat::PIXELFORMAT_COMPRESSED_DXT1_RGB
1367            )),
1368            RaylibError::PixelColor(_)
1369        ));
1370        assert!(matches!(
1371            RaylibError::from(SetCallbackError("trace log")),
1372            RaylibError::SetCallback(_)
1373        ));
1374    }
1375}