Skip to content

Commit b527ca3

Browse files
authored
Add publisher/subscription_event_type_is_supported (#1520)
Port rclpy PR ros2/rclpy#1647 (commit cfac914) to expose `rcl_publisher_event_type_is_supported()` and `rcl_subscription_event_type_is_supported()`, so user code can query the active RMW implementation about which QoS events it delivers before wiring up a handler. The underlying rcl APIs (ros2/rcl#1317) ship in ROS 2 Rolling only. * Native binding (`src/rcl_event_handle_bindings.cpp`): N-API wrappers exported as `isPublisherEventTypeSupported` / `isSubscriptionEventTypeSupported`, both gated by `#if ROS_VERSION >= 5000`. * JS wrapper (`lib/event_handler.js`): public `isPublisherEventTypeSupported(eventType)` / `isSubscriptionEventTypeSupported(eventType)`. Availability guard keys off `typeof rclnodejs.<fn> !== 'function'` (mirrors the C++ build gate, matches the existing pattern in `lib/action/client.js`) and throws `OperationError` on pre-rolling. Argument validation uses `TypeValidationError` / `RangeValidationError` for consistency with `lib/time.js`, `lib/logging.js`, `lib/message_serialization.js`. * Tests (`test/test-event-handle.js`): two suites gated on native-symbol presence — asserts the unavailable-throw path, boolean returns for every enum value, `MATCHED` reported supported, and the right validation-error class/message for invalid inputs. Fix: #1519
1 parent d78f9aa commit b527ca3

3 files changed

Lines changed: 204 additions & 1 deletion

File tree

lib/event_handler.js

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616

1717
const rclnodejs = require('./native_loader.js');
1818
const DistroUtils = require('./distro.js');
19-
const { OperationError } = require('./errors.js');
19+
const {
20+
OperationError,
21+
RangeValidationError,
22+
TypeValidationError,
23+
} = require('./errors.js');
2024
const Entity = require('./entity.js');
2125

2226
/**
@@ -55,6 +59,94 @@ const SubscriptionEventType = {
5559
SUBSCRIPTION_MATCHED: 5,
5660
};
5761

62+
/**
63+
* Check if a publisher event type is supported by the active RMW implementation.
64+
*
65+
* Only available in ROS 2 Rolling and later, where the underlying rcl API
66+
* (`rcl_publisher_event_type_is_supported`) is provided.
67+
*
68+
* @param {number} eventType - A {@link PublisherEventType} value.
69+
* @return {boolean} True if the event type is supported by the active RMW
70+
* implementation, false otherwise.
71+
* @throws {OperationError} if invoked on a ROS distro older than Rolling.
72+
* @throws {TypeValidationError} if eventType is not a number.
73+
* @throws {RangeValidationError} if eventType is not a valid
74+
* {@link PublisherEventType} value.
75+
*/
76+
function isPublisherEventTypeSupported(eventType) {
77+
if (typeof rclnodejs.isPublisherEventTypeSupported !== 'function') {
78+
throw new OperationError(
79+
'isPublisherEventTypeSupported is only available in ROS 2 Rolling and later',
80+
{
81+
code: 'UNSUPPORTED_ROS_VERSION',
82+
entityType: 'publisher event type',
83+
details: {
84+
requiredVersion: 'rolling',
85+
currentVersion: DistroUtils.getDistroId(),
86+
},
87+
}
88+
);
89+
}
90+
if (typeof eventType !== 'number') {
91+
throw new TypeValidationError('eventType', eventType, 'number', {
92+
entityType: 'publisher event type',
93+
});
94+
}
95+
if (!Object.values(PublisherEventType).includes(eventType)) {
96+
throw new RangeValidationError(
97+
'eventType',
98+
eventType,
99+
'one of PublisherEventType values',
100+
{ entityType: 'publisher event type' }
101+
);
102+
}
103+
return rclnodejs.isPublisherEventTypeSupported(eventType);
104+
}
105+
106+
/**
107+
* Check if a subscription event type is supported by the active RMW implementation.
108+
*
109+
* Only available in ROS 2 Rolling and later, where the underlying rcl API
110+
* (`rcl_subscription_event_type_is_supported`) is provided.
111+
*
112+
* @param {number} eventType - A {@link SubscriptionEventType} value.
113+
* @return {boolean} True if the event type is supported by the active RMW
114+
* implementation, false otherwise.
115+
* @throws {OperationError} if invoked on a ROS distro older than Rolling.
116+
* @throws {TypeValidationError} if eventType is not a number.
117+
* @throws {RangeValidationError} if eventType is not a valid
118+
* {@link SubscriptionEventType} value.
119+
*/
120+
function isSubscriptionEventTypeSupported(eventType) {
121+
if (typeof rclnodejs.isSubscriptionEventTypeSupported !== 'function') {
122+
throw new OperationError(
123+
'isSubscriptionEventTypeSupported is only available in ROS 2 Rolling and later',
124+
{
125+
code: 'UNSUPPORTED_ROS_VERSION',
126+
entityType: 'subscription event type',
127+
details: {
128+
requiredVersion: 'rolling',
129+
currentVersion: DistroUtils.getDistroId(),
130+
},
131+
}
132+
);
133+
}
134+
if (typeof eventType !== 'number') {
135+
throw new TypeValidationError('eventType', eventType, 'number', {
136+
entityType: 'subscription event type',
137+
});
138+
}
139+
if (!Object.values(SubscriptionEventType).includes(eventType)) {
140+
throw new RangeValidationError(
141+
'eventType',
142+
eventType,
143+
'one of SubscriptionEventType values',
144+
{ entityType: 'subscription event type' }
145+
);
146+
}
147+
return rclnodejs.isSubscriptionEventTypeSupported(eventType);
148+
}
149+
58150
class EventHandler extends Entity {
59151
constructor(handle, callback, eventType, eventTypeName) {
60152
super(handle, null, null);
@@ -488,4 +580,6 @@ module.exports = {
488580
PublisherEventType,
489581
SubscriptionEventCallbacks,
490582
SubscriptionEventType,
583+
isPublisherEventTypeSupported,
584+
isSubscriptionEventTypeSupported,
491585
};

src/rcl_event_handle_bindings.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,26 @@ Napi::Value CreatePublisherEventHandle(const Napi::CallbackInfo& info) {
247247
return js_obj;
248248
}
249249

250+
#if ROS_VERSION >= 5000
251+
Napi::Value IsPublisherEventTypeSupported(const Napi::CallbackInfo& info) {
252+
Napi::Env env = info.Env();
253+
rcl_publisher_event_type_t event_type =
254+
static_cast<rcl_publisher_event_type_t>(
255+
info[0].As<Napi::Number>().Int32Value());
256+
return Napi::Boolean::New(env,
257+
rcl_publisher_event_type_is_supported(event_type));
258+
}
259+
260+
Napi::Value IsSubscriptionEventTypeSupported(const Napi::CallbackInfo& info) {
261+
Napi::Env env = info.Env();
262+
rcl_subscription_event_type_t event_type =
263+
static_cast<rcl_subscription_event_type_t>(
264+
info[0].As<Napi::Number>().Int32Value());
265+
return Napi::Boolean::New(
266+
env, rcl_subscription_event_type_is_supported(event_type));
267+
}
268+
#endif // ROS_VERSION >= 5000
269+
250270
Napi::Value TakeEvent(const Napi::CallbackInfo& info) {
251271
Napi::Env env = info.Env();
252272
RclHandle* event_handle = RclHandle::Unwrap(info[0].As<Napi::Object>());
@@ -288,6 +308,12 @@ Napi::Object InitEventHandleBindings(Napi::Env env, Napi::Object exports) {
288308
exports.Set("createPublisherEventHandle",
289309
Napi::Function::New(env, CreatePublisherEventHandle));
290310
exports.Set("takeEvent", Napi::Function::New(env, TakeEvent));
311+
#if ROS_VERSION >= 5000
312+
exports.Set("isPublisherEventTypeSupported",
313+
Napi::Function::New(env, IsPublisherEventTypeSupported));
314+
exports.Set("isSubscriptionEventTypeSupported",
315+
Napi::Function::New(env, IsSubscriptionEventTypeSupported));
316+
#endif // ROS_VERSION >= 5000
291317
return exports;
292318
}
293319

test/test-event-handle.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ const {
2424
PublisherEventCallbacks,
2525
PublisherEventType,
2626
SubscriptionEventType,
27+
isPublisherEventTypeSupported,
28+
isSubscriptionEventTypeSupported,
2729
} = require('../lib/event_handler.js');
2830

2931
describe('Event handle test suite prior to jazzy', function () {
@@ -46,6 +48,87 @@ describe('Event handle test suite prior to jazzy', function () {
4648
});
4749
});
4850

51+
describe('Event type is supported - native binding unavailable', function () {
52+
before(function () {
53+
if (
54+
typeof rclnodejsBinding.isPublisherEventTypeSupported === 'function' &&
55+
typeof rclnodejsBinding.isSubscriptionEventTypeSupported === 'function'
56+
) {
57+
this.skip();
58+
}
59+
});
60+
61+
it('isPublisherEventTypeSupported throws when native binding is missing', function () {
62+
assert.throws(() => {
63+
isPublisherEventTypeSupported(PublisherEventType.PUBLISHER_MATCHED);
64+
}, /isPublisherEventTypeSupported is only available in ROS 2 Rolling and later/);
65+
});
66+
67+
it('isSubscriptionEventTypeSupported throws when native binding is missing', function () {
68+
assert.throws(() => {
69+
isSubscriptionEventTypeSupported(
70+
SubscriptionEventType.SUBSCRIPTION_MATCHED
71+
);
72+
}, /isSubscriptionEventTypeSupported is only available in ROS 2 Rolling and later/);
73+
});
74+
});
75+
76+
describe('Event type is supported - native binding available', function () {
77+
before(function () {
78+
if (
79+
typeof rclnodejsBinding.isPublisherEventTypeSupported !== 'function' ||
80+
typeof rclnodejsBinding.isSubscriptionEventTypeSupported !== 'function'
81+
) {
82+
this.skip();
83+
}
84+
});
85+
86+
it('isPublisherEventTypeSupported returns a boolean for every event type', function () {
87+
for (const eventType of Object.values(PublisherEventType)) {
88+
const result = isPublisherEventTypeSupported(eventType);
89+
assert.strictEqual(typeof result, 'boolean');
90+
}
91+
});
92+
93+
it('isSubscriptionEventTypeSupported returns a boolean for every event type', function () {
94+
for (const eventType of Object.values(SubscriptionEventType)) {
95+
const result = isSubscriptionEventTypeSupported(eventType);
96+
assert.strictEqual(typeof result, 'boolean');
97+
}
98+
});
99+
100+
it('MATCHED events are reported as supported across RMW implementations', function () {
101+
assert.strictEqual(
102+
isPublisherEventTypeSupported(PublisherEventType.PUBLISHER_MATCHED),
103+
true
104+
);
105+
assert.strictEqual(
106+
isSubscriptionEventTypeSupported(
107+
SubscriptionEventType.SUBSCRIPTION_MATCHED
108+
),
109+
true
110+
);
111+
});
112+
113+
it('isPublisherEventTypeSupported rejects invalid event types', function () {
114+
assert.throws(() => {
115+
isPublisherEventTypeSupported(-1);
116+
}, /Value '-1' for 'eventType' is out of range: one of PublisherEventType values/);
117+
assert.throws(() => {
118+
isPublisherEventTypeSupported('matched');
119+
}, /Invalid type for 'eventType': expected number, got string/);
120+
});
121+
122+
it('isSubscriptionEventTypeSupported rejects invalid event types', function () {
123+
assert.throws(() => {
124+
isSubscriptionEventTypeSupported(999);
125+
}, /Value '999' for 'eventType' is out of range: one of SubscriptionEventType values/);
126+
assert.throws(() => {
127+
isSubscriptionEventTypeSupported(undefined);
128+
}, /Invalid type for 'eventType': expected number, got undefined/);
129+
});
130+
});
131+
49132
describe('Event handle test suite', function () {
50133
this.timeout(5 * 1000);
51134
let node;

0 commit comments

Comments
 (0)