Skip to content
Merged
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
70 changes: 63 additions & 7 deletions lib/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ let validator = {

/**
* Validate a given topic or service name, and throw an error if invalid.
* @param {string} topic - The name of topic/service. and it must be fully-qualified and already expanded.
* @return {boolean} - True if it is valid.
* @param {string} topic - The name of topic/service. Must be fully-qualified and already expanded.
* @returns {true} Always returns true if valid.
* @throws {TypeValidationError} If topic is not a string.
* @throws {NameValidationError} If the topic name is invalid.
*/
validateFullTopicName(topic) {
if (typeof topic !== 'string') {
Expand All @@ -43,10 +45,24 @@ let validator = {
throw this._createErrorFromValidation(result, topic, 'topic');
},

/**
* Check if a fully-qualified topic name is valid without throwing.
* @param {string} topic - The name of topic/service. Must be fully-qualified and already expanded.
* @returns {boolean} True if valid, false otherwise.
*/
isValidFullTopicName(topic) {
if (typeof topic !== 'string') {
return false;
}
return rclnodejs.validateFullTopicName(topic) === null;
},

/**
* Validate a given node name, and throw an error if invalid.
* @param {string} name - The name of node.
* @return {boolean} - True if it is valid.
* @returns {true} Always returns true if valid.
* @throws {TypeValidationError} If name is not a string.
* @throws {NameValidationError} If the node name is invalid.
*/
validateNodeName(name) {
if (typeof name !== 'string') {
Expand All @@ -60,10 +76,24 @@ let validator = {
throw this._createErrorFromValidation(result, name, 'node');
},

/**
* Check if a node name is valid without throwing.
* @param {string} name - The name of node.
* @returns {boolean} True if valid, false otherwise.
*/
isValidNodeName(name) {
if (typeof name !== 'string') {
return false;
}
return rclnodejs.validateNodeName(name) === null;
},

/**
* Validate a given topic or service name, and throw an error if invalid.
* @param {string} topic - The name of topic/service and does not have to be fully-qualified and is not expanded.
* @return {boolean} - True if it is valid.
* @param {string} topic - The name of topic/service. Does not have to be fully-qualified.
* @returns {true} Always returns true if valid.
* @throws {TypeValidationError} If topic is not a string.
* @throws {NameValidationError} If the topic name is invalid.
*/
validateTopicName(topic) {
if (typeof topic !== 'string') {
Expand All @@ -77,10 +107,24 @@ let validator = {
throw this._createErrorFromValidation(result, topic, 'topic');
},

/**
* Check if a topic name is valid without throwing.
* @param {string} topic - The name of topic/service. Does not have to be fully-qualified.
* @returns {boolean} True if valid, false otherwise.
*/
isValidTopicName(topic) {
if (typeof topic !== 'string') {
return false;
}
return rclnodejs.validateTopicName(topic) === null;
},

/**
* Validate a given namespace, and throw an error if invalid.
* @param {string} namespace - The namespace to be validated
* @return {boolean} - True if it is valid.
* @param {string} namespace - The namespace to be validated.
* @returns {true} Always returns true if valid.
* @throws {TypeValidationError} If namespace is not a string.
* @throws {NameValidationError} If the namespace is invalid.
*/
validateNamespace(namespace) {
if (typeof namespace !== 'string') {
Expand All @@ -93,6 +137,18 @@ let validator = {
}
throw this._createErrorFromValidation(result, namespace, 'namespace');
},

/**
* Check if a namespace is valid without throwing.
* @param {string} namespace - The namespace to be validated.
* @returns {boolean} True if valid, false otherwise.
*/
isValidNamespace(namespace) {
if (typeof namespace !== 'string') {
return false;
}
return rclnodejs.validateNamespace(namespace) === null;
},
};

module.exports = validator;
123 changes: 123 additions & 0 deletions test/test-validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,127 @@ describe('rclnodejs validator testing', function () {
'invalid namespace!'
);
});

describe('isValidFullTopicName', function () {
it('should return true for valid fully-qualified topics', function () {
assert.strictEqual(
rclnodejs.validator.isValidFullTopicName('/chatter'),
true
);
assert.strictEqual(
rclnodejs.validator.isValidFullTopicName('/node_name/chatter'),
true
);
assert.strictEqual(
rclnodejs.validator.isValidFullTopicName('/ns/node_name/chatter'),
true
);
});

it('should return false for invalid topics', function () {
assert.strictEqual(
rclnodejs.validator.isValidFullTopicName('/invalid_topic?'),
false
);
assert.strictEqual(
rclnodejs.validator.isValidFullTopicName('relative_topic'),
false
);
});

it('should return false for non-string input', function () {
assert.strictEqual(rclnodejs.validator.isValidFullTopicName(123), false);
assert.strictEqual(rclnodejs.validator.isValidFullTopicName(null), false);
assert.strictEqual(
rclnodejs.validator.isValidFullTopicName(undefined),
false
);
});
});

describe('isValidNodeName', function () {
it('should return true for valid node names', function () {
assert.strictEqual(rclnodejs.validator.isValidNodeName('my_node'), true);
assert.strictEqual(rclnodejs.validator.isValidNodeName('node123'), true);
});

it('should return false for invalid node names', function () {
assert.strictEqual(rclnodejs.validator.isValidNodeName(''), false);
assert.strictEqual(
rclnodejs.validator.isValidNodeName('invalid_node?'),
false
);
assert.strictEqual(
rclnodejs.validator.isValidNodeName('/invalid_node'),
false
);
});

it('should return false for non-string input', function () {
assert.strictEqual(rclnodejs.validator.isValidNodeName(123), false);
assert.strictEqual(rclnodejs.validator.isValidNodeName(null), false);
});
});

describe('isValidTopicName', function () {
it('should return true for valid topic names', function () {
assert.strictEqual(rclnodejs.validator.isValidTopicName('chatter'), true);
assert.strictEqual(
rclnodejs.validator.isValidTopicName('{node}/chatter'),
true
);
assert.strictEqual(
rclnodejs.validator.isValidTopicName('~/chatter'),
true
);
assert.strictEqual(
rclnodejs.validator.isValidTopicName('/my/topic'),
true
);
});

it('should return false for invalid topic names', function () {
assert.strictEqual(rclnodejs.validator.isValidTopicName(''), false);
assert.strictEqual(
rclnodejs.validator.isValidTopicName('/invalid_topic?'),
false
);
assert.strictEqual(
rclnodejs.validator.isValidTopicName('invalid/42topic'),
false
);
});

it('should return false for non-string input', function () {
assert.strictEqual(rclnodejs.validator.isValidTopicName(123), false);
});
});

describe('isValidNamespace', function () {
it('should return true for valid namespaces', function () {
assert.strictEqual(rclnodejs.validator.isValidNamespace('/my_ns'), true);
assert.strictEqual(rclnodejs.validator.isValidNamespace('/'), true);
assert.strictEqual(
rclnodejs.validator.isValidNamespace('/deep/namespace'),
true
);
});

it('should return false for invalid namespaces', function () {
assert.strictEqual(rclnodejs.validator.isValidNamespace(''), false);
assert.strictEqual(
rclnodejs.validator.isValidNamespace('invalid_namespace'),
false
);
assert.strictEqual(
rclnodejs.validator.isValidNamespace('invalid namespace'),
false
);
});

it('should return false for non-string input', function () {
assert.strictEqual(rclnodejs.validator.isValidNamespace(123), false);
assert.strictEqual(rclnodejs.validator.isValidNamespace(null), false);
});
});
});
1 change: 1 addition & 0 deletions types/base.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@
/// <reference path="./time_source.d.ts" />
/// <reference path="./time.d.ts" />
/// <reference path="./timer.d.ts" />
/// <reference path="./validator.d.ts" />
86 changes: 86 additions & 0 deletions types/validator.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright (c) 2025 Mahmoud Alghalayini. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

declare module 'rclnodejs' {
/**
* Validator for ROS 2 names (topics, services, nodes, namespaces).
*/
namespace validator {
/**
* Validate a fully-qualified topic or service name.
* The name must be fully-qualified and already expanded.
* @param topic - The topic/service name to validate.
* @returns Always returns true if valid.
* @throws TypeValidationError if topic is not a string.
* @throws NameValidationError if the name is invalid.
*/
function validateFullTopicName(topic: string): true;

/**
* Check if a fully-qualified topic name is valid without throwing.
* @param topic - The topic/service name to check.
* @returns True if valid, false otherwise.
*/
function isValidFullTopicName(topic: string): boolean;

/**
* Validate a node name.
* @param name - The node name to validate.
* @returns Always returns true if valid.
* @throws TypeValidationError if name is not a string.
* @throws NameValidationError if the name is invalid.
*/
function validateNodeName(name: string): true;

/**
* Check if a node name is valid without throwing.
* @param name - The node name to check.
* @returns True if valid, false otherwise.
*/
function isValidNodeName(name: string): boolean;

/**
* Validate a topic or service name.
* The name does not have to be fully-qualified and is not expanded.
* @param topic - The topic/service name to validate.
* @returns Always returns true if valid.
* @throws TypeValidationError if topic is not a string.
* @throws NameValidationError if the name is invalid.
*/
function validateTopicName(topic: string): true;

/**
* Check if a topic name is valid without throwing.
* @param topic - The topic/service name to check.
* @returns True if valid, false otherwise.
*/
function isValidTopicName(topic: string): boolean;

/**
* Validate a namespace.
* @param namespace - The namespace to validate.
* @returns Always returns true if valid.
* @throws TypeValidationError if namespace is not a string.
* @throws NameValidationError if the namespace is invalid.
*/
function validateNamespace(namespace: string): true;

/**
* Check if a namespace is valid without throwing.
* @param namespace - The namespace to check.
* @returns True if valid, false otherwise.
*/
function isValidNamespace(namespace: string): boolean;
}
}
Loading