Skip to content

Commit 5f8a5c0

Browse files
refactor: allow to specify whether we want to have DataSchema validation
1 parent 066f725 commit 5f8a5c0

4 files changed

Lines changed: 39 additions & 10 deletions

File tree

packages/core/src/consumed-thing.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,7 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing {
558558

559559
const content = await client.readResource(form);
560560
try {
561-
return this.handleInteractionOutput(content, form, tp);
561+
return this.handleInteractionOutput(content, form, tp, false);
562562
} catch (e) {
563563
const error = e instanceof Error ? e : new Error(JSON.stringify(e));
564564
throw new Error(`Error while processing property for ${tp.title}. ${error.message}`);
@@ -568,7 +568,8 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing {
568568
private handleInteractionOutput(
569569
content: Content,
570570
form: TD.Form,
571-
outputDataSchema: WoT.DataSchema | undefined
571+
outputDataSchema: WoT.DataSchema | undefined,
572+
ignoreValidation: boolean | undefined
572573
): InteractionOutput {
573574
// infer media type from form if not in response metadata
574575
content.type ??= form.contentType ?? "application/json";
@@ -583,7 +584,7 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing {
583584
);
584585
}
585586
}
586-
return new InteractionOutput(content, form, outputDataSchema);
587+
return new InteractionOutput(content, form, outputDataSchema, ignoreValidation);
587588
}
588589

589590
async _readProperties(propertyNames: string[]): Promise<WoT.PropertyReadMap> {
@@ -703,7 +704,8 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing {
703704

704705
const content = await client.invokeResource(form, input);
705706
try {
706-
return this.handleInteractionOutput(content, form, ta.output);
707+
const ignoreValidation = ta.synchronous === undefined ? true : !ta.synchronous;
708+
return this.handleInteractionOutput(content, form, ta.output, ignoreValidation);
707709
} catch (e) {
708710
const error = e instanceof Error ? e : new Error(JSON.stringify(e));
709711
throw new Error(`Error while processing action for ${ta.title}. ${error.message}`);
@@ -746,7 +748,7 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing {
746748
// next
747749
(content) => {
748750
try {
749-
listener(this.handleInteractionOutput(content, form, tp));
751+
listener(this.handleInteractionOutput(content, form, tp, false));
750752
} catch (e) {
751753
const error = e instanceof Error ? e : new Error(JSON.stringify(e));
752754
warn(`Error while processing observe property for ${tp.title}. ${error.message}`);
@@ -802,7 +804,7 @@ export default class ConsumedThing extends TD.Thing implements IConsumedThing {
802804
formWithoutURITemplates,
803805
(content) => {
804806
try {
805-
listener(this.handleInteractionOutput(content, form, te.data));
807+
listener(this.handleInteractionOutput(content, form, te.data, false));
806808
} catch (e) {
807809
const error = e instanceof Error ? e : new Error(JSON.stringify(e));
808810
warn(`Error while processing event for ${te.title}. ${error.message}`);

packages/core/src/interaction-output.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export class InteractionOutput implements WoT.InteractionOutput {
4242
dataUsed: boolean;
4343
form?: WoT.Form;
4444
schema?: WoT.DataSchema;
45+
enforceValidation: boolean; // by default set to true
4546

4647
public get data(): ReadableStream {
4748
if (this.#stream) {
@@ -57,10 +58,11 @@ export class InteractionOutput implements WoT.InteractionOutput {
5758
return (this.#stream = ProtocolHelpers.toWoTStream(this.#content.body) as ReadableStream);
5859
}
5960

60-
constructor(content: Content, form?: WoT.Form, schema?: WoT.DataSchema) {
61+
constructor(content: Content, form?: WoT.Form, schema?: WoT.DataSchema, ignoreValidation?: boolean) {
6162
this.#content = content;
6263
this.form = form;
6364
this.schema = schema;
65+
this.enforceValidation = ignoreValidation === undefined ? true : !ignoreValidation;
6466
this.dataUsed = false;
6567
}
6668

@@ -123,14 +125,14 @@ export class InteractionOutput implements WoT.InteractionOutput {
123125
const validate = ajv.compile<T>(this.schema);
124126

125127
// Note: validation for action output should take place only if action is synchronous!
126-
if (!validate(json)) {
128+
if (this.enforceValidation && !validate(json)) {
127129
debug(`schema = ${util.inspect(this.schema, { depth: 10, colors: true })}`);
128130
debug(`value: ${json}`);
129131
debug(`Errror: ${validate.errors}`);
130132
throw new DataSchemaError("Invalid value according to DataSchema", json as WoT.DataSchemaValue);
131133
}
132134

133135
this.#value = json;
134-
return json;
136+
return json as T;
135137
}
136138
}

packages/core/test/ClientTest.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ class WoTClientTest {
527527
WoTClientTest.clientFactory.setTrap(async (form: Form, content: Content) => {
528528
const valueData = await content.toBuffer();
529529
expect(valueData.toString()).to.equal("23");
530-
return new Content("application/json", Readable.from(Buffer.from("{'status': 'pending'")));
530+
return new Content("application/json", Readable.from(Buffer.from(JSON.stringify({ status: "pending" }))));
531531
});
532532
const td = (await WoTClientTest.WoTHelpers.fetch("td://foo")) as ThingDescription;
533533

packages/core/test/InteractionOutputTest.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { expect, use } from "chai";
2020
import { Readable } from "stream";
2121
import { InteractionOutput } from "../src/interaction-output";
2222
import { Content } from "..";
23+
import { fail } from "assert";
2324

2425
use(promised);
2526
const delay = (ms: number) => {
@@ -106,6 +107,30 @@ class InteractionOutputTests {
106107
expect(result).be.true;
107108
}
108109

110+
@test async "should fail returning unexpected value with no validation"() {
111+
const stream = Readable.from(Buffer.from("not boolean", "utf-8"));
112+
const content = new Content("application/json", stream);
113+
114+
const out = new InteractionOutput(content, {}, { type: "boolean" }); // ignoreValidation false by default
115+
try {
116+
const result = await out.value();
117+
expect(result).be.true;
118+
fail("Wrongly allows invalid value");
119+
} catch {
120+
// expected to throw
121+
}
122+
}
123+
124+
@test async "should accept returning unexpected value with no validation"() {
125+
// type boolean should not throw since we set ignoreValidation to true
126+
const stream = Readable.from(Buffer.from("not boolean", "utf-8"));
127+
const content = new Content("application/json", stream);
128+
129+
const out = new InteractionOutput(content, {}, { type: "boolean" }, true);
130+
const result = await out.value();
131+
expect(result).to.eql("not boolean");
132+
}
133+
109134
@test async "should data be used after arrayBuffer"() {
110135
const stream = Readable.from(Buffer.from("true", "utf-8"));
111136
const content = new Content("application/json", stream);

0 commit comments

Comments
 (0)