-
Notifications
You must be signed in to change notification settings - Fork 80
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(core): implement exploreDirectory
method
#1186
Merged
danielpeintner
merged 21 commits into
eclipse-thingweb:master
from
JKRhb:explorer-directory
Dec 21, 2023
Merged
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
bfcf85c
feat(content-serdes): add application/ld+json to supported Content-Types
JKRhb 77b916f
feat(core): implement `exploreDirectory` method
JKRhb e9c4881
fixup! feat(core): implement `exploreDirectory` method
JKRhb 4a50219
fixup! feat(core): implement `exploreDirectory` method
JKRhb 455af97
fixup! feat(core): implement `exploreDirectory` method
JKRhb 9faa9d2
test: add test for `exploreDirectory` method
JKRhb db19eae
fixup! test: add test for `exploreDirectory` method
JKRhb 7eddb7f
fixup! feat(core): implement `exploreDirectory` method
JKRhb 764d9fa
fixup! feat(core): implement `exploreDirectory` method
JKRhb 0514567
fixup! feat(core): implement `exploreDirectory` method
JKRhb 57affb8
fixup! feat(core): implement `exploreDirectory` method
JKRhb 951ca10
fixup! test: add test for `exploreDirectory` method
JKRhb cddd356
fixup! test: add test for `exploreDirectory` method
JKRhb 99e102b
fixup! feat(core): implement `exploreDirectory` method
JKRhb 6f11544
fixup! feat(core): implement `exploreDirectory` method
JKRhb 568ebcc
refactor(core): use validation functions for requestThingDescription
JKRhb 5e17c5f
fixup! test: add test for `exploreDirectory` method
JKRhb a06e024
fixup! test: add test for `exploreDirectory` method
JKRhb 66104cf
fixup! feat(core): implement `exploreDirectory` method
JKRhb c8fc93f
fixup! test: add test for `exploreDirectory` method
JKRhb 7cc777f
fixup! feat(core): implement `exploreDirectory` method
JKRhb File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/******************************************************************************** | ||
* Copyright (c) 2023 Contributors to the Eclipse Foundation | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information regarding copyright ownership. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License v. 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0, or the W3C Software Notice and | ||
* Document License (2015-05-13) which is available at | ||
* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document. | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 | ||
********************************************************************************/ | ||
|
||
import { ErrorObject } from "ajv"; | ||
import Helpers from "./helpers"; | ||
|
||
export function isThingDescription(input: unknown): input is WoT.ThingDescription { | ||
return Helpers.tsSchemaValidator(input); | ||
} | ||
|
||
export function getLastValidationErrors() { | ||
const errors = Helpers.tsSchemaValidator.errors?.map((o: ErrorObject) => o.message).join("\n"); | ||
return new Error(errors); | ||
} | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
/******************************************************************************** | ||
* Copyright (c) 2023 Contributors to the Eclipse Foundation | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information regarding copyright ownership. | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Eclipse Public License v. 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0, or the W3C Software Notice and | ||
* Document License (2015-05-13) which is available at | ||
* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document. | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 | ||
********************************************************************************/ | ||
|
||
import { Form, SecurityScheme } from "@node-wot/td-tools"; | ||
import { Subscription } from "rxjs/Subscription"; | ||
import { Content } from "../src/content"; | ||
import { createLoggers } from "../src/logger"; | ||
import { ProtocolClient, ProtocolClientFactory } from "../src/protocol-interfaces"; | ||
import Servient from "../src/servient"; | ||
import { Readable } from "stream"; | ||
import { expect } from "chai"; | ||
|
||
const { debug, error } = createLoggers("core", "DiscoveryTest"); | ||
|
||
function createDirectoryTestTd(title: string, thingsPropertyHref: string) { | ||
return { | ||
"@context": "https://www.w3.org/2022/wot/td/v1.1", | ||
title, | ||
security: "nosec_sc", | ||
securityDefinitions: { | ||
nosec_sc: { | ||
scheme: "nosec", | ||
}, | ||
}, | ||
properties: { | ||
things: { | ||
forms: [ | ||
{ | ||
href: thingsPropertyHref, | ||
}, | ||
], | ||
}, | ||
}, | ||
}; | ||
} | ||
|
||
function createDiscoveryContent(td: unknown, contentType: string) { | ||
const buffer = Buffer.from(JSON.stringify(td)); | ||
const content = new Content(contentType, Readable.from(buffer)); | ||
return content; | ||
} | ||
|
||
const directoryTdUrl1 = "test://localhost/valid-output-tds"; | ||
const directoryTdUrl2 = "test://localhost/invalid-output-tds"; | ||
const directoryTdUrl3 = "test://localhost/no-array-output"; | ||
|
||
const directoryTdTitle1 = "Directory Test TD 1"; | ||
const directoryTdTitle2 = "Directory Test TD 2"; | ||
const directoryTdTitle3 = "Directory Test TD 3"; | ||
|
||
const directoryThingsUrl1 = "test://localhost/things1"; | ||
const directoryThingsUrl2 = "test://localhost/things2"; | ||
const directoryThingsUrl3 = "test://localhost/things3"; | ||
|
||
const directoryThingDescription1 = createDirectoryTestTd(directoryTdTitle1, directoryThingsUrl1); | ||
const directoryThingDescription2 = createDirectoryTestTd(directoryTdTitle2, directoryThingsUrl2); | ||
const directoryThingDescription3 = createDirectoryTestTd(directoryTdTitle3, directoryThingsUrl3); | ||
|
||
class TestProtocolClient implements ProtocolClient { | ||
async readResource(form: Form): Promise<Content> { | ||
const href = form.href; | ||
|
||
switch (href) { | ||
case directoryThingsUrl1: | ||
return createDiscoveryContent([directoryThingDescription1], "application/ld+json"); | ||
case directoryThingsUrl2: | ||
return createDiscoveryContent(["I am an invalid TD!"], "application/ld+json"); | ||
case directoryThingsUrl3: | ||
return createDiscoveryContent("I am no array and therefore invalid!", "application/ld+json"); | ||
} | ||
|
||
throw new Error("Invalid URL"); | ||
} | ||
|
||
writeResource(form: Form, content: Content): Promise<void> { | ||
throw new Error("Method not implemented."); | ||
} | ||
|
||
invokeResource(form: Form, content?: Content | undefined): Promise<Content> { | ||
throw new Error("Method not implemented."); | ||
} | ||
|
||
unlinkResource(form: Form): Promise<void> { | ||
throw new Error("Method not implemented."); | ||
} | ||
|
||
subscribeResource( | ||
form: Form, | ||
next: (content: Content) => void, | ||
error?: ((error: Error) => void) | undefined, | ||
complete?: (() => void) | undefined | ||
): Promise<Subscription> { | ||
throw new Error("Method not implemented."); | ||
} | ||
|
||
async requestThingDescription(uri: string): Promise<Content> { | ||
switch (uri) { | ||
case directoryTdUrl1: | ||
debug(`Found corrent URL ${uri} to fetch directory TD`); | ||
return createDiscoveryContent(directoryThingDescription1, "application/td+json"); | ||
case directoryTdUrl2: | ||
debug(`Found corrent URL ${uri} to fetch directory TD`); | ||
return createDiscoveryContent(directoryThingDescription2, "application/td+json"); | ||
case directoryTdUrl3: | ||
debug(`Found corrent URL ${uri} to fetch directory TD`); | ||
return createDiscoveryContent(directoryThingDescription3, "application/td+json"); | ||
} | ||
|
||
throw Error("Invalid URL"); | ||
} | ||
|
||
async start(): Promise<void> { | ||
// Do nothing | ||
} | ||
|
||
async stop(): Promise<void> { | ||
// Do nothing | ||
} | ||
|
||
setSecurity(metadata: SecurityScheme[], credentials?: unknown): boolean { | ||
return true; | ||
} | ||
} | ||
|
||
class TestProtocolClientFactory implements ProtocolClientFactory { | ||
public scheme = "test"; | ||
|
||
getClient(): ProtocolClient { | ||
return new TestProtocolClient(); | ||
} | ||
|
||
init(): boolean { | ||
return true; | ||
} | ||
|
||
destroy(): boolean { | ||
return true; | ||
} | ||
} | ||
|
||
describe("Discovery Tests", () => { | ||
it("should be possible to use the exploreDirectory method", async () => { | ||
const servient = new Servient(); | ||
servient.addClientFactory(new TestProtocolClientFactory()); | ||
|
||
const WoT = await servient.start(); | ||
|
||
const discoveryProcess = await WoT.exploreDirectory(directoryTdUrl1); | ||
|
||
let tdCounter = 0; | ||
for await (const thingDescription of discoveryProcess) { | ||
expect(thingDescription.title).to.eql(directoryTdTitle1); | ||
tdCounter++; | ||
} | ||
expect(tdCounter).to.eql(1); | ||
expect(discoveryProcess.error).to.eq(undefined); | ||
}); | ||
|
||
it("should receive no output and an error by the exploreDirectory method for invalid returned TDs", async () => { | ||
const servient = new Servient(); | ||
servient.addClientFactory(new TestProtocolClientFactory()); | ||
|
||
const WoT = await servient.start(); | ||
|
||
const discoveryProcess = await WoT.exploreDirectory(directoryTdUrl2); | ||
|
||
let tdCounter = 0; | ||
for await (const thingDescription of discoveryProcess) { | ||
error(`Encountered unexpected TD with title ${thingDescription.title}`); | ||
tdCounter++; | ||
} | ||
expect(tdCounter).to.eql(0); | ||
expect(discoveryProcess.error).to.not.eq(undefined); | ||
}); | ||
|
||
it("should receive no output and an error by the exploreDirectory method if no array is returned", async () => { | ||
const servient = new Servient(); | ||
servient.addClientFactory(new TestProtocolClientFactory()); | ||
|
||
const WoT = await servient.start(); | ||
|
||
const discoveryProcess = await WoT.exploreDirectory(directoryTdUrl3); | ||
|
||
let tdCounter = 0; | ||
for await (const thingDescription of discoveryProcess) { | ||
error(`Encountered unexpected TD with title ${thingDescription.title}`); | ||
tdCounter++; | ||
} | ||
expect(tdCounter).to.eql(0); | ||
expect(discoveryProcess.error).to.not.eq(undefined); | ||
}); | ||
|
||
it("should be possible to stop discovery with exploreDirectory prematurely", async () => { | ||
const servient = new Servient(); | ||
servient.addClientFactory(new TestProtocolClientFactory()); | ||
|
||
const WoT = await servient.start(); | ||
|
||
const discoveryProcess = await WoT.exploreDirectory(directoryTdUrl1); | ||
expect(discoveryProcess.done).to.not.eq(true); | ||
discoveryProcess.stop(); | ||
expect(discoveryProcess.done).to.eq(true); | ||
|
||
let tdCounter = 0; | ||
for await (const thingDescription of discoveryProcess) { | ||
error(`Encountered unexpected TD with title ${thingDescription.title}`); | ||
tdCounter++; | ||
} | ||
expect(tdCounter).to.eql(0); | ||
expect(discoveryProcess.error).to.eq(undefined); | ||
}); | ||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No strong opinion but the term might convey something which is not always the case. It is correct, it is the "last" validation. Anyhow, if used in some other code parts it might report errors of another validation step (not necessarily the one from last
isThingDescription
) that usedHelpers.tsSchemaValidator
..Do you know what I mean.