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}