Skip to content

Commit d375c7d

Browse files
committed
fixup! Implement ExposedThing functionality
1 parent 2e8dd71 commit d375c7d

File tree

5 files changed

+151
-7
lines changed

5 files changed

+151
-7
lines changed

example/exposed_thing/http_server.dart

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,21 @@ void main() async {
3333
],
3434
},
3535
},
36+
"actions": {
37+
"toggle": {
38+
"input": {
39+
"type": "boolean",
40+
},
41+
"output": {
42+
"type": "null",
43+
},
44+
"forms": [
45+
{
46+
"href": "/toggle",
47+
}
48+
],
49+
},
50+
},
3651
});
3752

3853
exposedThing
@@ -57,10 +72,21 @@ void main() async {
5772
}
5873

5974
throw const FormatException();
75+
})
76+
..setActionHandler("toggle", (
77+
actionInput, {
78+
data,
79+
formIndex,
80+
uriVariables,
81+
}) async {
82+
print(await actionInput.value());
83+
84+
return InteractionInput.fromNull();
6085
});
6186

6287
final thingDescription = await wot
6388
.requestThingDescription(Uri.parse("http://localhost:3000/test"));
89+
print(thingDescription.toJson());
6490
final consumedThing = await wot.consume(thingDescription);
6591

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

103+
final actionOutput = await consumedThing.invokeAction(
104+
"toggle",
105+
input: InteractionInput.fromBoolean(true),
106+
);
107+
108+
print(await actionOutput.value());
109+
77110
await servient.shutdown();
78111
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
//
5+
// SPDX-License-Identifier: BSD-3-Clause
6+
7+
import "../../core.dart";
8+
9+
/// Extension for determining the HTTP method that corresponds with an
10+
/// [OperationType].
11+
extension HttpMethodExtension on OperationType {
12+
/// Returns the default HTTP method as defined in the [HTTP binding template]
13+
/// specification.
14+
///
15+
/// If the [OperationType] value has no default method defined, an
16+
/// [ArgumentError] will be thrown.
17+
///
18+
/// [HTTP binding template]: https://w3c.github.io/wot-binding-templates/bindings/protocols/http/#http-default-vocabulary-terms
19+
String get defaultHttpMethod {
20+
switch (this) {
21+
case OperationType.readproperty:
22+
return "GET";
23+
case OperationType.writeproperty:
24+
return "PUT";
25+
case OperationType.invokeaction:
26+
return "POST";
27+
case OperationType.readallproperties:
28+
return "GET";
29+
case OperationType.writeallproperties:
30+
return "PUT";
31+
case OperationType.readmultipleproperties:
32+
return "GET";
33+
case OperationType.writemultipleproperties:
34+
return "PUT";
35+
default:
36+
throw ArgumentError(
37+
"OperationType $this has no default HTTP method defined.",
38+
);
39+
}
40+
}
41+
}

lib/src/binding_http/http_server.dart

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import "package:shelf_router/shelf_router.dart";
1313
import "../../core.dart" hide ExposedThing;
1414

1515
import "http_config.dart";
16+
import "http_extensions.dart";
1617

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

110113
return Response(
@@ -120,14 +123,16 @@ final class HttpServer implements ProtocolServer {
120123
Form(
121124
affordanceUri,
122125
op: const [
123-
OperationType.readproperty,
126+
operationType,
124127
],
125128
),
126129
);
127130
}
128131

129132
if (!readOnly) {
130-
router.put(path, (request) async {
133+
const operationType = OperationType.writeproperty;
134+
final methodName = operationType.defaultHttpMethod;
135+
router.add(methodName, path, (request) async {
131136
if (request is! Request) {
132137
throw Exception();
133138
}
@@ -151,14 +156,16 @@ final class HttpServer implements ProtocolServer {
151156
Form(
152157
affordanceUri,
153158
op: const [
154-
OperationType.writeproperty,
159+
operationType,
155160
],
156161
),
157162
);
158163
}
159164
// TODO: Handle observe
160165
case Action():
161-
router.post(path, (request) async {
166+
const operationType = OperationType.invokeaction;
167+
final methodName = operationType.defaultHttpMethod;
168+
router.add(methodName, path, (request) async {
162169
if (request is! Request) {
163170
throw Exception();
164171
}
@@ -167,13 +174,24 @@ final class HttpServer implements ProtocolServer {
167174
request.mimeType ?? "application/json",
168175
request.read(),
169176
);
170-
await thing.handleWriteProperty(affordance.key, content);
177+
final blah =
178+
await thing.handleInvokeAction(affordance.key, content);
171179

172180
return Response(
181+
body: blah?.body,
173182
204,
174183
);
175184
});
176185

186+
affordanceValue.forms.add(
187+
Form(
188+
affordanceUri,
189+
op: const [
190+
operationType,
191+
],
192+
),
193+
);
194+
177195
// TODO: Handle observe
178196
case Event():
179197
// TODO: Implement

lib/src/core/implementation/exposed_thing.dart

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ class ExposedThing implements scripting_api.ExposedThing, ExposableThing {
3030
final Map<String, scripting_api.PropertyWriteHandler> _propertyWriteHandlers =
3131
{};
3232

33+
final Map<String, scripting_api.ActionHandler> _actionHandlers = {};
34+
3335
@override
3436
Future<void> emitPropertyChange(String name) {
3537
// TODO(JKRhb): implement emitPropertyChange
@@ -60,7 +62,7 @@ class ExposedThing implements scripting_api.ExposedThing, ExposableThing {
6062

6163
@override
6264
void setActionHandler(String name, scripting_api.ActionHandler handler) {
63-
// TODO(JKRhb): implement setActionHandler
65+
_actionHandlers[name] = handler;
6466
}
6567

6668
@override
@@ -180,4 +182,45 @@ class ExposedThing implements scripting_api.ExposedThing, ExposableThing {
180182
data: data,
181183
);
182184
}
185+
186+
@override
187+
Future<Content?> handleInvokeAction(
188+
String actionName,
189+
Content input, {
190+
int? formIndex,
191+
Map<String, Object>? uriVariables,
192+
Object? data,
193+
}) async {
194+
final actionHandler = _actionHandlers[actionName];
195+
196+
if (actionHandler == null) {
197+
throw Exception(
198+
"Action handler for action $actionName is not defined.",
199+
);
200+
}
201+
202+
final action = thingDescription.actions?[actionName];
203+
204+
final processedInput = InteractionOutput(
205+
input,
206+
_servient.contentSerdes,
207+
// FIXME: Providing a form does not really make sense here.
208+
Form(Uri()),
209+
action?.input,
210+
);
211+
212+
final actionOutput = await actionHandler(
213+
processedInput,
214+
formIndex: formIndex,
215+
uriVariables: uriVariables,
216+
data: data,
217+
);
218+
219+
return Content.fromInteractionInput(
220+
actionOutput,
221+
"application/json",
222+
_servient.contentSerdes,
223+
null,
224+
);
225+
}
183226
}

lib/src/core/protocol_interfaces/exposable_thing.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,13 @@ abstract interface class ExposableThing {
2929
Map<String, Object>? uriVariables,
3030
Object? data,
3131
});
32+
33+
/// Handles a `invokeaction` operation triggered by a TD consumer.
34+
Future<Content?> handleInvokeAction(
35+
String propertyName,
36+
Content input, {
37+
int? formIndex,
38+
Map<String, Object>? uriVariables,
39+
Object? data,
40+
});
3241
}

0 commit comments

Comments
 (0)