Skip to content

Commit

Permalink
Merge pull request #94 from eclipse-thingweb/explore-directory-variables
Browse files Browse the repository at this point in the history
feat!: add initial support for query parameters to exploreDirectory method
  • Loading branch information
JKRhb authored Jan 5, 2024
2 parents 7adfed6 + 17334a9 commit 8b7e826
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 8 deletions.
29 changes: 29 additions & 0 deletions example/directory_discovery.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// SPDX-License-Identifier: BSD-3-Clause

// ignore_for_file: avoid_print

import "package:dart_wot/binding_http.dart";
import "package:dart_wot/core.dart";

Future<void> main(List<String> args) async {
final servient = Servient(
clientFactories: [
HttpClientFactory(),
],
);

final wot = await servient.start();
// FIXME(JRKhb): The "things" property currently points to "localhost",
// preventing this example from working
final url = Uri.parse("https://zion.vaimee.com/.well-known/wot");

final thingDiscovery = await wot.exploreDirectory(url);

await for (final thingDescription in thingDiscovery) {
print(thingDescription);
}
}
22 changes: 18 additions & 4 deletions lib/src/core/implementation/wot.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,17 @@ class WoT implements scripting_api.WoT {

@override
Future<scripting_api.ThingDiscoveryProcess> exploreDirectory(
Uri url, [
Uri url, {
scripting_api.ThingFilter? filter,
]) async {
int? offset,
int? limit,
scripting_api.DirectoryPayloadFormat? format,
}) async {
// TODO(JKRhb): Add support for the collection format.
if (format == scripting_api.DirectoryPayloadFormat.collection) {
throw ArgumentError('Format "$format" is currently not supported.');
}

final thingDescription = await requestThingDescription(url);

if (!thingDescription.isValidDirectoryThingDescription) {
Expand All @@ -124,8 +132,14 @@ class WoT implements scripting_api.WoT {

final consumedDirectoryThing = await consume(thingDescription);

final interactionOutput =
await consumedDirectoryThing.readProperty("things");
final interactionOutput = await consumedDirectoryThing.readProperty(
"things",
uriVariables: {
if (offset != null) "offset": offset,
if (limit != null) "limit": limit,
if (format != null) "format": format.toString(),
},
);
final rawThingDescriptions = await interactionOutput.value();

if (rawThingDescriptions is! List<Object?>) {
Expand Down
36 changes: 34 additions & 2 deletions lib/src/core/scripting_api/wot.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,35 @@ import "discovery/thing_filter.dart";
import "exposed_thing.dart";
import "types.dart";

/// Enumeration for specifying the value of the `format` query parameter when
/// using the `exploreDirectory` discovery method.
///
/// See [section 7.3.2.1.5] of the [WoT Discovery] specification for more
/// information.
///
/// [WoT Discovery]: https://www.w3.org/TR/2023/REC-wot-discovery-20231205
/// [section 7.3.2.1.5]: https://www.w3.org/TR/2023/REC-wot-discovery-20231205/#exploration-directory-api-things-listing
enum DirectoryPayloadFormat {
/// Indicates that an array of Thing Descriptions should be returned.
///
/// This is the default value.
array,

/// Indicates that an collection of Thing Descriptions should be returned.
collection,
;

@override
String toString() {
switch (this) {
case array:
return "array";
case collection:
return "collection";
}
}
}

/// Interface for a [WoT] runtime.
///
/// See WoT Scripting API specification,
Expand All @@ -39,9 +68,12 @@ abstract interface class WoT {
/// [ThingDescription] objects for Thing Descriptions that match an optional
/// [filter] argument of type [ThingFilter].
Future<ThingDiscoveryProcess> exploreDirectory(
Uri url, [
Uri url, {
ThingFilter? filter,
]);
int? offset,
int? limit,
DirectoryPayloadFormat? format,
});

/// Discovers [ThingDescription]s from a given [url] using the specified
/// [method].
Expand Down
76 changes: 74 additions & 2 deletions test/core/discovery_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ final directoryTestUri2 = Uri.parse("$testUriScheme://[::4]/.well-known/wot");
final directoryTestThingsUri2 = Uri.parse("$testUriScheme://[::4]/things");
final directoryTestUri3 = Uri.parse("$testUriScheme://[::5]/.well-known/wot");
final directoryTestThingsUri3 = Uri.parse("$testUriScheme://[::5]/things");
final directoryTestUri4 = Uri.parse("$testUriScheme://[::6]/.well-known/wot");
final directoryTestThingsUri4 = Uri.parse(
"$testUriScheme://[::3]/things?offset=2&limit=3&format=array",
);

const validTestTitle1 = "Test TD 1";
const validTestThingDescription = '''
Expand Down Expand Up @@ -49,6 +53,26 @@ final directoryThingDescription1 = '''
},
"properties": {
"things": {
"uriVariables": {
"offset": {
"title": "Number of TDs to skip before the page",
"type": "number",
"default": 0
},
"limit": {
"title": "Number of TDs in a page",
"type": "number"
},
"format": {
"title": "Payload format",
"type": "string",
"enum": [
"array",
"collection"
],
"default": "array"
}
},
"forms": [
{
"href": "$directoryTestThingsUri1"
Expand Down Expand Up @@ -128,8 +152,9 @@ class _MockedProtocolClient implements ProtocolClient {
}

@override
Future<Content> readResource(Form form) async {
final href = form.href;
Future<Content> readResource(AugmentedForm form) async {
final href = form.resolvedHref;

if (href == directoryTestThingsUri1) {
return "[$validTestThingDescription]".toContent("application/td+json");
}
Expand All @@ -142,6 +167,10 @@ class _MockedProtocolClient implements ProtocolClient {
return invalidTestThingDescription2.toContent("application/td+json");
}

if (href == directoryTestThingsUri4) {
return "[$validTestThingDescription]".toContent("application/ld+json");
}

throw StateError("Encountered an unknown URI $href.");
}

Expand All @@ -167,6 +196,10 @@ class _MockedProtocolClient implements ProtocolClient {
return directoryThingDescription3.toDiscoveryContent(url);
}

if (url == directoryTestUri4) {
return directoryThingDescription1.toDiscoveryContent(url);
}

throw StateError("Encountered invalid URL.");
}

Expand Down Expand Up @@ -377,5 +410,44 @@ void main() {
await thingDiscoveryProcess.stop();
expect(thingDiscoveryProcess.done, true);
});

test("should support the experimental query parameters API", () async {
final servient = Servient(
clientFactories: [
_MockedProtocolClientFactory(),
],
);

final wot = await servient.start();
final thingDiscoveryProcess = await wot.exploreDirectory(
directoryTestUri4,
offset: 2,
limit: 3,
format: DirectoryPayloadFormat.array,
);

var counter = 0;
await for (final thingDescription in thingDiscoveryProcess) {
counter++;
expect(thingDescription.title, validTestTitle1);
}
expect(counter, 1);
expect(thingDiscoveryProcess.done, true);
});

test(
'should currently not support the "collection" format when using the '
"experimental query parameters API", () async {
final servient = Servient();
final wot = await servient.start();

expect(
() async => await wot.exploreDirectory(
directoryTestUri4,
format: DirectoryPayloadFormat.collection,
),
throwsArgumentError,
);
});
});
}

0 comments on commit 8b7e826

Please sign in to comment.