Skip to content

Commit cee07c2

Browse files
authored
Merge pull request #1217 from relu91/fix_1216
Align `value` function to Scripting API
2 parents 6901b5a + ac4e5ae commit cee07c2

5 files changed

Lines changed: 73 additions & 54 deletions

File tree

packages/binding-http/test/http-server-test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ class HttpServerTest {
160160
let test: DataSchemaValue;
161161
testThing.setPropertyReadHandler("test", (_) => Promise.resolve(test));
162162
testThing.setPropertyWriteHandler("test", async (value) => {
163-
test = await value.value();
163+
test = Buffer.from(await value.arrayBuffer()).toString("utf-8");
164164
});
165165

166166
testThing.setActionHandler("try", async (input: WoT.InteractionOutput) => {

packages/binding-opcua/test/full-opcua-thing-test.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ const thingDescription: WoT.ThingDescription = {
4949
observable: true,
5050
readOnly: true,
5151
unit: "°C",
52+
type: "number",
5253
"opcua:nodeId": { root: "i=84", path: "/Objects/1:MySensor/2:ParameterSet/1:Temperature" },
5354
// Don't specifu type here as it could be multi form: type: [ "object", "number" ],
5455
forms: [
@@ -111,6 +112,7 @@ const thingDescription: WoT.ThingDescription = {
111112
description: "the temperature set point",
112113
observable: true,
113114
unit: "°C",
115+
type: "number",
114116
// dont't
115117
forms: [
116118
{
@@ -358,10 +360,25 @@ describe("Full OPCUA Thing Test", () => {
358360

359361
return { thing, servient };
360362
}
361-
async function doTest(thing: WoT.ConsumedThing, propertyName: string, localOptions: InteractionOptions) {
363+
async function doTest(
364+
thing: WoT.ConsumedThing,
365+
propertyName: string,
366+
localOptions: InteractionOptions,
367+
forceParsing = false
368+
) {
362369
debug("------------------------------------------------------");
363370
try {
364371
const content = await thing.readProperty(propertyName, localOptions);
372+
if (forceParsing) {
373+
// In opcua binding it is possible to return a special response that contains
374+
// richer details than the bare value. However this makes the returned value
375+
// not complaint with its data schema. Therefore we have to fallback to
376+
// custom parsing.
377+
const raw = await content.arrayBuffer();
378+
const json = JSON.parse(Buffer.from(raw).toString("utf-8"));
379+
debug(json?.toString());
380+
return json;
381+
}
365382
const json = await content.value();
366383
debug(json?.toString());
367384
return json;
@@ -395,13 +412,13 @@ describe("Full OPCUA Thing Test", () => {
395412
const json1 = await doTest(thing, propertyName, { formIndex: 1 });
396413
expect(json1).to.eql(25);
397414

398-
const json2 = await doTest(thing, propertyName, { formIndex: 2 });
415+
const json2 = await doTest(thing, propertyName, { formIndex: 2 }, true);
399416
expect(json2).to.eql({ Type: 11, Body: 25 });
400417

401418
expect(thingDescription.properties?.temperature.forms[3].contentType).eql(
402419
"application/opcua+json;type=DataValue"
403420
);
404-
const json3 = await doTest(thing, propertyName, { formIndex: 3 });
421+
const json3 = await doTest(thing, propertyName, { formIndex: 3 }, true);
405422
debug(json3?.toString());
406423
expect((json3 as Record<string, unknown>).Value).to.eql({ Type: 11, Body: 25 });
407424
} finally {

packages/core/src/interaction-output.ts

Lines changed: 41 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import * as util from "util";
1616
import * as WoT from "wot-typescript-definitions";
1717
import { ContentSerdes } from "./content-serdes";
1818
import { ProtocolHelpers } from "./core";
19-
import { DataSchemaError, NotSupportedError } from "./errors";
19+
import { DataSchemaError, NotReadableError, NotSupportedError } from "./errors";
2020
import { Content } from "./content";
2121
import Ajv from "ajv";
2222
import { createLoggers } from "./logger";
@@ -32,17 +32,17 @@ const { debug } = createLoggers("core", "interaction-output");
3232
const ajv = new Ajv({ strict: false });
3333

3434
export class InteractionOutput implements WoT.InteractionOutput {
35-
private content: Content;
36-
private parsedValue: unknown;
37-
private buffer?: ArrayBuffer;
38-
private _stream?: ReadableStream;
35+
#content: Content;
36+
#value: unknown;
37+
#buffer?: ArrayBuffer;
38+
#stream?: ReadableStream;
3939
dataUsed: boolean;
4040
form?: WoT.Form;
4141
schema?: WoT.DataSchema;
4242

4343
public get data(): ReadableStream {
44-
if (this._stream) {
45-
return this._stream;
44+
if (this.#stream) {
45+
return this.#stream;
4646
}
4747

4848
if (this.dataUsed) {
@@ -51,71 +51,74 @@ export class InteractionOutput implements WoT.InteractionOutput {
5151
// Once the stream is created data might be pulled unpredictably
5252
// therefore we assume that it is going to be used to be safe.
5353
this.dataUsed = true;
54-
return (this._stream = ProtocolHelpers.toWoTStream(this.content.body) as ReadableStream);
54+
return (this.#stream = ProtocolHelpers.toWoTStream(this.#content.body) as ReadableStream);
5555
}
5656

5757
constructor(content: Content, form?: WoT.Form, schema?: WoT.DataSchema) {
58-
this.content = content;
58+
this.#content = content;
5959
this.form = form;
6060
this.schema = schema;
6161
this.dataUsed = false;
6262
}
6363

6464
async arrayBuffer(): Promise<ArrayBuffer> {
65-
if (this.buffer) {
66-
return this.buffer;
65+
if (this.#buffer) {
66+
return this.#buffer;
6767
}
6868

6969
if (this.dataUsed) {
7070
throw new Error("Can't read the stream once it has been already used");
7171
}
7272

73-
const data = await this.content.toBuffer();
73+
const data = await this.#content.toBuffer();
7474
this.dataUsed = true;
75-
this.buffer = data;
75+
this.#buffer = data;
7676

7777
return data;
7878
}
7979

8080
async value<T extends WoT.DataSchemaValue>(): Promise<T> {
8181
// the value has been already read?
82-
if (this.parsedValue !== undefined) {
83-
return this.parsedValue as T;
82+
if (this.#value !== undefined) {
83+
return this.#value as T;
8484
}
8585

8686
if (this.dataUsed) {
87-
throw new Error("Can't read the stream once it has been already used");
87+
throw new NotReadableError("Can't read the stream once it has been already used");
88+
}
89+
90+
if (this.form == null) {
91+
throw new NotReadableError("No form defined");
92+
}
93+
94+
if (this.schema == null || this.schema.type == null) {
95+
throw new NotReadableError("No schema defined");
8896
}
8997

9098
// is content type valid?
91-
if (!this.form || !ContentSerdes.get().isSupported(this.content.type)) {
92-
const message = !this.form ? "Missing form" : `Content type ${this.content.type} not supported`;
99+
if (!ContentSerdes.get().isSupported(this.#content.type)) {
100+
const message = `Content type ${this.#content.type} not supported`;
93101
throw new NotSupportedError(message);
94102
}
95103

96104
// read fully the stream
97-
const data = await this.content.toBuffer();
105+
const bytes = await this.#content.toBuffer();
98106
this.dataUsed = true;
99-
this.buffer = data;
100-
101-
// call the contentToValue
102-
// TODO: should be fixed contentToValue MUST define schema as nullable
103-
const value = ContentSerdes.get().contentToValue({ type: this.content.type, body: data }, this.schema ?? {});
104-
105-
// any data (schema)?
106-
if (this.schema) {
107-
// validate the schema
108-
const validate = ajv.compile<T>(this.schema);
109-
110-
if (!validate(value)) {
111-
debug(`schema = ${util.inspect(this.schema, { depth: 10, colors: true })}`);
112-
debug(`value: ${value}`);
113-
debug(`Errror: ${validate.errors}`);
114-
throw new DataSchemaError("Invalid value according to DataSchema", value as WoT.DataSchemaValue);
115-
}
107+
this.#buffer = bytes;
108+
109+
const json = ContentSerdes.get().contentToValue({ type: this.#content.type, body: bytes }, this.schema);
110+
111+
// validate the schema
112+
const validate = ajv.compile<T>(this.schema);
113+
114+
if (!validate(json)) {
115+
debug(`schema = ${util.inspect(this.schema, { depth: 10, colors: true })}`);
116+
debug(`value: ${json}`);
117+
debug(`Errror: ${validate.errors}`);
118+
throw new DataSchemaError("Invalid value according to DataSchema", json as WoT.DataSchemaValue);
116119
}
117120

118-
this.parsedValue = value;
119-
return this.parsedValue as T;
121+
this.#value = json;
122+
return json;
120123
}
121124
}

packages/core/src/wot-impl.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,33 +22,34 @@ import Helpers from "./helpers";
2222
import { createLoggers } from "./logger";
2323
import ContentManager from "./content-serdes";
2424
import { getLastValidationErrors, isThingDescription } from "./validation";
25+
import { inspect } from "util";
2526

2627
const { debug } = createLoggers("core", "wot-impl");
2728

2829
class ThingDiscoveryProcess implements WoT.ThingDiscoveryProcess {
29-
constructor(rawThingDescriptions: WoT.DataSchemaValue, filter?: WoT.ThingFilter) {
30+
constructor(private directory: ConsumedThing, public filter?: WoT.ThingFilter) {
3031
this.filter = filter;
3132
this.done = false;
32-
this.rawThingDescriptions = rawThingDescriptions;
3333
}
3434

35-
rawThingDescriptions: WoT.DataSchemaValue;
36-
37-
filter?: WoT.ThingFilter | undefined;
3835
done: boolean;
3936
error?: Error | undefined;
4037
async stop(): Promise<void> {
4138
this.done = true;
4239
}
4340

4441
async *[Symbol.asyncIterator](): AsyncIterator<WoT.ThingDescription> {
45-
if (!(this.rawThingDescriptions instanceof Array)) {
46-
this.error = new Error("Encountered an invalid output value.");
42+
let rawThingDescriptions: WoT.ThingDescription[];
43+
try {
44+
const thingsPropertyOutput = await this.directory.readProperty("things");
45+
rawThingDescriptions = (await thingsPropertyOutput.value()) as WoT.ThingDescription[];
46+
} catch (error) {
47+
this.error = error instanceof Error ? error : new Error(inspect(error));
4748
this.done = true;
4849
return;
4950
}
5051

51-
for (const outputValue of this.rawThingDescriptions) {
52+
for (const outputValue of rawThingDescriptions) {
5253
if (this.done) {
5354
return;
5455
}
@@ -81,10 +82,7 @@ export default class WoTImpl {
8182
const directoyThingDescription = await this.requestThingDescription(url);
8283
const consumedDirectoy = await this.consume(directoyThingDescription);
8384

84-
const thingsPropertyOutput = await consumedDirectoy.readProperty("things");
85-
const rawThingDescriptions = await thingsPropertyOutput.value();
86-
87-
return new ThingDiscoveryProcess(rawThingDescriptions, filter);
85+
return new ThingDiscoveryProcess(consumedDirectoy, filter);
8886
}
8987

9088
/** @inheritDoc */

packages/core/test/DiscoveryTest.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ function createDirectoryTestTd(title: string, thingsPropertyHref: string) {
3636
},
3737
properties: {
3838
things: {
39+
type: "array",
3940
forms: [
4041
{
4142
href: thingsPropertyHref,

0 commit comments

Comments
 (0)