Skip to content

Commit

Permalink
feat(coap-server): add support for URI variables (#1078)
Browse files Browse the repository at this point in the history
  • Loading branch information
JKRhb authored Sep 12, 2023
1 parent 03751a9 commit 692af10
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 49 deletions.
49 changes: 39 additions & 10 deletions packages/binding-coap/src/coap-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);

Expand All @@ -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) {
Expand Down
76 changes: 75 additions & 1 deletion packages/binding-coap/test/coap-server-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -389,4 +389,78 @@ class CoapServerTest {
});
req.end();
}

@test async "should check uriVariables consistency"() {
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, {
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<InteractionInput>((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<string>((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);

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(propertyUri));
expect((await response1.toBuffer()).toString()).to.equal('"on"');

const response2 = await coapClient.invokeResource(new TD.Form(`${baseUri}/actions/try?step=5`));
expect((await response2.toBuffer()).toString()).to.equal('"TEST"');

await coapClient.stop();
await coapServer.stop();
}
}
40 changes: 3 additions & 37 deletions packages/binding-http/src/http-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
let urlPath = slugify(thing.title, { lower: true });

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
19 changes: 18 additions & 1 deletion packages/core/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -386,4 +386,21 @@ export default class Helpers implements Resolver {

return params;
}

public static updateInteractionNameWithUriVariablePattern(
interactionName: string,
affordanceUriVariables: PropertyElement["uriVariables"] = {},
thingUriVariables: PropertyElement["uriVariables"] = {}
): string {
const encodedInteractionName = encodeURIComponent(interactionName);
const uriVariables = [...Object.keys(affordanceUriVariables), ...Object.keys(thingUriVariables)];

if (uriVariables.length === 0) {
return encodedInteractionName;
}

const pattern = uriVariables.map(encodeURIComponent).join(",");

return encodedInteractionName + "{?" + pattern + "}";
}
}

0 comments on commit 692af10

Please sign in to comment.