Improve error messages for named/indexed lookups in WASM bindings.
PiperOrigin-RevId: 862682001 Change-Id: I6381af64b724de0ab5aee43cdc89ce463b590cf1
This commit is contained in:
committed by
Copybara-Service
parent
f6796f36b2
commit
59c5017073
@@ -19,6 +19,7 @@
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@@ -27,6 +28,7 @@
|
||||
#include <cstring> // NOLINT
|
||||
#include <memory>
|
||||
#include <optional> // NOLINT
|
||||
#include <sstream>
|
||||
#include <string> // NOLINT
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
@@ -102,6 +104,51 @@ __attribute__((constructor)) void InitMuJoCoErrorHandler() {
|
||||
mju_user_error = ThrowMujocoErrorToJS;
|
||||
}
|
||||
|
||||
// Generates a descriptive error message for when a key lookup fails.
|
||||
// The message includes the invalid name and a list of valid names of the
|
||||
// specified object type currently present in the model.
|
||||
//
|
||||
// Arguments:
|
||||
// model: Pointer to the mjModel.
|
||||
// objtype: The mjOBJ_* enum value representing the object type.
|
||||
// count: The number of objects of the given type in the model.
|
||||
// name: The invalid name that was looked up.
|
||||
//
|
||||
// Returns:
|
||||
// A string containing the error message.
|
||||
std::string KeyErrorMessage(const mjModel* model, int objtype, int count,
|
||||
std::string_view name, std::string_view accessor_name) {
|
||||
std::vector<std::string> valid_names;
|
||||
valid_names.reserve(count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const char* n = mj_id2name(model, objtype, i);
|
||||
if (n) {
|
||||
valid_names.push_back(n);
|
||||
}
|
||||
}
|
||||
std::sort(valid_names.begin(), valid_names.end());
|
||||
|
||||
std::ostringstream message;
|
||||
message << "Invalid name '" << name << "' for " << accessor_name
|
||||
<< ". Valid names: [";
|
||||
for (size_t i = 0; i < valid_names.size(); ++i) {
|
||||
message << "'" << valid_names[i] << "'";
|
||||
if (i < valid_names.size() - 1) {
|
||||
message << ", ";
|
||||
}
|
||||
}
|
||||
message << "]";
|
||||
return message.str();
|
||||
}
|
||||
|
||||
std::string IndexErrorMessage(int index, int count,
|
||||
std::string_view accessor_name) {
|
||||
std::ostringstream message;
|
||||
message << "Invalid index " << index << " for " << accessor_name
|
||||
<< ". Valid indices from 0 to " << count - 1;
|
||||
return message.str();
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
val MakeValArray(const char* (&strings)[N]) {
|
||||
val result = val::array();
|
||||
@@ -5299,13 +5346,13 @@ struct MjModel {
|
||||
if (val.isString()) { \
|
||||
int id = mj_name2id(ptr_, OBJTYPE, val.as<std::string>().c_str()); \
|
||||
if (id == -1) { \
|
||||
mju_error("Invalid name, MjModel." #accessor_name " not found"); \
|
||||
mju_error("%s", KeyErrorMessage(ptr_, OBJTYPE, ptr_->nfield, val.as<std::string>(), #accessor_name).c_str()); \
|
||||
} \
|
||||
return MjModel##Name##Accessor(ptr_, id); \
|
||||
} else if (val.isNumber()) { \
|
||||
int id = val.as<int>(); \
|
||||
if (id < 0 || id >= ptr_->nfield) { \
|
||||
mju_error_i("Invalid id %d for MjModel." #accessor_name, id); \
|
||||
mju_error("%s", IndexErrorMessage(id, ptr_->nfield, #accessor_name).c_str()); \
|
||||
} \
|
||||
return MjModel##Name##Accessor(ptr_, id); \
|
||||
} else { \
|
||||
|
||||
@@ -396,13 +396,13 @@ def build_struct_header(
|
||||
if (val.isString()) { \\
|
||||
int id = mj_name2id(ptr_, OBJTYPE, val.as<std::string>().c_str()); \\
|
||||
if (id == -1) { \\
|
||||
mju_error("Invalid name, MjModel." #accessor_name " not found"); \\
|
||||
mju_error("%s", KeyErrorMessage(ptr_, OBJTYPE, ptr_->nfield, val.as<std::string>(), #accessor_name).c_str()); \\
|
||||
} \\
|
||||
return MjModel##Name##Accessor(ptr_, id); \\
|
||||
} else if (val.isNumber()) { \\
|
||||
int id = val.as<int>(); \\
|
||||
if (id < 0 || id >= ptr_->nfield) { \\
|
||||
mju_error_i("Invalid id %d for MjModel." #accessor_name, id); \\
|
||||
mju_error("%s", IndexErrorMessage(id, ptr_->nfield, #accessor_name).c_str()); \\
|
||||
} \\
|
||||
return MjModel##Name##Accessor(ptr_, id); \\
|
||||
} else { \\
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@@ -27,6 +28,7 @@
|
||||
#include <cstring> // NOLINT
|
||||
#include <memory>
|
||||
#include <optional> // NOLINT
|
||||
#include <sstream>
|
||||
#include <string> // NOLINT
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
@@ -102,6 +104,51 @@ __attribute__((constructor)) void InitMuJoCoErrorHandler() {
|
||||
mju_user_error = ThrowMujocoErrorToJS;
|
||||
}
|
||||
|
||||
// Generates a descriptive error message for when a key lookup fails.
|
||||
// The message includes the invalid name and a list of valid names of the
|
||||
// specified object type currently present in the model.
|
||||
//
|
||||
// Arguments:
|
||||
// model: Pointer to the mjModel.
|
||||
// objtype: The mjOBJ_* enum value representing the object type.
|
||||
// count: The number of objects of the given type in the model.
|
||||
// name: The invalid name that was looked up.
|
||||
//
|
||||
// Returns:
|
||||
// A string containing the error message.
|
||||
std::string KeyErrorMessage(const mjModel* model, int objtype, int count,
|
||||
std::string_view name, std::string_view accessor_name) {
|
||||
std::vector<std::string> valid_names;
|
||||
valid_names.reserve(count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const char* n = mj_id2name(model, objtype, i);
|
||||
if (n) {
|
||||
valid_names.push_back(n);
|
||||
}
|
||||
}
|
||||
std::sort(valid_names.begin(), valid_names.end());
|
||||
|
||||
std::ostringstream message;
|
||||
message << "Invalid name '" << name << "' for " << accessor_name
|
||||
<< ". Valid names: [";
|
||||
for (size_t i = 0; i < valid_names.size(); ++i) {
|
||||
message << "'" << valid_names[i] << "'";
|
||||
if (i < valid_names.size() - 1) {
|
||||
message << ", ";
|
||||
}
|
||||
}
|
||||
message << "]";
|
||||
return message.str();
|
||||
}
|
||||
|
||||
std::string IndexErrorMessage(int index, int count,
|
||||
std::string_view accessor_name) {
|
||||
std::ostringstream message;
|
||||
message << "Invalid index " << index << " for " << accessor_name
|
||||
<< ". Valid indices from 0 to " << count - 1;
|
||||
return message.str();
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
val MakeValArray(const char* (&strings)[N]) {
|
||||
val result = val::array();
|
||||
|
||||
@@ -1879,7 +1879,26 @@ describe('MuJoCo WASM Bindings', () => {
|
||||
it('should throw an error for invalid geom names in model', () => {
|
||||
expect(() => {
|
||||
model!.geom('badgeom');
|
||||
}).toThrowError('MuJoCo Error: Invalid name, MjModel.geom not found');
|
||||
})
|
||||
.toThrowError(
|
||||
`MuJoCo Error: Invalid name 'badgeom' for geom. Valid names: \['mybox', 'myplane'\]`);
|
||||
});
|
||||
|
||||
// Corresponds to
|
||||
// bindings_test.py:test_named_indexing_invalid_index_in_model
|
||||
it('should throw an error for invalid geom indices in model', () => {
|
||||
const numGeoms = model!.ngeom;
|
||||
expect(() => {
|
||||
model!.geom(numGeoms);
|
||||
})
|
||||
.toThrowError(
|
||||
`MuJoCo Error: Invalid index 3 for geom. Valid indices from 0 to 2`);
|
||||
|
||||
expect(() => {
|
||||
model!.geom(-1);
|
||||
})
|
||||
.toThrowError(
|
||||
`MuJoCo Error: Invalid index -1 for geom. Valid indices from 0 to 2`);
|
||||
});
|
||||
|
||||
// Corresponds to bindings_test.py:test_named_indexing_geom_size
|
||||
|
||||
Reference in New Issue
Block a user