diff --git a/packages/binding-http/test/http-client-test.ts b/packages/binding-http/test/http-client-test.ts index 1bb7067a4..25baaaef5 100644 --- a/packages/binding-http/test/http-client-test.ts +++ b/packages/binding-http/test/http-client-test.ts @@ -22,12 +22,20 @@ import chai, { expect, should } from "chai"; import * as http from "http"; -import { Content, DefaultContent, ContentSerdes, createLoggers, ProtocolServer } from "@node-wot/core"; +import { + Content, + DefaultContent, + ContentSerdes, + createLoggers, + ProtocolServer, + Servient, + Helpers, +} from "@node-wot/core"; import { Readable } from "stream"; import HttpClient from "../src/http-client"; -import { HttpForm } from "../src/http"; +import { HttpForm, HttpClientFactory } from "../src/http"; import express from "express"; import serveStatic from "serve-static"; @@ -518,4 +526,24 @@ class HttpClientTest2 { ); }); } + + @test async "should fetchTD successfully"() { + const servient = new Servient(); + servient.addClientFactory(new HttpClientFactory()); + const helpers = new Helpers(servient); + const td = await helpers.fetchTD("http://plugfest.thingweb.io:8083/counter"); + expect(td).to.contains.keys("@context", "title"); + } + + @test async "should fail fetching a non TD resource"() { + const servient = new Servient(); + servient.addClientFactory(new HttpClientFactory()); + const helpers = new Helpers(servient); + try { + await helpers.fetchTD("http://plugfest.thingweb.io:8083/"); // reports array + expect(1).to.equal(0, "Does not report that we do not deal with a proper TD"); + } catch (err) { + // correct since it is not a proper TD + } + } } diff --git a/packages/core/src/helpers.ts b/packages/core/src/helpers.ts index df7f7d224..63124a7de 100644 --- a/packages/core/src/helpers.ts +++ b/packages/core/src/helpers.ts @@ -34,7 +34,7 @@ import * as TDT from "wot-thing-description-types"; import { ContentSerdes } from "./content-serdes"; import Ajv, { ValidateFunction, ErrorObject } from "ajv"; import TDSchema from "wot-thing-description-types/schema/td-json-schema-validation.json"; -import { DataSchemaValue, ExposedThingInit } from "wot-typescript-definitions"; +import { DataSchemaValue, ExposedThingInit, ThingDescription } 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"; @@ -152,8 +152,28 @@ export default class Helpers implements Resolver { } } - // TODO: specialize fetch to retrieve just thing descriptions - // see https://github.com/eclipse-thingweb/node-wot/issues/1055 + /** + * Fetches a resource with additionally checking TD conformance + * + * @param uri The URI of a TD resource + * @returns A ThingDescription + */ + public async fetchTD(uri: string): Promise { + const data = await this.fetch(uri); + const isValid = Helpers.tsSchemaValidator(data); + if (!isValid) { + const errors = Helpers.tsSchemaValidator.errors?.map((o: ErrorObject) => o.message).join("\n"); + throw new Error(errors); + } + return data as ThingDescription; + } + + /** + * Fetches a resource + * + * @param uri The URI of a resource + * @returns An object + */ public fetch(uri: string): Promise { return new Promise((resolve, reject) => { const client = this.srv.getClientFor(Helpers.extractScheme(uri)); diff --git a/packages/examples/src/bindings/coap/example-client.ts b/packages/examples/src/bindings/coap/example-client.ts index b815f0aac..5b3ed5a1f 100644 --- a/packages/examples/src/bindings/coap/example-client.ts +++ b/packages/examples/src/bindings/coap/example-client.ts @@ -15,7 +15,6 @@ import { Servient, Helpers } from "@node-wot/core"; import { CoapClientFactory } from "@node-wot/binding-coap"; -import { ThingDescription } from "wot-typescript-definitions"; // create Servient and add CoAP binding const servient = new Servient(); @@ -23,10 +22,9 @@ servient.addClientFactory(new CoapClientFactory()); const wotHelper = new Helpers(servient); wotHelper - .fetch("coap://plugfest.thingweb.io:5683/testthing") - .then(async (fetched) => { - const td: ThingDescription = fetched as ThingDescription; - // using await for serial execution (note 'async' in then() of fetch()) + .fetchTD("coap://plugfest.thingweb.io:5683/testthing") + .then(async (td) => { + // using await for serial execution (note 'async' in then() of fetchTD()) try { const WoT = await servient.start(); const thing = await WoT.consume(td); diff --git a/packages/examples/src/bindings/http/example-client.ts b/packages/examples/src/bindings/http/example-client.ts index bab479dda..c988d4c34 100644 --- a/packages/examples/src/bindings/http/example-client.ts +++ b/packages/examples/src/bindings/http/example-client.ts @@ -15,7 +15,6 @@ import { Servient, Helpers } from "@node-wot/core"; import { HttpClientFactory } from "@node-wot/binding-http"; -import { ThingDescription } from "wot-typescript-definitions"; // create Servient and add HTTP binding const servient = new Servient(); @@ -23,10 +22,9 @@ servient.addClientFactory(new HttpClientFactory()); const wotHelper = new Helpers(servient); wotHelper - .fetch("http://plugfest.thingweb.io:8083/testthing") - .then(async (fetched) => { - const td: ThingDescription = fetched as ThingDescription; - // using await for serial execution (note 'async' in then() of fetch()) + .fetchTD("http://plugfest.thingweb.io:8083/testthing") + .then(async (td) => { + // using await for serial execution (note 'async' in then() of fetchTD()) try { const WoT = await servient.start(); const thing = await WoT.consume(td); diff --git a/packages/examples/src/scripts/counter-client.ts b/packages/examples/src/scripts/counter-client.ts index d4af4d4ae..184c341d0 100644 --- a/packages/examples/src/scripts/counter-client.ts +++ b/packages/examples/src/scripts/counter-client.ts @@ -14,7 +14,6 @@ ********************************************************************************/ import { Helpers } from "@node-wot/core"; -import { ThingDescription } from "wot-typescript-definitions"; let WoTHelpers!: Helpers; @@ -31,11 +30,11 @@ function getFormIndexForDecrementWithCoAP(thing: WoT.ConsumedThing): number { return 0; } -WoTHelpers.fetch("coap://localhost:5683/counter") +WoTHelpers.fetchTD("coap://localhost:5683/counter") .then(async (td) => { - // using await for serial execution (note 'async' in then() of fetch()) + // using await for serial execution (note 'async' in then() of fetchTD()) try { - const thing = await WoT.consume(td as ThingDescription); + const thing = await WoT.consume(td); console.info("=== TD ==="); console.info(td); console.info("=========="); diff --git a/packages/examples/src/scripts/smart-coffee-machine-client.ts b/packages/examples/src/scripts/smart-coffee-machine-client.ts index 3d2f6e55e..655f77888 100644 --- a/packages/examples/src/scripts/smart-coffee-machine-client.ts +++ b/packages/examples/src/scripts/smart-coffee-machine-client.ts @@ -17,7 +17,6 @@ // It considers a fictional smart coffee machine in order to demonstrate the capabilities of Web of Things. // An accompanying tutorial is available at http://www.thingweb.io/smart-coffee-machine.html. -import { ThingDescription } from "wot-typescript-definitions"; import { Helpers } from "@node-wot/core"; let WoTHelpers!: Helpers; @@ -29,9 +28,9 @@ function log(msg: string, data: unknown) { console.info("======================"); } -WoTHelpers.fetch("http://127.0.0.1:8080/smart-coffee-machine").then(async (td) => { +WoTHelpers.fetchTD("http://127.0.0.1:8080/smart-coffee-machine").then(async (td) => { try { - const thing = await WoT.consume(td as ThingDescription); + const thing = await WoT.consume(td); log("Thing Description:", td); // Read property allAvailableResources diff --git a/packages/examples/src/security/oauth/consumer.ts b/packages/examples/src/security/oauth/consumer.ts index 8b18127bb..93d2bdad1 100644 --- a/packages/examples/src/security/oauth/consumer.ts +++ b/packages/examples/src/security/oauth/consumer.ts @@ -13,12 +13,11 @@ * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 ********************************************************************************/ import { Helpers } from "@node-wot/core"; -import { ThingDescription } from "wot-typescript-definitions"; let WoTHelpers!: Helpers; -WoTHelpers.fetch("https://localhost:8080/oauth").then((td) => { - WoT.consume(td as ThingDescription).then(async (thing) => { +WoTHelpers.fetchTD("https://localhost:8080/oauth").then((td) => { + WoT.consume(td).then(async (thing) => { try { const resp = await thing.invokeAction("sayOk"); const result = await resp?.value(); diff --git a/packages/examples/src/testthing/testclient.ts b/packages/examples/src/testthing/testclient.ts index d73ad90ee..fc9e6cd0e 100644 --- a/packages/examples/src/testthing/testclient.ts +++ b/packages/examples/src/testthing/testclient.ts @@ -14,7 +14,6 @@ ********************************************************************************/ import { Helpers } from "@node-wot/core"; -import { ThingDescription } from "wot-typescript-definitions"; let WoTHelpers!: Helpers; console.log = () => { @@ -51,11 +50,11 @@ async function testPropertyWrite( } } -WoTHelpers.fetch("http://localhost:8080/testthing") +WoTHelpers.fetchTD("http://localhost:8080/testthing") .then(async (td) => { - // using await for serial execution (note 'async' in then() of fetch()) + // using await for serial execution (note 'async' in then() of fetchTD()) try { - const thing = await WoT.consume(td as ThingDescription); + const thing = await WoT.consume(td); console.info("=== TD ==="); console.info(td); console.info("==========");