Skip to main content

mujoco_rs/
error.rs

1//! Error types for MuJoCo-rs operations.
2//!
3//! - [`MjDataError`] - physics data, view-signature, and Jacobian operations.
4//! - [`MjSceneError`] - 3-D scene and visualization operations (`MjvScene`).
5//! - [`MjrContextError`] - GPU rendering-context operations (`MjrContext`).
6//! - [`MjEditError`] - model-specification editing operations (`MjSpec`).
7//! - [`MjModelError`] - model loading, saving, and state operations (`MjModel`).
8//! - [`MjVfsError`] - virtual file system operations (`MjVfs`).
9//! - [`MjPluginError`] - plugin library loading operations.
10//! - [`GlInitError`] - OpenGL / window initialization (feature-gated).
11use std::fmt;
12
13/// Errors that can occur in [`MjData`](crate::wrappers::MjData) physics data
14/// and Jacobian operations.
15#[derive(Debug, Clone, PartialEq, Eq)]
16#[non_exhaustive]
17pub enum MjDataError {
18    /// A provided object or body index was out of the valid range.
19    IndexOutOfBounds {
20        /// Name of the index parameter, e.g. `"body_id"` or `"geom_id"`.
21        kind: &'static str,
22        /// The index value that was passed.
23        id: usize,
24        /// Exclusive upper bound of the valid range.
25        upper: usize,
26    },
27    /// The provided MuJoCo object type is not supported by this operation.
28    ///
29    /// Contains the raw MuJoCo C object-type code (`mjOBJ_*`) that was not recognized.
30    UnsupportedObjectType(i32),
31    /// MuJoCo failed to allocate the requested structure.
32    AllocationFailed,
33    /// A buffer passed to the operation is too small for the required data.
34    BufferTooSmall {
35        /// Descriptive name of the buffer (e.g. `"destination"`, `"rgb"`).
36        name: &'static str,
37        /// Actual length of the buffer that was provided.
38        got: usize,
39        /// Minimum length the buffer must have.
40        needed: usize,
41    },
42    /// A slice or array parameter has the wrong length for the operation.
43    LengthMismatch {
44        /// Descriptive name of the parameter.
45        name: &'static str,
46        /// Expected length.
47        expected: usize,
48        /// Actual length that was provided.
49        got: usize,
50    },
51    /// Two model-signature-bound objects were created from different models.
52    ///
53    /// This is returned by APIs that require matching model signatures,
54    /// including data-copy operations and info-view accessors.
55    SignatureMismatch {
56        /// Model signature of the source object.
57        source: u64,
58        /// Model signature of the destination object.
59        destination: u64,
60    },
61    /// The specified actuator or sensor has no associated history buffer.
62    NoHistoryBuffer {
63        /// `"actuator"` or `"sensor"`.
64        kind: &'static str,
65        /// The zero-based index of the actuator or sensor.
66        id: usize,
67    },
68    /// The contact buffer is full; no more contacts can be added.
69    ContactBufferFull,
70    /// A filesystem path argument contains invalid UTF-8.
71    InvalidUtf8Path,
72}
73
74impl fmt::Display for MjDataError {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        match self {
77            Self::IndexOutOfBounds { kind, id, upper } => {
78                write!(f, "{kind} {id} is out of bounds [0, {upper})")
79            }
80            Self::UnsupportedObjectType(raw) => {
81                write!(f, "object type {raw} is not supported by this operation")
82            }
83            Self::AllocationFailed => {
84                write!(f, "MuJoCo allocation failed")
85            }
86            Self::BufferTooSmall { name, got, needed } => {
87                write!(
88                    f,
89                    "{name} buffer is too small: got {got} elements, \
90                     but need at least {needed}"
91                )
92            }
93            Self::SignatureMismatch { source, destination } => {
94                write!(
95                    f,
96                    "model signature mismatch: source {source:#X}, \
97                     destination {destination:#X}"
98                )
99            }
100            Self::LengthMismatch { name, expected, got } => {
101                write!(
102                    f,
103                    "{name} has wrong length: expected {expected}, got {got}"
104                )
105            }
106            Self::NoHistoryBuffer { kind, id } => {
107                write!(f, "{kind} {id} has no history buffer")
108            }
109            Self::ContactBufferFull => {
110                write!(f, "contact buffer is full")
111            }
112            Self::InvalidUtf8Path => {
113                write!(f, "path contains invalid UTF-8")
114            }
115        }
116    }
117}
118
119impl std::error::Error for MjDataError {}
120
121
122/// Errors that can occur in 3-D scene and visualization operations
123/// ([`MjvScene`](crate::wrappers::MjvScene)).
124#[derive(Debug, Clone, PartialEq, Eq)]
125#[non_exhaustive]
126pub enum MjSceneError {
127    /// No more space is available for new geoms in the scene.
128    ///
129    /// Increase the `max_geom` capacity passed to
130    /// [`MjvScene::new`](crate::wrappers::MjvScene::new).
131    SceneFull {
132        /// The current maximum geom capacity of the scene.
133        capacity: i32,
134    },
135    /// A string label exceeds the fixed-size label buffer of an [`MjvGeom`](crate::wrappers::MjvGeom).
136    LabelTooLong {
137        /// Length of the label in bytes.
138        len: usize,
139        /// Maximum number of bytes (excluding the NUL terminator) the buffer can hold.
140        capacity: usize,
141    },
142    /// The figure's line-data buffer for a given plot is full.
143    FigureBufferFull {
144        /// Index of the plot whose buffer is full.
145        plot_index: usize,
146        /// Maximum number of data points the buffer can hold.
147        capacity: usize,
148    },
149    /// A point index is out of range for the current data in a figure plot.
150    FigureIndexOutOfBounds {
151        /// Index of the plot.
152        plot_index: usize,
153        /// The point index that was provided.
154        point_index: usize,
155        /// Current number of data points in the plot.
156        current_len: usize,
157    },
158    /// A plot index is out of the valid range `[0, mjMAXLINE)`.
159    InvalidPlotIndex {
160        /// The out-of-range plot index that was provided.
161        plot_index: usize,
162        /// Maximum number of plots (`mjMAXLINE`).
163        max_plots: usize,
164    },
165    /// A geom label string contains non-ASCII bytes.
166    ///
167    /// MuJoCo's renderer treats the label buffer as ASCII; multi-byte UTF-8
168    /// sequences would be rendered as garbage characters.
169    NonAsciiLabel,
170    /// An integer value does not correspond to any known camera type variant.
171    InvalidCameraType(i32),
172}
173
174impl fmt::Display for MjSceneError {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        match self {
177            Self::SceneFull { capacity } => {
178                write!(
179                    f,
180                    "scene geom buffer is full (capacity = {capacity}); \
181                     increase max_geom when constructing MjvScene"
182                )
183            }
184            Self::LabelTooLong { len, capacity } => {
185                write!(
186                    f,
187                    "label of {len} bytes exceeds the fixed buffer capacity of {capacity} bytes"
188                )
189            }
190            Self::FigureBufferFull { plot_index, capacity } => {
191                write!(
192                    f,
193                    "figure plot {plot_index} buffer is full \
194                     (capacity = {capacity} data points)"
195                )
196            }
197            Self::FigureIndexOutOfBounds { plot_index, point_index, current_len } => {
198                write!(
199                    f,
200                    "point index {point_index} is out of bounds for plot {plot_index} \
201                     (current length = {current_len})"
202                )
203            }
204            Self::InvalidPlotIndex { plot_index, max_plots } => {
205                write!(
206                    f,
207                    "plot index {plot_index} is out of range [0, {max_plots})"
208                )
209            }
210            Self::NonAsciiLabel => {
211                write!(f, "label contains non-ASCII characters")
212            }
213            Self::InvalidCameraType(raw) => {
214                write!(f, "unknown camera type {raw}")
215            }
216        }
217    }
218}
219
220impl std::error::Error for MjSceneError {}
221
222
223/// Errors that can occur in GPU rendering-context ([`MjrContext`](crate::wrappers::MjrContext)) operations.
224#[derive(Debug, Clone, PartialEq, Eq)]
225#[non_exhaustive]
226pub enum MjrContextError {
227    /// An index passed to a rendering-context operation is out of the valid range.
228    IndexOutOfBounds {
229        /// The index that was supplied.
230        id: usize,
231        /// Exclusive upper bound of the valid range.
232        len: usize,
233    },
234    /// A viewport has invalid (negative) dimensions.
235    InvalidViewport {
236        /// The viewport width that was provided.
237        width: i32,
238        /// The viewport height that was provided.
239        height: i32,
240    },
241    /// A pixel buffer passed to a rendering-context operation is too small.
242    BufferTooSmall {
243        /// Descriptive name of the buffer (e.g. `"rgb"`, `"depth"`).
244        name: &'static str,
245        /// Actual length of the buffer that was provided.
246        got: usize,
247        /// Minimum length the buffer must have.
248        needed: usize,
249    },
250}
251
252impl fmt::Display for MjrContextError {
253    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254        match self {
255            Self::IndexOutOfBounds { id, len } => {
256                write!(f, "index {id} is out of range [0, {len})")
257            }
258            Self::InvalidViewport { width, height } => {
259                write!(
260                    f,
261                    "viewport dimensions must be non-negative, got {width}x{height}"
262                )
263            }
264            Self::BufferTooSmall { name, got, needed } => {
265                write!(
266                    f,
267                    "{name} buffer is too small: got {got} elements, \
268                     but need at least {needed}"
269                )
270            }
271        }
272    }
273}
274
275impl std::error::Error for MjrContextError {}
276
277
278/// Errors that can occur in model-specification editing operations
279/// ([`MjSpec`](crate::wrappers::mj_editing::MjSpec) and related types).
280#[derive(Debug, Clone, PartialEq, Eq)]
281#[non_exhaustive]
282pub enum MjEditError {
283    /// MuJoCo failed to allocate the requested model element.
284    AllocationFailed,
285    /// A filesystem path argument contains invalid UTF-8.
286    InvalidUtf8Path,
287    /// MuJoCo failed to parse the XML (or other format) input.
288    ParseFailed(String),
289    /// MuJoCo failed to compile the spec into a model.
290    CompileFailed(String),
291    /// MuJoCo failed to save the spec to XML.
292    SaveFailed(String),
293    /// A referenced element (e.g. parent default class) was not found.
294    NotFound,
295    /// An element with the same name already exists.
296    AlreadyExists,
297    /// This operation is not supported for the current element.
298    UnsupportedOperation,
299    /// MuJoCo returned an error while attempting to delete the element.
300    DeleteFailed(String),
301    /// The output buffer passed to [`MjSpec::save_xml_string`](crate::wrappers::mj_editing::MjSpec::save_xml_string)
302    /// was too small to hold the XML.
303    ///
304    /// `required_size` follows `snprintf`-style semantics: it is the number of bytes MuJoCo would
305    /// write, **not** counting the NUL terminator.  To retry successfully, pass a buffer of at
306    /// least `required_size + 1` bytes.
307    XmlBufferTooSmall {
308        /// Number of bytes MuJoCo would write (excluding the NUL terminator).
309        /// Pass a buffer of at least `required_size + 1` bytes to retry.
310        required_size: usize,
311    },
312    /// Returned when the parameters to the given method/function are invalid.
313    InvalidParameter(String),
314    /// The given index is out of range.
315    IndexOutOfBounds {
316        /// The index that was supplied.
317        id: usize,
318        /// Exclusive upper bound of the valid range.
319        len: usize,
320    },
321}
322
323impl fmt::Display for MjEditError {
324    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
325        match self {
326            Self::AllocationFailed => write!(f, "MuJoCo failed to allocate the model element"),
327            Self::InvalidUtf8Path => write!(f, "path contains invalid UTF-8"),
328            Self::ParseFailed(msg) => write!(f, "parse failed: {msg}"),
329            Self::CompileFailed(msg) => write!(f, "compilation failed: {msg}"),
330            Self::SaveFailed(msg) => write!(f, "save failed: {msg}"),
331            Self::NotFound => write!(f, "referenced element not found"),
332            Self::AlreadyExists => write!(f, "element with the same name already exists"),
333            Self::UnsupportedOperation => write!(f, "this operation is not supported"),
334            Self::DeleteFailed(msg) => write!(f, "delete failed: {msg}"),
335            Self::XmlBufferTooSmall { required_size } => write!(
336                f,
337                "XML output buffer too small; retry with at least {} bytes",
338                required_size + 1
339            ),
340            Self::InvalidParameter(msg) => write!(f, "{msg}"),
341            Self::IndexOutOfBounds { id, len } => write!(
342                f,
343                "index {id} is out of bounds (length is {len})"
344            ),
345        }
346    }
347}
348
349impl std::error::Error for MjEditError {}
350
351
352/// Errors that can occur in [`MjModel`](crate::wrappers::MjModel) operations.
353#[derive(Debug, Clone, PartialEq, Eq)]
354#[non_exhaustive]
355pub enum MjModelError {
356    /// A filesystem path argument contains invalid UTF-8.
357    InvalidUtf8Path,
358    /// MuJoCo failed to load the model from a file, string, or buffer.
359    LoadFailed(String),
360    /// MuJoCo failed to save the model XML.
361    SaveFailed(String),
362    /// MuJoCo failed to allocate the requested structure.
363    AllocationFailed,
364    /// The state source slice has the wrong length for the given spec.
365    StateSliceLengthMismatch {
366        /// Expected length.
367        expected: usize,
368        /// Actual length.
369        got: usize,
370    },
371    /// The destination spec is not a subset of the source spec.
372    SpecNotSubset,
373    /// A destination buffer is too small for the operation.
374    BufferTooSmall {
375        /// Minimum number of elements required.
376        needed: usize,
377        /// Actual number of elements available.
378        available: usize,
379    },
380    /// Two model-signature-bound objects were created from different models.
381    SignatureMismatch {
382        /// Model signature of the source object.
383        source: u64,
384        /// Model signature of the destination object.
385        destination: u64,
386    },
387    /// A virtual-file-system operation failed while loading a model from a string.
388    VfsError(MjVfsError),
389    /// The given index is out of range.
390    IndexOutOfBounds {
391        /// The index that was supplied.
392        id: usize,
393        /// Exclusive upper bound of the valid range.
394        len: usize,
395    },
396}
397
398impl fmt::Display for MjModelError {
399    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
400        match self {
401            Self::InvalidUtf8Path => write!(f, "path contains invalid UTF-8"),
402            Self::LoadFailed(msg) => write!(f, "model load failed: {msg}"),
403            Self::SaveFailed(msg) => write!(f, "model save failed: {msg}"),
404            Self::AllocationFailed => write!(f, "MuJoCo failed to allocate the requested structure"),
405            Self::StateSliceLengthMismatch { expected, got } => {
406                write!(f, "state slice length mismatch: expected {expected}, got {got}")
407            }
408            Self::SpecNotSubset => {
409                write!(f, "dst_spec must be a subset of src_spec")
410            }
411            Self::BufferTooSmall { needed, available } => {
412                write!(
413                    f,
414                    "buffer is too small: got {available} elements, \
415                     but need at least {needed}"
416                )
417            }
418            Self::SignatureMismatch { source, destination } => {
419                write!(
420                    f,
421                    "model signature mismatch: source {source:#X}, \
422                     destination {destination:#X}"
423                )
424            }
425            Self::VfsError(e) => write!(f, "VFS error: {e}"),
426            Self::IndexOutOfBounds { id, len } => write!(
427                f,
428                "index {id} is out of bounds (length is {len})"
429            ),
430        }
431    }
432}
433
434impl std::error::Error for MjModelError {
435    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
436        match self {
437            Self::VfsError(e) => Some(e),
438            _ => None,
439        }
440    }
441}
442
443impl From<MjVfsError> for MjModelError {
444    fn from(e: MjVfsError) -> Self {
445        Self::VfsError(e)
446    }
447}
448
449
450/// Errors that can occur in virtual file system ([`MjVfs`](crate::wrappers::MjVfs)) operations.
451#[derive(Debug, Clone, PartialEq, Eq)]
452#[non_exhaustive]
453pub enum MjVfsError {
454    /// A file or mount with the same name already exists.
455    AlreadyExists,
456    /// MuJoCo failed to load the file or register the buffer.
457    LoadFailed,
458    /// The specified file or directory was not found in the VFS.
459    NotFound,
460    /// The provided path contains invalid UTF-8.
461    InvalidUtf8Path,
462    /// The buffer length exceeds `i32::MAX` bytes.
463    BufferTooLarge,
464    /// An unrecognized MuJoCo return code.
465    Unknown(i32),
466}
467
468impl fmt::Display for MjVfsError {
469    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
470        match self {
471            Self::AlreadyExists => write!(f, "file already exists in VFS"),
472            Self::LoadFailed => write!(f, "failed to load file into VFS"),
473            Self::NotFound => write!(f, "file not found in VFS"),
474            Self::InvalidUtf8Path => write!(f, "path contains invalid UTF-8"),
475            Self::BufferTooLarge => write!(f, "buffer length exceeds i32::MAX bytes"),
476            Self::Unknown(code) => write!(f, "unknown VFS error (code {code})"),
477        }
478    }
479}
480
481impl std::error::Error for MjVfsError {}
482
483
484/// Errors that can occur when loading MuJoCo plugin libraries.
485#[derive(Debug, Clone, PartialEq, Eq)]
486#[non_exhaustive]
487pub enum MjPluginError {
488    /// The provided path contains invalid UTF-8.
489    InvalidUtf8Path,
490    /// The path string contains an interior null byte.
491    NullBytePath,
492}
493
494impl fmt::Display for MjPluginError {
495    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
496        match self {
497            Self::InvalidUtf8Path => write!(f, "path contains invalid UTF-8"),
498            Self::NullBytePath => write!(f, "path contains an interior null byte"),
499        }
500    }
501}
502
503impl std::error::Error for MjPluginError {}
504
505/// Errors that can occur during OpenGL / window initialization.
506///
507/// Each variant represents a distinct step in the initialization pipeline.
508#[cfg(any(feature = "viewer", feature = "renderer-winit-fallback"))]
509#[derive(Debug, Clone)]
510#[non_exhaustive]
511pub enum GlInitError {
512    /// The windowing / display builder failed to initialize.
513    DisplayBuild(String),
514    /// The display builder succeeded but did not produce a window.
515    NoWindow,
516    /// Failed to obtain the native window handle.
517    WindowHandle(String),
518    /// OpenGL context creation failed. Wraps [`glutin::error::Error`].
519    ContextCreation(glutin::error::Error),
520    /// Window surface attributes could not be constructed.
521    SurfaceAttributes(String),
522    /// Rendering surface creation failed. Wraps [`glutin::error::Error`].
523    SurfaceCreation(glutin::error::Error),
524    /// Making the GL context current on the surface failed. Wraps [`glutin::error::Error`].
525    MakeCurrent(glutin::error::Error),
526}
527
528#[cfg(any(feature = "viewer", feature = "renderer-winit-fallback"))]
529impl fmt::Display for GlInitError {
530    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
531        match self {
532            Self::DisplayBuild(e) => write!(f, "display build failed: {e}"),
533            Self::NoWindow => write!(f, "display builder did not create a window"),
534            Self::WindowHandle(e) => write!(f, "failed to obtain window handle: {e}"),
535            Self::ContextCreation(e) => write!(f, "GL context creation failed: {e}"),
536            Self::SurfaceAttributes(e) => write!(f, "failed to build surface attributes: {e}"),
537            Self::SurfaceCreation(e) => write!(f, "window surface creation failed: {e}"),
538            Self::MakeCurrent(e) => write!(f, "failed to make GL context current: {e}"),
539        }
540    }
541}
542
543#[cfg(any(feature = "viewer", feature = "renderer-winit-fallback"))]
544impl std::error::Error for GlInitError {
545    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
546        match self {
547            Self::ContextCreation(e) |
548            Self::SurfaceCreation(e) |
549            Self::MakeCurrent(e) => Some(e),
550            _ => None,
551        }
552    }
553}