Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
19 changes: 18 additions & 1 deletion lib/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -1053,7 +1053,15 @@ class Node extends rclnodejs.ShadowNode {
* @return {Array<{name: string, namespace: string}>} An array of the names and namespaces.
*/
getNodeNamesAndNamespaces() {
return rclnodejs.getNodeNames(this.handle);
return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ false);
}

/**
* Get the list of nodes and their namespaces with enclaves discovered by the provided node.
* @return {Array<{name: string, namespace: string, enclave: string}>} An array of the names, namespaces and enclaves.
*/
getNodeNamesAndNamespacesWithEnclaves() {
return rclnodejs.getNodeNames(this.handle, /*getEnclaves=*/ true);
}

/**
Expand Down Expand Up @@ -1610,6 +1618,15 @@ class Node extends rclnodejs.ShadowNode {
}
}

/**
* Get the fully qualified name of the node.
*
* @returns {string} - Return String containing the fully qualified name of the node.
Copy link

Copilot AI May 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The JSDoc comment for getFullyQualifiedName can be simplified for clarity. For example:

/**
 * @returns {string} Fully qualified node name.
 */
Suggested change
* Get the fully qualified name of the node.
*
* @returns {string} - Return String containing the fully qualified name of the node.
* @returns {string} Fully qualified node name.

Copilot uses AI. Check for mistakes.
*/
getFullyQualifiedName() {
return rclnodejs.getFullyQualifiedName(this.handle);
}

// returns on 1st error or result {successful, reason}
_validateParameters(parameters = [], declareParameterMode = false) {
for (const parameter of parameters) {
Expand Down
45 changes: 37 additions & 8 deletions src/rcl_node_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -385,40 +385,67 @@ Napi::Value GetNodeNames(const Napi::CallbackInfo& info) {

Copy link

Copilot AI May 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validate info.Length() >= 2 and that info[1] is a Boolean before accessing it to prevent out-of-bounds or type errors in the binding.

Suggested change
if (info.Length() < 2 || !info[1].IsBoolean()) {
Napi::TypeError::New(env, "Expected at least two arguments, with the second being a Boolean.")
.ThrowAsJavaScriptException();
return env.Null();
}

Copilot uses AI. Check for mistakes.
RclHandle* node_handle = RclHandle::Unwrap(info[0].As<Napi::Object>());
rcl_node_t* node = reinterpret_cast<rcl_node_t*>(node_handle->ptr());
bool get_enclaves = info[1].As<Napi::Boolean>().Value();
rcutils_string_array_t node_names =
rcutils_get_zero_initialized_string_array();
rcutils_string_array_t node_namespaces =
rcutils_get_zero_initialized_string_array();
rcutils_string_array_t enclaves = rcutils_get_zero_initialized_string_array();
rcl_allocator_t allocator = rcl_get_default_allocator();

THROW_ERROR_IF_NOT_EQUAL(
RCL_RET_OK,
rcl_get_node_names(node, allocator, &node_names, &node_namespaces),
"Failed to get_node_names.");
if (get_enclaves) {
Copy link

Copilot AI May 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The error message used here is generic ("Failed to get_node_names."). Consider updating it to something like "Failed to get node names with enclaves." to clarify which branch failed.

Copilot uses AI. Check for mistakes.
THROW_ERROR_IF_NOT_EQUAL(
RCL_RET_OK,
rcl_get_node_names_with_enclaves(node, allocator, &node_names,
&node_namespaces, &enclaves),
"Failed to get_node_names.");
} else {
THROW_ERROR_IF_NOT_EQUAL(
RCL_RET_OK,
rcl_get_node_names(node, allocator, &node_names, &node_namespaces),
"Failed to get_node_names.");
}

Napi::Array result_list = Napi::Array::New(env, node_names.size);

for (size_t i = 0; i < node_names.size; ++i) {
Napi::Object item = Napi::Object::New(env);

item.Set("name", Napi::String::New(env, node_names.data[i]));
item.Set("namespace", Napi::String::New(env, node_namespaces.data[i]));

if (get_enclaves) {
item.Set("enclave", Napi::String::New(env, enclaves.data[i]));
}
result_list.Set(i, item);
}

rcutils_ret_t fini_names_ret = rcutils_string_array_fini(&node_names);
rcutils_ret_t fini_namespaces_ret =
rcutils_string_array_fini(&node_namespaces);

rcutils_ret_t fini_enclaves_ret = rcutils_string_array_fini(&enclaves);
THROW_ERROR_IF_NOT_EQUAL(RCL_RET_OK, fini_names_ret,
"Failed to destroy node_names");
THROW_ERROR_IF_NOT_EQUAL(RCL_RET_OK, fini_namespaces_ret,
"Failed to destroy node_namespaces");

THROW_ERROR_IF_NOT_EQUAL(RCL_RET_OK, fini_enclaves_ret,
"Failed to fini enclaves string array");
return result_list;
}

Napi::Value GetFullyQualifiedName(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();

RclHandle* node_handle = RclHandle::Unwrap(info[0].As<Napi::Object>());
rcl_node_t* node = reinterpret_cast<rcl_node_t*>(node_handle->ptr());
const char* fully_qualified_node_name =
rcl_node_get_fully_qualified_name(node);
if (!fully_qualified_node_name) {
Napi::Error::New(env, "Fully qualified name not set")
.ThrowAsJavaScriptException();
return env.Undefined();
}
return Napi::String::New(env, fully_qualified_node_name);
}

Napi::Object InitNodeBindings(Napi::Env env, Napi::Object exports) {
exports.Set("getParameterOverrides",
Napi::Function::New(env, GetParameterOverrides));
Expand All @@ -439,6 +466,8 @@ Napi::Object InitNodeBindings(Napi::Env env, Napi::Object exports) {
exports.Set("countServices", Napi::Function::New(env, CountServices));
#endif
exports.Set("getNodeNames", Napi::Function::New(env, GetNodeNames));
exports.Set("getFullyQualifiedName",
Napi::Function::New(env, GetFullyQualifiedName));
return exports;
}

Expand Down
22 changes: 22 additions & 0 deletions test/test-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ describe('rclnodejs node test suite', function () {
assert.deepStrictEqual(node.namespace(), '/ns');
});

it('Get fully qualified name', function () {
let nodeName = 'example_node_with_ns',
nodeNamespace = '/ns';

var node = rclnodejs.createNode(nodeName, nodeNamespace);
assert.deepStrictEqual(
node.getFullyQualifiedName(),
'/ns/example_node_with_ns'
);
});

it('Try creating a node with the empty namespace', function () {
let nodeName = 'example_node_with_empty_ns',
nodeNamespace = '';
Expand Down Expand Up @@ -398,6 +409,17 @@ describe('rcl node methods testing', function () {
assert.strictEqual(currentNode.namespace, '/my_ns');
});

it('node.getNodeNamesAndNamespacesWithEnclaves', function () {
var nodeNames = node.getNodeNamesAndNamespacesWithEnclaves();
var currentNode = nodeNames.find(function (nodeName) {
return nodeName.name === 'my_node';
});

assert.strictEqual(currentNode.name, 'my_node');
assert.strictEqual(currentNode.namespace, '/my_ns');
assert.strictEqual(currentNode.enclave, '/');
});

it('node.countPublishers', async () => {
assert.strictEqual(node.countPublishers('chatter'), 0);

Expand Down
4 changes: 4 additions & 0 deletions test/types/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ expectType<rclnodejs.NamesAndTypesQueryResult[]>(
expectType<rclnodejs.NamesAndTypesQueryResult[]>(node.getTopicNamesAndTypes());
expectType<string[]>(node.getNodeNames());
expectType<rclnodejs.NodeNamesQueryResult[]>(node.getNodeNamesAndNamespaces());
expectType<rclnodejs.NodeNamesQueryResultWithEnclaves[]>(
node.getNodeNamesAndNamespacesEnclaves()
Copy link

Copilot AI May 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method name mismatch: the implementation and JS wrapper use getNodeNamesAndNamespacesWithEnclaves(), so this test should call that exact name.

Suggested change
node.getNodeNamesAndNamespacesEnclaves()
node.getNodeNamesAndNamespacesWithEnclaves()

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI May 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test calls getNodeNamesAndNamespacesEnclaves(), but the actual method is named getNodeNamesAndNamespacesWithEnclaves(). Update the test to use the correct method name.

Suggested change
node.getNodeNamesAndNamespacesEnclaves()
node.getNodeNamesAndNamespacesWithEnclaves()

Copilot uses AI. Check for mistakes.
);
expectType<Array<object>>(node.getPublishersInfoByTopic('topic', false));
expectType<Array<object>>(node.getSubscriptionsInfoByTopic('topic', false));
expectType<number>(node.countPublishers(TOPIC));
Expand All @@ -76,6 +79,7 @@ expectType<number>(node.countServices(SERVICE_NAME));
expectType<rclnodejs.Options<string | rclnodejs.QoS>>(
rclnodejs.Node.getDefaultOptions()
);
expectType<string>(node.getFullyQualifiedName());

// ---- LifecycleNode ----
const lifecycleNode = rclnodejs.createLifecycleNode(LIFECYCLE_NODE_NAME);
Expand Down
32 changes: 32 additions & 0 deletions types/node.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,24 @@ declare module 'rclnodejs' {
namespace: string;
};

/**
* Result of Node.getNodeNames() query
*
* @example
* ```
* [
* { name: 'gazebo', namespace: '/', enclave: '/'},
* { name: 'robot_state_publisher', namespace: '/', enclave: '/' },
* { name: 'cam2image', namespace: '/demo' , enclave: '/'}
* ]
* ```
*/
type NodeNamesQueryResultWithEnclaves = {
name: string;
namespace: string;
enclave: string;
};

/**
* Node is the primary entrypoint in a ROS system for communication.
* It can be used to create ROS entities such as publishers, subscribers,
Expand Down Expand Up @@ -769,6 +787,13 @@ declare module 'rclnodejs' {
*/
getNodeNamesAndNamespaces(): Array<NodeNamesQueryResult>;

/**
* Get the list of nodes and their namespaces with enclaves discovered by the provided node.
*
* @returns An array of the names, namespaces and enclaves.
*/
getNodeNamesAndNamespacesEnclaves(): Array<NodeNamesQueryResultWithEnclaves>;

/**
* Return the number of publishers on a given topic.
* @param topic - The name of the topic.
Expand Down Expand Up @@ -796,5 +821,12 @@ declare module 'rclnodejs' {
* @returns Number of services.
*/
countServices(serviceName: string): number;

/**
* Get the fully qualified name of the node.
*
* @returns String containing the fully qualified name of the node.
*/
getFullyQualifiedName(): string;
}
}
Loading