Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions include/behaviortree_cpp/basic_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,9 @@ struct Timestamp
/// and control characters (ASCII 0-31, 127). UTF-8 multibyte sequences are allowed.
[[nodiscard]] char findForbiddenChar(StringView name);

/// Describes a character returned by findForbiddenChar() for an error message.
[[nodiscard]] std::string formatForbiddenChar(char c);

class TypeInfo
{
public:
Expand Down Expand Up @@ -468,6 +471,11 @@ template <typename T = AnyTypeAllowed>
auto sname = static_cast<std::string>(name);
if(!IsAllowedPortName(sname))
{
if(const char c = findForbiddenChar(sname); c != '\0')
{
throw RuntimeError("Port name '", sname, "' contains forbidden character ",
formatForbiddenChar(c));
}
throw RuntimeError("The name of a port must not be `name` or `ID` "
"and must start with an alphabetic character. "
"Underscore is reserved.");
Expand Down
9 changes: 9 additions & 0 deletions src/basic_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,15 @@ char findForbiddenChar(StringView name)
return '\0';
}

std::string formatForbiddenChar(char c)
{
if(c < 32 || c == 127)
{
return "control character (ASCII " + std::to_string(static_cast<int>(c)) + ")";
}
return std::string("'") + c + "'";
}

Any convertFromJSON(StringView json_text, std::type_index type)
{
const nlohmann::json json = nlohmann::json::parse(json_text);
Expand Down
10 changes: 0 additions & 10 deletions src/xml_parsing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,6 @@ auto StrEqual = [](const char* str1, const char* str2) -> bool {
return strcmp(str1, str2) == 0;
};

// Helper to format forbidden character for error messages
std::string formatForbiddenChar(char c)
{
if(c < 32 || c == 127)
{
return "control character (ASCII " + std::to_string(static_cast<int>(c)) + ")";
}
return std::string("'") + c + "'";
}

void validateModelName(const std::string& name, int line_number)
{
const auto line_str = std::to_string(line_number);
Expand Down
26 changes: 26 additions & 0 deletions tests/gtest_name_validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,32 @@ TEST(NameValidation, IsAllowedPortName_Invalid)
EXPECT_FALSE(IsAllowedPortName("port<T>")); // angle brackets
}

TEST(NameValidation, CreatePort_NamesForbiddenChar)
{
try
{
(void)InputPort<std::string>("goal.pose");
FAIL() << "Expected RuntimeError to be thrown";
}
catch(const RuntimeError& e)
{
const std::string msg = e.what();
EXPECT_NE(msg.find("goal.pose"), std::string::npos) << msg;
EXPECT_NE(msg.find("forbidden character '.'"), std::string::npos) << msg;
}

try
{
(void)InputPort<std::string>("goal\tpose");
FAIL() << "Expected RuntimeError to be thrown";
}
catch(const RuntimeError& e)
{
const std::string msg = e.what();
EXPECT_NE(msg.find("control character (ASCII 9)"), std::string::npos) << msg;
}
}

// ============== Tests for XML parsing validation ==============

class NameValidationXMLTest : public testing::Test
Expand Down
Loading