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