Skip to content

fix(topic): validate absolute names and reject empty components - #275

Open
YuanYuYuan wants to merge 3 commits into
mainfrom
fix/topic-name-validation
Open

fix(topic): validate absolute names and reject empty components#275
YuanYuYuan wants to merge 3 commits into
mainfrom
fix/topic-name-validation

Conversation

@YuanYuYuan

@YuanYuYuan YuanYuYuan commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

qualify_topic_name validated name components on the relative and ~private branches only. The absolute branch — anything starting with / — returned the name unchecked. Every entity type routes through this function, so publishers, subscribers, services and actions were all affected.

Closes #264.

Defect

Examples use namespace = "/ns" and node_name = "node" unless the row shows otherwise.

tag hole on main observed result on main
D1 the absolute branch returned without validating components create_client("/bad name", ..) accepted; the name passed through unchanged
D2 validate_namespace skipped empty components qualify_topic_name("chatter", "//ns", "node") returned Ok("//ns/chatter")
D3 empty components were skipped, not rejected, on the topic branches a//b resolved to /ns/a//b; ~// resolved to /ns/node//
D4 the private branch did not normalize a trailing slash ~/a/ resolved to /ns/node/a/, while /a/ resolved to /a and a/ to /ns/a

D1 is the reported defect; see #264 for the reproduction and the downstream failure it produces. D2 is reachable because namespaces are user-supplied, and hiroz concatenates the namespace verbatim into the qualified name. //ns/chatter is the exact form the topic branch rejects.

What this PR does

change detail
Validate components on the absolute branch same is_valid_topic_component rule the other two branches already applied
Reject empty components instead of skipping them on all three topic branches and in validate_namespace
Normalize a trailing slash on all three branches the private branch now matches the absolute and relative ones
Extract validate_topic_components one helper, so the branches cannot drift apart again
Correct seven doc blocks six public rustdoc entries on the node factories (create_pub, create_sub, create_service, create_client, create_dyn_sub_auto, create_dyn_sub) plus the qualify_topic_name doc comment, all of which said absolute names are "used as-is"

Two subtleties are load-bearing:

  • The absolute branch strips exactly one leading slash before validating. trim_start_matches('/') would turn //a into a and then accept it — the form this exists to reject. See validate_topic_components.
  • ~ and ~/ legitimately mean the node itself, so they leave an empty suffix. Stripping the trailing slash of ~// also empties it, which would alias it to ~. A guard rejects that case before the emptiness test: private-branch guard.

Evidence

Eight new tests in crates/hiroz/src/topic_name.rs take the module from 11 to 19 tests. They assert 37 name forms across the boundary set.

test forms asserted
absolute_names_reject_invalid_components 9
absolute_names_still_accept_valid_components 5
empty_components_are_rejected_on_every_branch 3
namespaces_reject_empty_components 7
trailing_slash_is_normalized_on_every_branch 3
private_slash_only_suffixes_are_rejected 5
slash_only_names_are_rejected 3
service_names_reject_invalid_absolute_components 2

The service test exists because qualify_service_name delegates to qualify_topic_name, and the issue's reproduction is a client rather than a topic.

All 28 GitHub checks are green on e99805b578ba794ed7b06c8459b8f48478c55900, plus the CLA status.

Breaking changes

main accepted names that this change rejects at construction, and one resolved name changes.

tag what changes examples action
BC1 absolute names with an invalid component are rejected /bad name, /1abc, /a-b, /a.b, /a/2b rename to valid ROS 2 components
BC2 any name with an empty component is rejected //a, /a//b, ///, a//b, ~/a//b, ~// collapse the doubled or trailing slash
BC3 namespaces with an empty component are rejected //ns, /ns//sub collapse the doubled slash
BC4 ~/a/ now resolves to /ns/node/a instead of /ns/node/a/ none, unless a peer was matched on the trailing-slash form

Not affected: /ns, ns, / and the empty string remain valid namespaces; ~ and ~/ still qualify to /<ns>/<node>; /a/ and a/ normalize as before. The topic names / and //, and the namespace /ns/, were already rejected on main, so they are not part of BC1BC3.

Note

Every form in BC1BC3 was already an invalid ROS 2 name. It failed later and less legibly, or not at all. BC4 produced a name with a trailing slash, which the issue report describes as producing the same opaque key-expression error. A working peer match on the old form is therefore unlikely.

Coverage this does not have

None of these block the fix. They bound what green CI proves.

tag gap
G1 the in-repo sweep is a grep over crates/, docs/ and repo config for name literals the new rules reject. It found no call site that the new rules break; the only matches are the negative tests added here
G2 that sweep says nothing about downstream code, which is why this warrants a release note
G3 the claim about how an invalid name fails downstream comes from the issue report. This PR did not re-execute that path

Important

The release note for BC1BC4 is not written yet. The changelog is generated from the commit messages, and every commit on this branch is a plain fix(topic) with no breaking marker.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Validates ROS 2 names earlier to prevent malformed topics from reaching Zenoh.

Changes:

  • Adds shared topic-component validation.
  • Rejects empty components in topics and namespaces.
  • Adds validation tests and updates API documentation.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
crates/hiroz/src/topic_name.rs Implements validation and tests.
crates/hiroz/src/node.rs Updates entity-creation documentation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/hiroz/src/topic_name.rs
qualify_topic_name validated components on the relative and ~private
branches only. Absolute names -- anything starting with '/' -- were
returned unchecked, so `create_client("/bad name", ..)` was accepted at
construction and failed later inside zenoh's key-expression parser, with
an error citing a dependency path and never naming the topic. Every entity
type routes through this function.

Empty components were also skipped rather than rejected on all three
branches, so `//a//b` passed ROS validation for the same late, opaque
failure.

Both now go through one helper. Note it strips exactly one leading slash:
trimming all of them would turn `//a` into `a` and accept the form this is
meant to reject.

Closes #264
Adversarial review found the headline claim was false. Empty components
were rejected on the three *topic* branches but not in validate_namespace,
which still skipped them -- and the namespace is concatenated verbatim
into the qualified name. So `//ns` validated and produced `//ns/chatter`,
the exact form the topic branch rejects, reaching zenoh's key-expression
parser with the same opaque error this change exists to prevent.
Namespaces are user-supplied, so the path is reachable.

Also: the private branch did not strip a trailing slash, so `~/a/` alone
was rejected while `/a/` and `a/` were accepted and normalized. No reason
for the asymmetry.

Docs corrected in the same pass: `qualify_topic_name` and six public
rustdoc blocks on the node factories still said absolute names are "used
as-is", which this change makes false.

Tests added for what review found uncovered: the namespace cases, the
trailing slash on all three branches, and the "just slashes" family.
Copilot caught a hole my own trailing-slash fix opened. `~//` strips to
`//`, then one leading slash to `/`, then the new strip_suffix to `""` --
which is the "no suffix" case, so validation was skipped and the name
silently qualified to `/ns/node`, aliasing `~`.

`~` and `~/` legitimately mean the node itself. `~//` does not. Record
whether a suffix was present before stripping and reject the case where
stripping empties it.
@YuanYuYuan
YuanYuYuan force-pushed the fix/topic-name-validation branch from e99805b to 8ecbab8 Compare August 14, 2026 18:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

qualify_topic_name does not validate absolute topic/service/action names

2 participants