From 5f9ebbe82cc4e093ac32c02a50441de41e3e762d Mon Sep 17 00:00:00 2001 From: Jan Romann Date: Thu, 28 Dec 2023 13:51:53 +0100 Subject: [PATCH] fixup! feat: improve DataSchemaValue handling --- lib/src/core/codecs/content_codec.dart | 4 +- lib/src/core/codecs/link_format_codec.dart | 44 ------------------- lib/src/core/codecs/text_codec.dart | 32 +++++++++----- lib/src/core/content_serdes.dart | 12 +++-- lib/src/core/interaction_output.dart | 5 +-- lib/src/core/servient.dart | 5 +-- lib/src/core/thing_discovery.dart | 18 +++----- lib/src/core/wot.dart | 5 +-- lib/src/scripting_api/data_schema_value.dart | 24 ++++++++++ lib/src/scripting_api/interaction_input.dart | 4 ++ lib/src/scripting_api/interaction_output.dart | 3 +- test/core/content_serdes_test.dart | 2 +- 12 files changed, 72 insertions(+), 86 deletions(-) delete mode 100644 lib/src/core/codecs/link_format_codec.dart diff --git a/lib/src/core/codecs/content_codec.dart b/lib/src/core/codecs/content_codec.dart index 4c7cc358..c240dd61 100644 --- a/lib/src/core/codecs/content_codec.dart +++ b/lib/src/core/codecs/content_codec.dart @@ -11,13 +11,13 @@ import '../../scripting_api/data_schema_value.dart'; abstract class ContentCodec { /// Converts an [Object] to its byte representation in the given media type. List valueToBytes( - DataSchemaValue? value, + DataSchemaValue? value, DataSchema? dataSchema, Map? parameters, ); /// Converts a payload of the given media type to an [Object]. - DataSchemaValue? bytesToValue( + DataSchemaValue? bytesToValue( List bytes, DataSchema? dataSchema, Map? parameters, diff --git a/lib/src/core/codecs/link_format_codec.dart b/lib/src/core/codecs/link_format_codec.dart deleted file mode 100644 index c560db47..00000000 --- a/lib/src/core/codecs/link_format_codec.dart +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2022 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 'dart:convert'; - -import 'package:coap/coap.dart'; - -import '../../definitions/data_schema.dart'; -import '../../scripting_api/data_schema_value.dart'; -import 'content_codec.dart'; - -/// A [ContentCodec] that encodes and decodes the CoRE Link Format ([RFC 6690]). -/// -/// [RFC 6690]: https://datatracker.ietf.org/doc/html/rfc6690 -class LinkFormatCodec extends ContentCodec { - @override - List valueToBytes( - Object? value, - DataSchema? dataSchema, - Map? parameters, - ) { - // TODO(JKRhb): The question which value types are allowed needs to be - // revisited. - if (value is CoapResource) { - return CoapLinkFormat.serialize(value).codeUnits; - } - - throw FormatException('Error deserializing CoRE Link Format', value); - } - - @override - DataSchemaValue? bytesToValue( - List bytes, - DataSchema? dataSchema, - Map? parameters, - ) { - throw Exception(); - // final string = utf8.decode(bytes); - // return CoapLinkFormat.parse(string); - } -} diff --git a/lib/src/core/codecs/text_codec.dart b/lib/src/core/codecs/text_codec.dart index 2cd0b92d..1a288631 100644 --- a/lib/src/core/codecs/text_codec.dart +++ b/lib/src/core/codecs/text_codec.dart @@ -11,6 +11,8 @@ import '../../definitions/data_schema.dart'; import '../../scripting_api/data_schema_value.dart'; import 'content_codec.dart'; +const _utf8Coding = 'utf-8'; + /// A [ContentCodec] that encodes and decodes plain text data. class TextCodec extends ContentCodec { @override @@ -23,14 +25,16 @@ class TextCodec extends ContentCodec { return []; } - final rawValue = value.value; + final rawValue = value.value.toString(); - if (rawValue is! String) { - throw ArgumentError.value(value); - } + final coding = parameters.coding; - // TODO: Support other codings - return utf8.encode(rawValue); + switch (coding) { + case _utf8Coding: + return utf8.encode(rawValue); + default: + throw FormatException('Encountered unsupported text coding $coding'); + } } @override @@ -39,13 +43,21 @@ class TextCodec extends ContentCodec { DataSchema? dataSchema, Map? parameters, ) { - // TODO(JKRhb): Use dataSchema for validation - if (bytes.isEmpty) { return null; } - // TODO: Support other codings - return DataSchemaValue.fromString(utf8.decoder.convert(bytes)); + final coding = parameters.coding; + + switch (coding) { + case _utf8Coding: + return DataSchemaValue.fromString(utf8.decoder.convert(bytes)); + default: + throw FormatException('Encountered unsupported text coding $coding'); + } } } + +extension _ParametersExtension on Map? { + String get coding => this?['charset']?.toLowerCase() ?? _utf8Coding; +} diff --git a/lib/src/core/content_serdes.dart b/lib/src/core/content_serdes.dart index 6216beae..04d31382 100644 --- a/lib/src/core/content_serdes.dart +++ b/lib/src/core/content_serdes.dart @@ -16,7 +16,6 @@ import 'codecs/cbor_codec.dart'; import 'codecs/codec_media_type.dart'; import 'codecs/content_codec.dart'; import 'codecs/json_codec.dart'; -import 'codecs/link_format_codec.dart'; import 'codecs/text_codec.dart'; import 'content.dart'; @@ -25,7 +24,6 @@ const defaultMediaType = 'application/json'; /// Custom [Exception] that is thrown when Serialization or Deserialization /// fails. -// TODO(JKRhb): Add codecs for text-based media types // TODO(JKRhb): Add codecs for XML based media types // TODO(JKRhb): Add codecs for Base64 media types // TODO(JKRhb): Add codec for OctetStream media type @@ -54,11 +52,11 @@ class ContentSerdes { /// The supported codecs by this [ContentSerdes] object. /// /// Is initialized with support for JSON, CBOR, and the CoRE Link-Format. - final _codecs = { - CodecMediaType('application', 'json'): JsonCodec(), - CodecMediaType('application', 'cbor'): CborCodec(), - CodecMediaType('application', 'link-format'): LinkFormatCodec(), - CodecMediaType('text', 'plain'): TextCodec(), + final _codecs = { + (type: 'application', subtype: 'json'): JsonCodec(), + (type: 'application', subtype: 'cbor'): CborCodec(), + (type: 'application', subtype: 'link-format'): TextCodec(), + (type: 'text', subtype: 'plain'): TextCodec(), }; final Set _offeredMediaTypes = { diff --git a/lib/src/core/interaction_output.dart b/lib/src/core/interaction_output.dart index 2dabdb51..933959b3 100644 --- a/lib/src/core/interaction_output.dart +++ b/lib/src/core/interaction_output.dart @@ -35,8 +35,7 @@ class InteractionOutput implements scripting_api.InteractionOutput { bool _dataUsed = false; // TODO: Name these fields - (bool, scripting_api.DataSchemaValue?) _value = - (false, scripting_api.DataSchemaValue.fromNull()); + (bool, Object?) _value = (false, null); @override Future arrayBuffer() async { @@ -48,7 +47,7 @@ class InteractionOutput implements scripting_api.InteractionOutput { bool get dataUsed => _dataUsed; @override - Future value() async { + Future value() async { if (_value.$1) { return _value.$2; } diff --git a/lib/src/core/servient.dart b/lib/src/core/servient.dart index f1b5dfe0..bec82db4 100644 --- a/lib/src/core/servient.dart +++ b/lib/src/core/servient.dart @@ -8,7 +8,6 @@ import 'package:uuid/uuid.dart'; import '../definitions/interaction_affordances/interaction_affordance.dart'; import '../definitions/thing_description.dart'; -import '../scripting_api/data_schema_value.dart'; import 'consumed_thing.dart'; import 'content_serdes.dart'; import 'credentials/callbacks.dart'; @@ -235,12 +234,12 @@ class Servient { final value = await contentSerdes.contentToValue(content, null); - if (value is! ObjectValue) { + if (value is! Map) { throw DiscoveryException( 'Could not parse Thing Description obtained from $url', ); } - return ThingDescription.fromJson(value.value); + return ThingDescription.fromJson(value); } } diff --git a/lib/src/core/thing_discovery.dart b/lib/src/core/thing_discovery.dart index ef3deeb3..e513be38 100644 --- a/lib/src/core/thing_discovery.dart +++ b/lib/src/core/thing_discovery.dart @@ -13,7 +13,6 @@ import 'package:multicast_dns/multicast_dns.dart'; import '../../core.dart'; import '../../scripting_api.dart' as scripting_api; import '../definitions/thing_description.dart'; -import '../scripting_api/data_schema_value.dart'; import '../scripting_api/discovery/discovery_method.dart'; import 'content.dart'; @@ -107,13 +106,13 @@ class ThingDiscovery extends Stream DiscoveryContent content, ) async { final value = await _servient.contentSerdes.contentToValue(content, null); - if (value is! ObjectValue) { + if (value is! Map) { throw DiscoveryException( 'Could not parse Thing Description obtained from ${content.sourceUri}', ); } - return ThingDescription.fromJson(value.value); + return ThingDescription.fromJson(value); } Stream _discoverDirectly(Uri uri) async* { @@ -125,16 +124,13 @@ class ThingDiscovery extends Stream } Future?> _getCoreWebLinks(Content content) async { - throw UnimplementedError(); + final value = await _servient.contentSerdes.contentToValue(content, null); - // final value = await _servient.contentSerdes.contentToValue(content, null); - // if (value is CoapWebLink) { - // return [value]; - // } else if (value is List) { - // return value; - // } + if (value is String) { + return CoapLinkFormat.parse(value).toList(); + } - // return null; + return null; } Future> _filterCoreWebLinks( diff --git a/lib/src/core/wot.dart b/lib/src/core/wot.dart index 6ea22790..cbdb1b1a 100644 --- a/lib/src/core/wot.dart +++ b/lib/src/core/wot.dart @@ -8,7 +8,6 @@ import 'dart:async'; import '../../scripting_api.dart' as scripting_api; import '../definitions/thing_description.dart'; -import '../scripting_api/data_schema_value.dart'; import '../scripting_api/discovery/discovery_method.dart'; import 'consumed_thing.dart'; import 'exposed_thing.dart'; @@ -119,7 +118,7 @@ class WoT implements scripting_api.WoT { await consumedDirectoryThing.readProperty('things'); final rawThingDescriptions = await interactionOutput.value(); - if (rawThingDescriptions is! ArrayValue) { + if (rawThingDescriptions is! List) { throw DiscoveryException( 'Expected an array of Thing Descriptions but received an ' 'invalid output instead.', @@ -127,7 +126,7 @@ class WoT implements scripting_api.WoT { } final thingDescriptionStream = Stream.fromIterable( - rawThingDescriptions.value.whereType>(), + rawThingDescriptions.whereType>(), ).toThingDescriptionStream(); return ThingDiscoveryProcess(thingDescriptionStream, filter); diff --git a/lib/src/scripting_api/data_schema_value.dart b/lib/src/scripting_api/data_schema_value.dart index d1d4360b..304f5dad 100644 --- a/lib/src/scripting_api/data_schema_value.dart +++ b/lib/src/scripting_api/data_schema_value.dart @@ -184,3 +184,27 @@ final class ObjectValue extends DataSchemaValue> { return ObjectValue._fromValue(result); } } + +extension NullDataSchemaValueExtension on Null { + DataSchemaValue asDataSchemaValue() => DataSchemaValue.fromNull(); +} + +extension StringDataSchemaValueExtension on String { + DataSchemaValue asDataSchemaValue() => DataSchemaValue.fromString(this); +} + +extension IntegerDataSchemaValueExtension on int { + DataSchemaValue asDataSchemaValue() => DataSchemaValue.fromInteger(this); +} + +extension NumberDataSchemaValueExtension on double { + DataSchemaValue asDataSchemaValue() => DataSchemaValue.fromNumber(this); +} + +extension ArrayDataSchemaValueExtension on List { + DataSchemaValue asDataSchemaValue() => DataSchemaValue.fromArray(this); +} + +extension ObjectDataSchemaValueExtension on Map { + DataSchemaValue asDataSchemaValue() => DataSchemaValue.fromObject(this); +} diff --git a/lib/src/scripting_api/interaction_input.dart b/lib/src/scripting_api/interaction_input.dart index e1c6923f..e1d45292 100644 --- a/lib/src/scripting_api/interaction_input.dart +++ b/lib/src/scripting_api/interaction_input.dart @@ -64,6 +64,10 @@ final class StreamInput extends InteractionInput { final Stream> byteStream; } +extension NullInteractionInputExtension on Null { + InteractionInput asInteractionInput() => InteractionInput.fromNull(); +} + extension StringInteractionInputExtension on String { InteractionInput asInteractionInput() => InteractionInput.fromString(this); } diff --git a/lib/src/scripting_api/interaction_output.dart b/lib/src/scripting_api/interaction_output.dart index e5d9eda1..7781c686 100644 --- a/lib/src/scripting_api/interaction_output.dart +++ b/lib/src/scripting_api/interaction_output.dart @@ -8,7 +8,6 @@ import 'dart:typed_data'; import '../definitions/data_schema.dart'; import '../definitions/form.dart'; -import 'data_schema_value.dart'; /// Exposes the data obtained by Thing interactions. /// @@ -35,5 +34,5 @@ abstract interface class InteractionOutput { Future arrayBuffer(); /// The parsed value of the [InteractionOutput]. - Future value(); + Future value(); } diff --git a/test/core/content_serdes_test.dart b/test/core/content_serdes_test.dart index 77358671..78a267e2 100644 --- a/test/core/content_serdes_test.dart +++ b/test/core/content_serdes_test.dart @@ -29,7 +29,7 @@ void main() { expect( await contentSerdes.contentToValue(testContent1, successfulSchema), - DataSchemaValue.tryParse(42), + 42, ); final testContent2 = _getTestContent('42');