Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add initial requestThingDescription implementation #1166

Merged
merged 7 commits into from
Nov 29, 2023
18 changes: 18 additions & 0 deletions packages/binding-coap/src/coap-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,24 @@
});
}

/**
* @inheritdoc
*/
requestThingDescription(uri: string): Promise<Content> {
const options: CoapRequestParams = this.uriToOptions(uri);
const req = this.agent.request(options);

req.setOption("Accept", "application/td+json");
return new Promise<Content>((resolve, reject) => {
req.on("response", (res: IncomingMessage) => {
const contentType = (res.headers["Content-Format"] as string) ?? "application/td+json";
resolve(new Content(contentType, Readable.from(res.payload)));
});
req.on("error", (err: Error) => reject(err));
req.end();
});
}

Check warning on line 202 in packages/binding-coap/src/coap-client.ts

View check run for this annotation

Codecov / codecov/patch

packages/binding-coap/src/coap-client.ts#L190-L202

Added lines #L190 - L202 were not covered by tests

public async start(): Promise<void> {
// do nothing
}
Expand Down
17 changes: 17 additions & 0 deletions packages/binding-coap/src/coaps-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,23 @@
});
}

/**
* @inheritdoc
*/
public async requestThingDescription(uri: string): Promise<Content> {
const response = await coaps.request(uri, "get", undefined, {
// FIXME: Add accept option
// Currently not supported by node-coap-client
});

// TODO: Respect Content-Format in response.
// Currently not really well supported by node-coap-client
const contentType = "application/td+json";
const payload = response.payload ?? Buffer.alloc(0);

return new Content(contentType, Readable.from(payload));
}

Check warning on line 157 in packages/binding-coap/src/coaps-client.ts

View check run for this annotation

Codecov / codecov/patch

packages/binding-coap/src/coaps-client.ts#L146-L157

Added lines #L146 - L157 were not covered by tests

public async start(): Promise<void> {
// do nothing
}
Expand Down
71 changes: 43 additions & 28 deletions packages/binding-file/src/file-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,40 +24,48 @@

const { debug, warn } = createLoggers("binding-file", "file-client");

/**
* Used to determine the Content-Type of a file from the extension in its
* {@link filePath} if no explicit Content-Type is defined.
*
* @param filepath The file path the Content-Type is determined for.
* @returns An appropriate Content-Type or `application/octet-stream` as a fallback.
*/
function mapFileExtensionToContentType(filepath: string) {
const fileExtension = path.extname(filepath);
debug(`FileClient found '${fileExtension}' extension`);
switch (fileExtension) {
case ".txt":
case ".log":
case ".ini":
case ".cfg":
return "text/plain";
case ".json":
return "application/json";
case ".jsontd":
return "application/td+json";
case ".jsonld":
return "application/ld+json";
default:
warn(`FileClient cannot determine media type for path '${filepath}'`);
return "application/octet-stream";
}
}

Check warning on line 53 in packages/binding-file/src/file-client.ts

View check run for this annotation

Codecov / codecov/patch

packages/binding-file/src/file-client.ts#L34-L53

Added lines #L34 - L53 were not covered by tests

export default class FileClient implements ProtocolClient {
public toString(): string {
return "[FileClient]";
}

private async readFile(filepath: string, contentType?: string): Promise<Content> {
const resource = fs.createReadStream(filepath);
const resourceContentType = contentType ?? mapFileExtensionToContentType(filepath);
return new Content(resourceContentType, resource);
}

Check warning on line 65 in packages/binding-file/src/file-client.ts

View check run for this annotation

Codecov / codecov/patch

packages/binding-file/src/file-client.ts#L60-L65

Added lines #L60 - L65 were not covered by tests
public async readResource(form: Form): Promise<Content> {
const filepath = form.href.split("//");
const resource = fs.createReadStream(filepath[1]);
const extension = path.extname(filepath[1]);
debug(`FileClient found '${extension}' extension`);
let contentType;
if (form.contentType != null) {
contentType = form.contentType;
} else {
// *guess* contentType based on file extension
contentType = "application/octet-stream";
switch (extension) {
case ".txt":
case ".log":
case ".ini":
case ".cfg":
contentType = "text/plain";
break;
case ".json":
contentType = "application/json";
break;
case ".jsonld":
contentType = "application/ld+json";
break;
default:
warn(`FileClient cannot determine media type of '${form.href}'`);
}
}
return new Content(contentType, resource);
const filepath = new URL(form.href).pathname;
return this.readFile(filepath, form.contentType);

Check warning on line 68 in packages/binding-file/src/file-client.ts

View check run for this annotation

Codecov / codecov/patch

packages/binding-file/src/file-client.ts#L67-L68

Added lines #L67 - L68 were not covered by tests
}

public async writeResource(form: Form, content: Content): Promise<void> {
Expand All @@ -72,6 +80,13 @@
throw new Error("FileClient does not implement unlink");
}

/**
* @inheritdoc
*/
public async requestThingDescription(uri: string): Promise<Content> {
return this.readFile(uri, "application/td+json");
}

Check warning on line 89 in packages/binding-file/src/file-client.ts

View check run for this annotation

Codecov / codecov/patch

packages/binding-file/src/file-client.ts#L83-L89

Added lines #L83 - L89 were not covered by tests
public async subscribeResource(
form: Form,
next: (value: Content) => void,
Expand Down
12 changes: 12 additions & 0 deletions packages/binding-http/src/http-client-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,18 @@
}
}

/**
* @inheritdoc
*/
public async requestThingDescription(uri: string): Promise<Content> {
const headers: HeadersInit = {
Accept: "application/td+json",
};
const response = await fetch(uri, { headers });
const body = ProtocolHelpers.toNodeStream(response.body as Readable);
return new Content(response.headers.get("content-type") ?? "application/td+json", body);
}

Check warning on line 225 in packages/binding-http/src/http-client-impl.ts

View check run for this annotation

Codecov / codecov/patch

packages/binding-http/src/http-client-impl.ts#L219-L225

Added lines #L219 - L225 were not covered by tests

public async start(): Promise<void> {
// do nothing
}
Expand Down
7 changes: 7 additions & 0 deletions packages/binding-mbus/src/mbus-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@
throw new Error("Method not implemented.");
}

/**
* @inheritdoc
*/
public async requestThingDescription(uri: string): Promise<Content> {
throw new Error("Method not implemented");
}

Check warning on line 69 in packages/binding-mbus/src/mbus-client.ts

View check run for this annotation

Codecov / codecov/patch

packages/binding-mbus/src/mbus-client.ts#L68-L69

Added lines #L68 - L69 were not covered by tests

async start(): Promise<void> {
// do nothing
}
Expand Down
7 changes: 7 additions & 0 deletions packages/binding-modbus/src/modbus-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,13 @@
});
}

/**
* @inheritdoc
*/
public async requestThingDescription(uri: string): Promise<Content> {
throw new Error("Method not implemented");
}

Check warning on line 139 in packages/binding-modbus/src/modbus-client.ts

View check run for this annotation

Codecov / codecov/patch

packages/binding-modbus/src/modbus-client.ts#L138-L139

Added lines #L138 - L139 were not covered by tests

async start(): Promise<void> {
// do nothing
}
Expand Down
7 changes: 7 additions & 0 deletions packages/binding-mqtt/src/mqtt-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,13 @@
}
}

/**
* @inheritdoc
*/
public async requestThingDescription(uri: string): Promise<Content> {
throw new Error("Method not implemented");
relu91 marked this conversation as resolved.
Show resolved Hide resolved
}

Check warning on line 169 in packages/binding-mqtt/src/mqtt-client.ts

View check run for this annotation

Codecov / codecov/patch

packages/binding-mqtt/src/mqtt-client.ts#L168-L169

Added lines #L168 - L169 were not covered by tests

public async start(): Promise<void> {
// do nothing
}
Expand Down
7 changes: 7 additions & 0 deletions packages/binding-netconf/src/netconf-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,13 @@
throw unimplementedError;
}

/**
* @inheritdoc
*/
public async requestThingDescription(uri: string): Promise<Content> {
throw new Error("Method not implemented");
}

Check warning on line 163 in packages/binding-netconf/src/netconf-client.ts

View check run for this annotation

Codecov / codecov/patch

packages/binding-netconf/src/netconf-client.ts#L162-L163

Added lines #L162 - L163 were not covered by tests

public async start(): Promise<void> {
// do nothing
}
Expand Down
7 changes: 7 additions & 0 deletions packages/binding-opcua/src/opcua-protocol-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,13 @@
});
}

/**
* @inheritdoc
*/
public async requestThingDescription(uri: string): Promise<Content> {
throw new Error("Method not implemented");
}

Check warning on line 438 in packages/binding-opcua/src/opcua-protocol-client.ts

View check run for this annotation

Codecov / codecov/patch

packages/binding-opcua/src/opcua-protocol-client.ts#L437-L438

Added lines #L437 - L438 were not covered by tests

start(): Promise<void> {
debug("start: Sorry not implemented");
throw new Error("Method not implemented.");
Expand Down
7 changes: 7 additions & 0 deletions packages/binding-websockets/src/ws-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ export default class WebSocketClient implements ProtocolClient {
throw new Error("Websocket client does not implement subscribeResource");
}

/**
* @inheritdoc
*/
public async requestThingDescription(uri: string): Promise<Content> {
throw new Error("Method not implemented");
}

public async start(): Promise<void> {
// do nothing
}
Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/protocol-interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ export interface ProtocolClient {
complete?: () => void
): Promise<Subscription>;

/**
* Requests a single Thing Description from a given {@link uri}.
*
* The result is returned asynchronously as {@link Content}, which has to
* be deserialized and validated by the upper layers of the implementation.
*
* @param uri
*/
requestThingDescription(uri: string): Promise<Content>;

/** start the client (ensure it is ready to send requests) */
start(): Promise<void>;
/** stop the client */
Expand Down
17 changes: 16 additions & 1 deletion packages/core/src/wot-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import ConsumedThing from "./consumed-thing";
import Helpers from "./helpers";
import { createLoggers } from "./logger";
import ContentManager from "./content-serdes";
import { ErrorObject } from "ajv";

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

Expand All @@ -39,8 +41,21 @@
throw new Error("not implemented");
}

/** @inheritDoc */
async requestThingDescription(url: string): Promise<WoT.ThingDescription> {
throw new Error("not implemented");
const uriScheme = Helpers.extractScheme(url);
const client = this.srv.getClientFor(uriScheme);
const content = await client.requestThingDescription(url);
const value = ContentManager.contentToValue({ type: content.type, body: await content.toBuffer() }, {});

const isValidThingDescription = Helpers.tsSchemaValidator(value);

if (!isValidThingDescription) {
const errors = Helpers.tsSchemaValidator.errors?.map((o: ErrorObject) => o.message).join("\n");
throw new Error(errors);
}

return value as WoT.ThingDescription;

Check warning on line 58 in packages/core/src/wot-impl.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/wot-impl.ts#L46-L58

Added lines #L46 - L58 were not covered by tests
}

/** @inheritDoc */
Expand Down
12 changes: 12 additions & 0 deletions packages/core/test/ClientTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ class TDClient implements ProtocolClient {
return new Subscription();
}

async requestThingDescription(uri: string): Promise<Content> {
throw new Error("Method not implemented.");
}

public async start(): Promise<void> {
// do nothing
}
Expand Down Expand Up @@ -230,6 +234,10 @@ class TrapClient implements ProtocolClient {
return new Subscription();
}

async requestThingDescription(uri: string): Promise<Content> {
throw new Error("Method not implemented.");
}

public async start(): Promise<void> {
// do nothing
}
Expand Down Expand Up @@ -293,6 +301,10 @@ class TestProtocolClient implements ProtocolClient {
throw new Error("Method not implemented.");
}

async requestThingDescription(uri: string): Promise<Content> {
throw new Error("Method not implemented.");
}

async start(): Promise<void> {
throw new Error("Method not implemented.");
}
Expand Down
Loading