From a6d62fa7f52d9783a50fe7dbd1a59eecfefaf33a Mon Sep 17 00:00:00 2001 From: Jan Romann Date: Mon, 11 Sep 2023 18:34:53 +0200 Subject: [PATCH 1/7] refactor: move updateInteractionNameWithUriVariablePattern to core --- packages/binding-http/src/http-server.ts | 40 ++---------------------- packages/core/src/helpers.ts | 36 ++++++++++++++++++++- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/packages/binding-http/src/http-server.ts b/packages/binding-http/src/http-server.ts index 772925cb6..aa5b5422b 100644 --- a/packages/binding-http/src/http-server.ts +++ b/packages/binding-http/src/http-server.ts @@ -261,40 +261,6 @@ export default class HttpServer implements ProtocolServer { } } - private updateInteractionNameWithUriVariablePattern( - interactionName: string, - uriVariables: PropertyElement["uriVariables"] = {}, - thingVariables: PropertyElement["uriVariables"] = {} - ): string { - const variables = Object.assign({}, uriVariables, thingVariables); - if (Object.keys(variables).length > 0) { - let pattern = "{?"; - let index = 0; - if (uriVariables) { - for (const key in uriVariables) { - if (index !== 0) { - pattern += ","; - } - pattern += encodeURIComponent(key); - index++; - } - } - if (thingVariables) { - for (const key in thingVariables) { - if (index !== 0) { - pattern += ","; - } - pattern += encodeURIComponent(key); - index++; - } - } - pattern += "}"; - return encodeURIComponent(interactionName) + pattern; - } else { - return encodeURIComponent(interactionName); - } - } - public async expose(thing: ExposedThing, tdTemplate: WoT.ExposedThingInit = {}): Promise { let urlPath = slugify(thing.title, { lower: true }); @@ -400,7 +366,7 @@ export default class HttpServer implements ProtocolServer { } for (const propertyName in thing.properties) { - const propertyNamePattern = this.updateInteractionNameWithUriVariablePattern( + const propertyNamePattern = Helpers.updateInteractionNameWithUriVariablePattern( propertyName, thing.properties[propertyName].uriVariables, thing.uriVariables @@ -453,7 +419,7 @@ export default class HttpServer implements ProtocolServer { } for (const actionName in thing.actions) { - const actionNamePattern = this.updateInteractionNameWithUriVariablePattern( + const actionNamePattern = Helpers.updateInteractionNameWithUriVariablePattern( actionName, thing.actions[actionName].uriVariables, thing.uriVariables @@ -475,7 +441,7 @@ export default class HttpServer implements ProtocolServer { } for (const eventName in thing.events) { - const eventNamePattern = this.updateInteractionNameWithUriVariablePattern( + const eventNamePattern = Helpers.updateInteractionNameWithUriVariablePattern( eventName, thing.events[eventName].uriVariables, thing.uriVariables diff --git a/packages/core/src/helpers.ts b/packages/core/src/helpers.ts index 46a0211d9..585aba794 100644 --- a/packages/core/src/helpers.ts +++ b/packages/core/src/helpers.ts @@ -38,7 +38,7 @@ import { DataSchemaValue, ExposedThingInit } from "wot-typescript-definitions"; import { SomeJSONSchema } from "ajv/dist/types/json-schema"; import { ThingInteraction, ThingModelHelpers } from "@node-wot/td-tools"; import { Resolver } from "@node-wot/td-tools/src/resolver-interface"; -import { DataSchema } from "wot-thing-description-types"; +import { PropertyElement, DataSchema } from "wot-thing-description-types"; import { createLoggers } from "./logger"; const { debug, error, warn } = createLoggers("core", "helpers"); @@ -386,4 +386,38 @@ export default class Helpers implements Resolver { return params; } + + public static updateInteractionNameWithUriVariablePattern( + interactionName: string, + uriVariables: PropertyElement["uriVariables"] = {}, + thingVariables: PropertyElement["uriVariables"] = {} + ): string { + const variables = Object.assign({}, uriVariables, thingVariables); + if (Object.keys(variables).length > 0) { + let pattern = "{?"; + let index = 0; + if (uriVariables) { + for (const key in uriVariables) { + if (index !== 0) { + pattern += ","; + } + pattern += encodeURIComponent(key); + index++; + } + } + if (thingVariables) { + for (const key in thingVariables) { + if (index !== 0) { + pattern += ","; + } + pattern += encodeURIComponent(key); + index++; + } + } + pattern += "}"; + return encodeURIComponent(interactionName) + pattern; + } else { + return encodeURIComponent(interactionName); + } + } } From c5253cef718e97c3c90e69d49fc8e140f55bbaf5 Mon Sep 17 00:00:00 2001 From: Jan Romann Date: Mon, 11 Sep 2023 18:43:06 +0200 Subject: [PATCH 2/7] feat(coap-server): add support for URI variables --- packages/binding-coap/src/coap-server.ts | 49 +++++++++++++++++++----- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/packages/binding-coap/src/coap-server.ts b/packages/binding-coap/src/coap-server.ts index 04ecb5d3f..e2acd028e 100644 --- a/packages/binding-coap/src/coap-server.ts +++ b/packages/binding-coap/src/coap-server.ts @@ -189,7 +189,9 @@ export default class CoapServer implements ProtocolServer { this.PROPERTY_DIR, propertyName, offeredMediaType, - opValues + opValues, + property.uriVariables, + thing.uriVariables ); property.forms.push(form); @@ -204,7 +206,9 @@ export default class CoapServer implements ProtocolServer { this.ACTION_DIR, actionName, offeredMediaType, - "invokeaction" + "invokeaction", + action.uriVariables, + thing.uriVariables ); action.forms.push(form); @@ -215,10 +219,15 @@ export default class CoapServer implements ProtocolServer { private fillInEventBindingData(thing: ExposedThing, base: string, port: number, offeredMediaType: string) { for (const [eventName, event] of Object.entries(thing.events)) { - const [href, form] = this.createHrefAndForm(base, this.EVENT_DIR, eventName, offeredMediaType, [ - "subscribeevent", - "unsubscribeevent", - ]); + const [href, form] = this.createHrefAndForm( + base, + this.EVENT_DIR, + eventName, + offeredMediaType, + ["subscribeevent", "unsubscribeevent"], + event.uriVariables, + thing.uriVariables + ); event.forms.push(form); @@ -231,16 +240,36 @@ export default class CoapServer implements ProtocolServer { affordancePathSegment: string, affordanceName: string, offeredMediaType: string, - opValues: string | string[] + opValues: string | string[], + affordanceUriVariables: PropertyElement["uriVariables"] = {}, + thingUriVariables: PropertyElement["uriVariables"] = {} ): [string, TD.Form] { - const href = this.createFormHref(base, affordancePathSegment, affordanceName); + const href = this.createFormHref( + base, + affordancePathSegment, + affordanceName, + affordanceUriVariables, + thingUriVariables + ); const form = this.createAffordanceForm(href, offeredMediaType, opValues); return [href, form]; } - private createFormHref(base: string, affordancePathSegment: string, affordanceName: string) { - return `${base}/${affordancePathSegment}/${encodeURIComponent(affordanceName)}`; + private createFormHref( + base: string, + affordancePathSegment: string, + affordanceName: string, + affordanceUriVariables: PropertyElement["uriVariables"] = {}, + thingUriVariables: PropertyElement["uriVariables"] = {} + ) { + const affordanceNamePattern = Helpers.updateInteractionNameWithUriVariablePattern( + affordanceName, + affordanceUriVariables, + thingUriVariables + ); + + return `${base}/${affordancePathSegment}/${encodeURIComponent(affordanceNamePattern)}`; } private createAffordanceForm(href: string, offeredMediaType: string, op: string[] | string) { From 3f6f2df1909c3b1ecdeb3a116f1ff5558d665cdc Mon Sep 17 00:00:00 2001 From: Jan Romann Date: Mon, 11 Sep 2023 19:21:01 +0200 Subject: [PATCH 3/7] test(coap-server): add test for URI variables --- .../binding-coap/test/coap-server-test.ts | 75 ++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/packages/binding-coap/test/coap-server-test.ts b/packages/binding-coap/test/coap-server-test.ts index a7e0057e1..bbd228601 100644 --- a/packages/binding-coap/test/coap-server-test.ts +++ b/packages/binding-coap/test/coap-server-test.ts @@ -20,7 +20,7 @@ import Servient, { ExposedThing, Content } from "@node-wot/core"; import { suite, test } from "@testdeck/mocha"; import { expect, should } from "chai"; -import { DataSchemaValue, InteractionInput } from "wot-typescript-definitions"; +import { DataSchemaValue, InteractionInput, InteractionOptions } from "wot-typescript-definitions"; import * as TD from "@node-wot/td-tools"; import CoapServer from "../src/coap-server"; import { CoapClient } from "../src/coap"; @@ -389,4 +389,77 @@ class CoapServerTest { }); req.end(); } + + @test async "should check uriVariables consistency"() { + const portNumber = 5683; + const coapServer = new CoapServer(portNumber); + const servient = new Servient(); + + await coapServer.start(servient); + + const testThing = new ExposedThing(servient, { + title: "Test", + properties: { + test: { + type: "string", + uriVariables: { + id: { + type: "string", + }, + }, + }, + }, + actions: { + try: { + output: { type: "string" }, + uriVariables: { + step: { type: "integer" }, + }, + }, + }, + }); + + let test: DataSchemaValue; + testThing.setPropertyReadHandler("test", (options) => { + expect(options?.uriVariables).to.deep.equal({ id: "testId" }); + return new Promise((resolve, reject) => { + resolve(test); + }); + }); + testThing.setPropertyWriteHandler("test", async (value, options) => { + expect(options?.uriVariables).to.deep.equal({ id: "testId" }); + test = await value.value(); + expect(test?.valueOf()).to.deep.equal("on"); + }); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + testThing.properties.test.forms = []; + testThing.setActionHandler("try", (input: WoT.InteractionOutput, params?: InteractionOptions) => { + return new Promise((resolve, reject) => { + expect(params?.uriVariables).to.deep.equal({ step: 5 }); + resolve("TEST"); + }); + }); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + testThing.actions.try.forms = []; + + await coapServer.expose(testThing); + + const coapClient = new CoapClient(coapServer); + + await coapClient.writeResource( + new TD.Form("coap://localhost/test/properties/test?id=testId"), + new Content("text/plain", Readable.from("on")) + ); + + const response1 = await coapClient.readResource(new TD.Form("coap://localhost/test/properties/test?id=testId")); + expect((await response1.toBuffer()).toString()).to.equal('"on"'); + + const response2 = await coapClient.invokeResource(new TD.Form("coap://localhost/test/actions/try?step=5")); + expect((await response2.toBuffer()).toString()).to.equal('"TEST"'); + + await coapClient.stop(); + await coapServer.stop(); + } } From a5c7c4d32964e9e786117a296d5b0e73be9db018 Mon Sep 17 00:00:00 2001 From: Jan Romann Date: Mon, 11 Sep 2023 19:49:32 +0200 Subject: [PATCH 4/7] fixup! test(coap-server): add test for URI variables --- packages/binding-coap/test/coap-server-test.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/binding-coap/test/coap-server-test.ts b/packages/binding-coap/test/coap-server-test.ts index bbd228601..8364cc91f 100644 --- a/packages/binding-coap/test/coap-server-test.ts +++ b/packages/binding-coap/test/coap-server-test.ts @@ -391,10 +391,12 @@ class CoapServerTest { } @test async "should check uriVariables consistency"() { - const portNumber = 5683; + const portNumber = 9003; const coapServer = new CoapServer(portNumber); const servient = new Servient(); + const baseUri = `coap://localhost:${portNumber}/test`; + await coapServer.start(servient); const testThing = new ExposedThing(servient, { @@ -448,15 +450,14 @@ class CoapServerTest { const coapClient = new CoapClient(coapServer); - await coapClient.writeResource( - new TD.Form("coap://localhost/test/properties/test?id=testId"), - new Content("text/plain", Readable.from("on")) - ); + const propertyUri = `${baseUri}/properties/test?id=testId`; + + await coapClient.writeResource(new TD.Form(propertyUri), new Content("text/plain", Readable.from("on"))); - const response1 = await coapClient.readResource(new TD.Form("coap://localhost/test/properties/test?id=testId")); + const response1 = await coapClient.readResource(new TD.Form(propertyUri)); expect((await response1.toBuffer()).toString()).to.equal('"on"'); - const response2 = await coapClient.invokeResource(new TD.Form("coap://localhost/test/actions/try?step=5")); + const response2 = await coapClient.invokeResource(new TD.Form(`${baseUri}/actions/try?step=5`)); expect((await response2.toBuffer()).toString()).to.equal('"TEST"'); await coapClient.stop(); From b7408929523e06bdd2eb4f257938ff0f22675c9d Mon Sep 17 00:00:00 2001 From: Jan Romann Date: Mon, 11 Sep 2023 19:50:05 +0200 Subject: [PATCH 5/7] refactor(core): simplify updateInteractionNameWithUriVariablePattern --- packages/core/src/helpers.ts | 38 ++++++++++-------------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/packages/core/src/helpers.ts b/packages/core/src/helpers.ts index 585aba794..62aa30027 100644 --- a/packages/core/src/helpers.ts +++ b/packages/core/src/helpers.ts @@ -389,35 +389,17 @@ export default class Helpers implements Resolver { public static updateInteractionNameWithUriVariablePattern( interactionName: string, - uriVariables: PropertyElement["uriVariables"] = {}, - thingVariables: PropertyElement["uriVariables"] = {} + affordanceUriVariables: PropertyElement["uriVariables"] = {}, + thingUriVariables: PropertyElement["uriVariables"] = {} ): string { - const variables = Object.assign({}, uriVariables, thingVariables); - if (Object.keys(variables).length > 0) { - let pattern = "{?"; - let index = 0; - if (uriVariables) { - for (const key in uriVariables) { - if (index !== 0) { - pattern += ","; - } - pattern += encodeURIComponent(key); - index++; - } - } - if (thingVariables) { - for (const key in thingVariables) { - if (index !== 0) { - pattern += ","; - } - pattern += encodeURIComponent(key); - index++; - } - } - pattern += "}"; - return encodeURIComponent(interactionName) + pattern; - } else { - return encodeURIComponent(interactionName); + const encodedInteractionName = encodeURIComponent(interactionName); + const uriVariables = [...Object.keys(affordanceUriVariables), ...Object.keys(thingUriVariables)]; + const pattern = uriVariables.map(encodeURIComponent).join(","); + + if (pattern.length > 0) { + return `${encodedInteractionName}{?${pattern}}`; } + + return encodedInteractionName; } } From e0c95848e37c9057250daade320edf7330ce10b5 Mon Sep 17 00:00:00 2001 From: Jan Romann Date: Mon, 11 Sep 2023 21:50:19 +0200 Subject: [PATCH 6/7] fixup! refactor(core): simplify updateInteractionNameWithUriVariablePattern --- packages/core/src/helpers.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/core/src/helpers.ts b/packages/core/src/helpers.ts index 62aa30027..2cee4ba61 100644 --- a/packages/core/src/helpers.ts +++ b/packages/core/src/helpers.ts @@ -394,12 +394,13 @@ export default class Helpers implements Resolver { ): string { const encodedInteractionName = encodeURIComponent(interactionName); const uriVariables = [...Object.keys(affordanceUriVariables), ...Object.keys(thingUriVariables)]; - const pattern = uriVariables.map(encodeURIComponent).join(","); - if (pattern.length > 0) { - return `${encodedInteractionName}{?${pattern}}`; + if (uriVariables.length === 0) { + return encodedInteractionName; } - return encodedInteractionName; + const pattern = uriVariables.map(encodeURIComponent).join(","); + + return `${encodedInteractionName}{?${pattern}}`; } } From 2bc9e198a4d01e036351a59f77695325c4426fb4 Mon Sep 17 00:00:00 2001 From: Jan Romann Date: Mon, 11 Sep 2023 21:58:09 +0200 Subject: [PATCH 7/7] fiuxp! refactor(core): simplify updateInteractionNameWithUriVariablePattern --- packages/core/src/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/helpers.ts b/packages/core/src/helpers.ts index 2cee4ba61..48d9ca26c 100644 --- a/packages/core/src/helpers.ts +++ b/packages/core/src/helpers.ts @@ -401,6 +401,6 @@ export default class Helpers implements Resolver { const pattern = uriVariables.map(encodeURIComponent).join(","); - return `${encodedInteractionName}{?${pattern}}`; + return encodedInteractionName + "{?" + pattern + "}"; } }