Support ID-based CoAP paths for exposed Things#1490
Support ID-based CoAP paths for exposed Things#1490Pranav-0440 wants to merge 2 commits intoeclipse-thingweb:masterfrom
Conversation
…eb#1458) Signed-off-by: Pranav-0440 <pranavghorpade61@gmail.com>
There was a problem hiding this comment.
Pull request overview
This PR implements ID-based path support for the CoAP binding, mirroring the functionality introduced in PR #1464 for the HTTP binding. It addresses issue #1458 by enabling stable, production-safe URIs based on Thing IDs while maintaining backward compatibility through title-based paths.
Changes:
- Added
devFriendlyUriconfiguration option (defaults to true for backward compatibility) - Implemented dual-path registration allowing Things to be accessed via both slugified title and ID-based paths
- Updated
destroy()method to properly clean up all registered path aliases for a Thing - Added test validating reachability through both path types
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| packages/binding-coap/src/coap.ts | Added devFriendlyUri configuration option to CoapServerConfig interface |
| packages/binding-coap/src/coap-server.ts | Refactored path creation to support dual registration, updated expose() and destroy() methods to handle multiple paths per Thing |
| packages/binding-coap/test/coap-server-test.ts | Added test verifying Things are reachable via both title-based and ID-based paths |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let urlPath = slugify(thing.title, { lower: true }); | ||
| private createThingUrlPaths(thing: ExposedThing): string[] { | ||
| const paths: string[] = []; | ||
| // Title-based path |
There was a problem hiding this comment.
Consider adding a comment explaining why the title-based path is created even when devFriendlyUri is false if no ID is provided, similar to the explanatory comment in the HTTP binding at http-server.ts:274. This helps future maintainers understand the fallback behavior for Things without IDs.
| // Title-based path | |
| // Title-based path | |
| // Even when devFriendlyUri is false we still create a title-based path if the Thing has no ID. | |
| // This mirrors the HTTP binding behavior and ensures that Things without IDs can still be exposed | |
| // under a stable, discoverable path derived from their title. |
There was a problem hiding this comment.
I think this comment could rather be used as the basis for a doc comment for the function. Generally speaking, though, I don't think the mirroring of the HTTP binding needs to be mentioned that often.
| await coapClient.stop(); | ||
| await coapServer.stop(); | ||
| } | ||
|
|
There was a problem hiding this comment.
The test validates dual-path registration but doesn't cover the case where devFriendlyUri is set to false. Consider adding a test case that verifies when devFriendlyUri is false and a Thing has an ID, only the ID-based path is registered (not the title-based path).
| @test async "should expose Thing with id and title and be reachable only by id when devFriendlyUri is false"() { | |
| const coapServer = new CoapServer({ port: PORT, devFriendlyUri: false }); | |
| await coapServer.start(new Servient()); | |
| const testThing = new ExposedThing(new Servient(), { | |
| title: "TestThing", | |
| id: "urn:dev:wot:test-thing-1234", | |
| properties: { | |
| test: { | |
| type: "string", | |
| }, | |
| }, | |
| }); | |
| testThing.setPropertyReadHandler("test", async () => "OK"); | |
| // eslint-disable-next-line @typescript-eslint/ban-ts-comment | |
| // @ts-ignore | |
| testThing.properties.test.forms = []; | |
| await coapServer.expose(testThing); | |
| const uriByTitle = `coap://localhost:${coapServer.getPort()}/testthing/properties/test`; | |
| const uriById = `coap://localhost:${coapServer.getPort()}/urn:dev:wot:test-thing-1234/properties/test`; | |
| const coapClient = new CoapClient(coapServer); | |
| // ID-based path should be reachable | |
| const respById = await coapClient.readResource(new Form(uriById)); | |
| expect((await respById.toBuffer()).toString()).to.equal('"OK"'); | |
| // Title-based path should not be registered when devFriendlyUri is false | |
| const respByTitle = await new Promise<IncomingMessage>((resolve) => { | |
| const req = request(uriByTitle); | |
| req.on("response", (res) => resolve(res)); | |
| req.end(); | |
| }); | |
| expect(respByTitle.code).to.equal("4.04"); | |
| await coapClient.stop(); | |
| await coapServer.stop(); | |
| } |
There was a problem hiding this comment.
Seems like a valid addition at first glance.
| @@ -30,6 +30,7 @@ export * from "./coaps-client"; | |||
| export interface CoapServerConfig { | |||
| port?: number; | |||
| address?: string; | |||
There was a problem hiding this comment.
Consider adding JSDoc documentation for the devFriendlyUri configuration option to explain its purpose and behavior. Similar to the HTTP binding, this option controls whether title-based paths are generated (true by default for backward compatibility). When false and a Thing has an ID, only ID-based paths are created.
| address?: string; | |
| address?: string; | |
| /** | |
| * Controls whether title-based CoAP resource paths are generated. | |
| * | |
| * Similar to the HTTP binding, this option is `true` by default for backward | |
| * compatibility, so that Things are exposed using paths derived from their | |
| * titles. | |
| * | |
| * When set to `false` and a Thing has an ID, only ID-based paths are created | |
| * for that Thing. | |
| */ |
There was a problem hiding this comment.
The reference to the HTTP binding could be removed here.
| paths.push(urlPath); | ||
| } | ||
| // ID-based path | ||
| if (typeof thing.id === "string" && thing.id.length > 0) { |
There was a problem hiding this comment.
The ID-based path registration doesn't check for collisions with existing paths. If a Thing's ID matches another Thing's title-based path, it will silently overwrite the existing registration. Consider adding collision detection for ID paths or at least logging a warning when an ID path overwrites an existing path, similar to how title-based paths handle collisions with the numeric suffix pattern.
| if (typeof thing.id === "string" && thing.id.length > 0) { | |
| if (typeof thing.id === "string" && thing.id.length > 0) { | |
| if (this.things.has(thing.id)) { | |
| warn( | |
| `CoapServer path collision detected: Thing with id '${thing.id}' will overwrite existing Thing registration at path '/${thing.id}'.` | |
| ); | |
| } |
There was a problem hiding this comment.
Oh, that is actually another interesting observation.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1490 +/- ##
==========================================
- Coverage 77.99% 77.60% -0.39%
==========================================
Files 86 86
Lines 15904 15917 +13
Branches 1509 1511 +2
==========================================
- Hits 12404 12353 -51
- Misses 3479 3541 +62
- Partials 21 23 +2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
JKRhb
left a comment
There was a problem hiding this comment.
Thank you, @Pranav-0440, see a couple of comments below:
There was a problem hiding this comment.
| const paths = this.createThingUrlPaths(thing); | |
| const urlPaths = this.createThingUrlPaths(thing); |
There was a problem hiding this comment.
| for (const urlPath of paths) { | |
| for (const urlPath of urlPaths) { |
| let urlPath = slugify(thing.title, { lower: true }); | ||
| private createThingUrlPaths(thing: ExposedThing): string[] { | ||
| const paths: string[] = []; | ||
| // Title-based path |
There was a problem hiding this comment.
I think this comment could rather be used as the basis for a doc comment for the function. Generally speaking, though, I don't think the mirroring of the HTTP binding needs to be mentioned that often.
There was a problem hiding this comment.
| const paths: string[] = []; | |
| const urlPaths: string[] = []; |
There was a problem hiding this comment.
| paths.push(urlPath); | |
| urlPaths.push(urlPath); |
| if (this.things.has(urlPath)) { | ||
| let uniqueUrlPath; | ||
| let nameClashCnt = 2; | ||
| do { | ||
| uniqueUrlPath = urlPath + "_" + nameClashCnt++; | ||
| } while (this.things.has(uniqueUrlPath)); | ||
| urlPath = uniqueUrlPath; | ||
| } |
There was a problem hiding this comment.
Hmm. I was wondering whether we could end up in the following situation here:
- Starting point: two (partial) TDs have the same title, but only the second one has an ID
- For the second TD, a nameclash regarding the title is detected because the first TD did not have an ID and therefore an identical URL path
- The title of the second TD is appended with a
_2 - The urlPath of the second ID is further expanded by the ID
This is certainly an edge case (I am not actually not certain at the moment whether it is possible to have a missing ID at this point or whether that one is going to be set by the Servient), but I still wanted to mention it here.
| paths.push(urlPath); | ||
| } | ||
| // ID-based path | ||
| if (typeof thing.id === "string" && thing.id.length > 0) { |
There was a problem hiding this comment.
Oh, that is actually another interesting observation.
| @@ -30,6 +30,7 @@ export * from "./coaps-client"; | |||
| export interface CoapServerConfig { | |||
| port?: number; | |||
| address?: string; | |||
There was a problem hiding this comment.
The reference to the HTTP binding could be removed here.
| await coapClient.stop(); | ||
| await coapServer.stop(); | ||
| } | ||
|
|
There was a problem hiding this comment.
Seems like a valid addition at first glance.
Signed-off-by: Pranav-0440 <pranavghorpade61@gmail.com>
|
Thanks @JKRhb for the review!
All tests pass locally. Please let me know if further refinements are needed. |
This PR implements ID-based path support for the CoAP binding, similar to the HTTP implementation introduced in #1464.
Changes:
devFriendlyUriconfiguration option (default: true)destroy()This maintains backward compatibility while enabling stable, production-safe URIs for CoAP-exposed Things.
Fixes - #1458