Skip to content

Commit 943c75d

Browse files
authored
Merge pull request #86 from eclipse-thingweb/dataschema-value-improvements
feat: improve DataSchemaValue handling
2 parents e6610fe + 2feb002 commit 943c75d

27 files changed

+1198
-247
lines changed

example/coap_discovery.dart

+8-2
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,16 @@ Future<void> handleThingDescription(
2121
ThingDescription thingDescription,
2222
) async {
2323
final consumedThing = await wot.consume(thingDescription);
24-
await consumedThing.writeProperty(propertyName, 'Hello World!');
24+
await consumedThing.writeProperty(
25+
propertyName,
26+
'Hello World'.asInteractionInput(),
27+
);
2528
var output = await consumedThing.readProperty(propertyName);
2629
await output.printValue();
27-
await consumedThing.writeProperty(propertyName, 'Bye World!');
30+
await consumedThing.writeProperty(
31+
propertyName,
32+
'Bye Value'.asInteractionInput(),
33+
);
2834
output = await consumedThing.readProperty(propertyName);
2935
await output.printValue();
3036
}

example/mqtt_example.dart

+7-5
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,15 @@ Future<void> main(List<String> args) async {
8585
},
8686
);
8787

88-
await consumedThing.invokeAction('toggle', input: 'Hello World!');
89-
await consumedThing.invokeAction('toggle', input: 'Hello World!');
90-
await consumedThing.invokeAction('toggle', input: 'Hello World!');
91-
await consumedThing.invokeAction('toggle', input: 'Hello World!');
88+
final actionInput = 'Hello World'.asInteractionInput();
89+
90+
await consumedThing.invokeAction('toggle', input: actionInput);
91+
await consumedThing.invokeAction('toggle', input: actionInput);
92+
await consumedThing.invokeAction('toggle', input: actionInput);
93+
await consumedThing.invokeAction('toggle', input: actionInput);
9294
await subscription.stop();
9395

94-
await consumedThing.invokeAction('toggle', input: 'Bye World!');
96+
await consumedThing.invokeAction('toggle', input: actionInput);
9597
await consumedThing.readAndPrintProperty('status');
9698
print('Done!');
9799
}

lib/scripting_api.dart

+2
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111
library scripting_api;
1212

1313
export 'src/scripting_api/consumed_thing.dart';
14+
export 'src/scripting_api/data_schema_value.dart';
1415
export 'src/scripting_api/discovery/discovery_method.dart';
1516
export 'src/scripting_api/discovery/thing_discovery.dart';
1617
export 'src/scripting_api/discovery/thing_filter.dart';
1718
export 'src/scripting_api/exposed_thing.dart';
19+
export 'src/scripting_api/interaction_input.dart';
1820
export 'src/scripting_api/interaction_output.dart';
1921
export 'src/scripting_api/subscription.dart';
2022
export 'src/scripting_api/types.dart';

lib/src/core/codecs/cbor_codec.dart

+13-5
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,34 @@
77
import 'package:cbor/cbor.dart' as cbor;
88

99
import '../../definitions/data_schema.dart';
10+
import '../../scripting_api/data_schema_value.dart';
1011
import 'content_codec.dart';
1112

1213
/// A [ContentCodec] that encodes and decodes CBOR data.
1314
class CborCodec extends ContentCodec {
1415
@override
1516
List<int> valueToBytes(
16-
Object? value,
17+
DataSchemaValue? dataSchemaValue,
1718
DataSchema? dataSchema,
1819
Map<String, String>? parameters,
1920
) {
20-
return cbor.cborEncode(cbor.CborValue(value));
21+
if (dataSchemaValue == null) {
22+
return [];
23+
}
24+
25+
final cborValue = cbor.CborValue(dataSchemaValue.value);
26+
27+
return cbor.cborEncode(cborValue);
2128
}
2229

2330
@override
24-
Object? bytesToValue(
31+
DataSchemaValue? bytesToValue(
2532
List<int> bytes,
2633
DataSchema? dataSchema,
2734
Map<String, String>? parameters,
2835
) {
29-
// TODO(JKRhb): Use dataSchema for validation
30-
return cbor.cborDecode(bytes).toObject();
36+
final cborObject = cbor.cborDecode(bytes).toObject();
37+
38+
return DataSchemaValue.tryParse(cborObject);
3139
}
3240
}

lib/src/core/codecs/content_codec.dart

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,19 @@
55
// SPDX-License-Identifier: BSD-3-Clause
66

77
import '../../definitions/data_schema.dart';
8+
import '../../scripting_api/data_schema_value.dart';
89

910
/// Interface for providing a codec for a specific media type.
1011
abstract class ContentCodec {
1112
/// Converts an [Object] to its byte representation in the given media type.
1213
List<int> valueToBytes(
13-
Object? value,
14+
DataSchemaValue<Object?> value,
1415
DataSchema? dataSchema,
1516
Map<String, String>? parameters,
1617
);
1718

1819
/// Converts a payload of the given media type to an [Object].
19-
Object? bytesToValue(
20+
DataSchemaValue<Object?>? bytesToValue(
2021
List<int> bytes,
2122
DataSchema? dataSchema,
2223
Map<String, String>? parameters,

lib/src/core/codecs/json_codec.dart

+15-4
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,38 @@ import 'dart:convert';
88

99
import '../../definitions/data_schema.dart';
1010

11+
import '../../scripting_api/data_schema_value.dart';
1112
import 'content_codec.dart';
1213

1314
/// A [ContentCodec] that encodes and decodes JSON data.
1415
class JsonCodec extends ContentCodec {
1516
@override
1617
List<int> valueToBytes(
17-
Object? value,
18+
DataSchemaValue? dataSchemaValue,
1819
DataSchema? dataSchema,
1920
Map<String, String>? parameters,
2021
) {
21-
return utf8.encode(jsonEncode(value));
22+
if (dataSchemaValue == null) {
23+
return [];
24+
}
25+
26+
return utf8.encode(jsonEncode(dataSchemaValue.value));
2227
}
2328

2429
@override
25-
Object? bytesToValue(
30+
DataSchemaValue? bytesToValue(
2631
List<int> bytes,
2732
DataSchema? dataSchema,
2833
Map<String, String>? parameters,
2934
) {
3035
// TODO(JKRhb): Use dataSchema for validation
3136

32-
return jsonDecode(utf8.decoder.convert(bytes));
37+
if (bytes.isEmpty) {
38+
return null;
39+
}
40+
41+
final decodedJson = jsonDecode(utf8.decoder.convert(bytes));
42+
43+
return DataSchemaValue.tryParse(decodedJson);
3344
}
3445
}

lib/src/core/codecs/link_format_codec.dart

-42
This file was deleted.

lib/src/core/codecs/text_codec.dart

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright 2023 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 'dart:convert';
8+
9+
import '../../definitions/data_schema.dart';
10+
11+
import '../../scripting_api/data_schema_value.dart';
12+
import 'content_codec.dart';
13+
14+
const _utf8Coding = 'utf-8';
15+
16+
/// A [ContentCodec] that encodes and decodes plain text data.
17+
class TextCodec extends ContentCodec {
18+
@override
19+
List<int> valueToBytes(
20+
DataSchemaValue? dataSchemaValue,
21+
DataSchema? dataSchema,
22+
Map<String, String>? parameters,
23+
) {
24+
if (dataSchemaValue == null) {
25+
return [];
26+
}
27+
28+
final rawValue = dataSchemaValue.value.toString();
29+
30+
final coding = parameters.coding;
31+
32+
switch (coding) {
33+
case _utf8Coding:
34+
return utf8.encode(rawValue);
35+
default:
36+
throw FormatException('Encountered unsupported text coding $coding');
37+
}
38+
}
39+
40+
@override
41+
DataSchemaValue? bytesToValue(
42+
List<int> bytes,
43+
DataSchema? dataSchema,
44+
Map<String, String>? parameters,
45+
) {
46+
if (bytes.isEmpty) {
47+
return null;
48+
}
49+
50+
final coding = parameters.coding;
51+
52+
switch (coding) {
53+
case _utf8Coding:
54+
return DataSchemaValue.fromString(utf8.decoder.convert(bytes));
55+
default:
56+
throw FormatException('Encountered unsupported text coding $coding');
57+
}
58+
}
59+
}
60+
61+
extension _ParametersExtension on Map<String, String>? {
62+
String get coding => this?['charset']?.toLowerCase() ?? _utf8Coding;
63+
}

lib/src/core/consumed_thing.dart

+18-6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import '../definitions/form.dart';
1111
import '../definitions/interaction_affordances/interaction_affordance.dart';
1212
import '../definitions/operation_type.dart';
1313
import '../definitions/thing_description.dart';
14+
import 'content.dart';
1415
import 'interaction_output.dart';
1516
import 'protocol_interfaces/protocol_client.dart';
1617
import 'servient.dart';
@@ -153,7 +154,7 @@ class ConsumedThing implements scripting_api.ConsumedThing {
153154
@override
154155
Future<void> writeProperty(
155156
String propertyName,
156-
InteractionInput input, {
157+
InteractionInput? input, {
157158
int? formIndex,
158159
Map<String, Object>? uriVariables,
159160
Object? data,
@@ -179,15 +180,21 @@ class ConsumedThing implements scripting_api.ConsumedThing {
179180

180181
final form = clientAndForm.form;
181182
final client = clientAndForm.client;
182-
final content = servient.contentSerdes
183-
.valueToContent(input, property, form.contentType);
183+
184+
final content = Content.fromInteractionInput(
185+
input,
186+
form.contentType,
187+
servient.contentSerdes,
188+
property,
189+
);
190+
184191
await client.writeResource(form, content);
185192
}
186193

187194
@override
188195
Future<InteractionOutput> invokeAction(
189196
String actionName, {
190-
InteractionInput input,
197+
InteractionInput? input,
191198
Object? data,
192199
int? formIndex,
193200
Map<String, Object>? uriVariables,
@@ -213,8 +220,13 @@ class ConsumedThing implements scripting_api.ConsumedThing {
213220

214221
final form = clientAndForm.form;
215222
final client = clientAndForm.client;
216-
final content = servient.contentSerdes
217-
.valueToContent(input, action.input, form.contentType);
223+
224+
final content = Content.fromInteractionInput(
225+
input,
226+
form.contentType,
227+
servient.contentSerdes,
228+
action.input,
229+
);
218230

219231
final output = await client.invokeResource(form, content);
220232

lib/src/core/content.dart

+33
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,45 @@ import 'dart:typed_data';
88

99
import 'package:typed_data/typed_data.dart';
1010

11+
import '../definitions/data_schema.dart';
12+
import '../scripting_api/interaction_input.dart';
13+
import 'content_serdes.dart';
14+
1115
/// This class contains binary input or output data and indicates the media
1216
/// type this data is encoded in.
1317
class Content {
1418
/// Creates a new [Content] object from a media [type] and a [body].
1519
Content(this.type, this.body);
1620

21+
/// Creates a new [Content] object from an [interactionInput].
22+
///
23+
/// If the [interactionInput] is not a [StreamInput], it will be converted to
24+
/// a [Stream] by the referenced [contentSerdes] if it supports the specified
25+
/// [contentType].
26+
/// In this case, the optional [dataSchema] will be used for validation before
27+
/// the conversion.
28+
factory Content.fromInteractionInput(
29+
InteractionInput? interactionInput,
30+
String contentType,
31+
ContentSerdes contentSerdes,
32+
DataSchema? dataSchema,
33+
) {
34+
if (interactionInput == null) {
35+
return Content(contentType, const Stream.empty());
36+
}
37+
38+
switch (interactionInput) {
39+
case DataSchemaValueInput():
40+
return contentSerdes.valueToContent(
41+
interactionInput.dataSchemaValue,
42+
dataSchema,
43+
contentType,
44+
);
45+
case StreamInput():
46+
return Content(contentType, interactionInput.byteStream);
47+
}
48+
}
49+
1750
/// The media type corresponding with this [Content] object.
1851
///
1952
/// Examples would be `application/json` or `application/cbor`.

0 commit comments

Comments
 (0)