Skip to content

Commit

Permalink
fixup! Implement ExposedThing functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
JKRhb committed Jun 15, 2024
1 parent 2e8dd71 commit d375c7d
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 7 deletions.
33 changes: 33 additions & 0 deletions example/exposed_thing/http_server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,21 @@ void main() async {
],
},
},
"actions": {
"toggle": {
"input": {
"type": "boolean",
},
"output": {
"type": "null",
},
"forms": [
{
"href": "/toggle",
}
],
},
},
});

exposedThing
Expand All @@ -57,10 +72,21 @@ void main() async {
}

throw const FormatException();
})
..setActionHandler("toggle", (
actionInput, {
data,
formIndex,
uriVariables,
}) async {
print(await actionInput.value());

return InteractionInput.fromNull();
});

final thingDescription = await wot
.requestThingDescription(Uri.parse("http://localhost:3000/test"));
print(thingDescription.toJson());
final consumedThing = await wot.consume(thingDescription);

var value = await (await consumedThing.readProperty("status")).value();
Expand All @@ -74,5 +100,12 @@ void main() async {
value = await (await consumedThing.readProperty("status")).value();
print(value);

final actionOutput = await consumedThing.invokeAction(
"toggle",
input: InteractionInput.fromBoolean(true),
);

print(await actionOutput.value());

await servient.shutdown();
}
41 changes: 41 additions & 0 deletions lib/src/binding_http/http_extensions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// 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

import "../../core.dart";

/// Extension for determining the HTTP method that corresponds with an
/// [OperationType].
extension HttpMethodExtension on OperationType {
/// Returns the default HTTP method as defined in the [HTTP binding template]
/// specification.
///
/// If the [OperationType] value has no default method defined, an
/// [ArgumentError] will be thrown.
///
/// [HTTP binding template]: https://w3c.github.io/wot-binding-templates/bindings/protocols/http/#http-default-vocabulary-terms
String get defaultHttpMethod {
switch (this) {
case OperationType.readproperty:
return "GET";
case OperationType.writeproperty:
return "PUT";
case OperationType.invokeaction:
return "POST";
case OperationType.readallproperties:
return "GET";
case OperationType.writeallproperties:
return "PUT";
case OperationType.readmultipleproperties:
return "GET";
case OperationType.writemultipleproperties:
return "PUT";
default:
throw ArgumentError(
"OperationType $this has no default HTTP method defined.",
);
}
}
}
30 changes: 24 additions & 6 deletions lib/src/binding_http/http_server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import "package:shelf_router/shelf_router.dart";
import "../../core.dart" hide ExposedThing;

import "http_config.dart";
import "http_extensions.dart";

/// A [ProtocolServer] for the Hypertext Transfer Protocol (HTTP).
final class HttpServer implements ProtocolServer {
Expand Down Expand Up @@ -104,7 +105,9 @@ final class HttpServer implements ProtocolServer {
// TODO: Handle values from protocol bindings
case Property(:final readOnly, :final writeOnly):
if (!writeOnly) {
router.get(path, (request) async {
const operationType = OperationType.readproperty;
final methodName = operationType.defaultHttpMethod;
router.add(methodName, path, (request) async {
final content = await thing.handleReadProperty(affordance.key);

return Response(
Expand All @@ -120,14 +123,16 @@ final class HttpServer implements ProtocolServer {
Form(
affordanceUri,
op: const [
OperationType.readproperty,
operationType,
],
),
);
}

if (!readOnly) {
router.put(path, (request) async {
const operationType = OperationType.writeproperty;
final methodName = operationType.defaultHttpMethod;
router.add(methodName, path, (request) async {
if (request is! Request) {
throw Exception();
}
Expand All @@ -151,14 +156,16 @@ final class HttpServer implements ProtocolServer {
Form(
affordanceUri,
op: const [
OperationType.writeproperty,
operationType,
],
),
);
}
// TODO: Handle observe
case Action():
router.post(path, (request) async {
const operationType = OperationType.invokeaction;
final methodName = operationType.defaultHttpMethod;
router.add(methodName, path, (request) async {
if (request is! Request) {
throw Exception();
}
Expand All @@ -167,13 +174,24 @@ final class HttpServer implements ProtocolServer {
request.mimeType ?? "application/json",
request.read(),
);
await thing.handleWriteProperty(affordance.key, content);
final blah =
await thing.handleInvokeAction(affordance.key, content);

return Response(
body: blah?.body,
204,
);
});

affordanceValue.forms.add(
Form(
affordanceUri,
op: const [
operationType,
],
),
);

// TODO: Handle observe
case Event():
// TODO: Implement
Expand Down
45 changes: 44 additions & 1 deletion lib/src/core/implementation/exposed_thing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class ExposedThing implements scripting_api.ExposedThing, ExposableThing {
final Map<String, scripting_api.PropertyWriteHandler> _propertyWriteHandlers =
{};

final Map<String, scripting_api.ActionHandler> _actionHandlers = {};

@override
Future<void> emitPropertyChange(String name) {
// TODO(JKRhb): implement emitPropertyChange
Expand Down Expand Up @@ -60,7 +62,7 @@ class ExposedThing implements scripting_api.ExposedThing, ExposableThing {

@override
void setActionHandler(String name, scripting_api.ActionHandler handler) {
// TODO(JKRhb): implement setActionHandler
_actionHandlers[name] = handler;
}

@override
Expand Down Expand Up @@ -180,4 +182,45 @@ class ExposedThing implements scripting_api.ExposedThing, ExposableThing {
data: data,
);
}

@override
Future<Content?> handleInvokeAction(
String actionName,
Content input, {
int? formIndex,
Map<String, Object>? uriVariables,
Object? data,
}) async {
final actionHandler = _actionHandlers[actionName];

if (actionHandler == null) {
throw Exception(
"Action handler for action $actionName is not defined.",
);
}

final action = thingDescription.actions?[actionName];

final processedInput = InteractionOutput(
input,
_servient.contentSerdes,
// FIXME: Providing a form does not really make sense here.
Form(Uri()),
action?.input,
);

final actionOutput = await actionHandler(
processedInput,
formIndex: formIndex,
uriVariables: uriVariables,
data: data,
);

return Content.fromInteractionInput(
actionOutput,
"application/json",
_servient.contentSerdes,
null,
);
}
}
9 changes: 9 additions & 0 deletions lib/src/core/protocol_interfaces/exposable_thing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,13 @@ abstract interface class ExposableThing {
Map<String, Object>? uriVariables,
Object? data,
});

/// Handles a `invokeaction` operation triggered by a TD consumer.
Future<Content?> handleInvokeAction(
String propertyName,
Content input, {
int? formIndex,
Map<String, Object>? uriVariables,
Object? data,
});
}

0 comments on commit d375c7d

Please sign in to comment.