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}