diff --git a/lib/src/core/exceptions.dart b/lib/src/core/exceptions.dart index 744e79ba..58fa74f9 100644 --- a/lib/src/core/exceptions.dart +++ b/lib/src/core/exceptions.dart @@ -4,6 +4,8 @@ // // SPDX-License-Identifier: BSD-3-Clause +export "exceptions/web_idl.dart"; + /// Base class for custom exceptions defined in `dart_wot`. base class DartWotException implements Exception { /// Constructor. diff --git a/lib/src/core/exceptions/web_idl.dart b/lib/src/core/exceptions/web_idl.dart new file mode 100644 index 00000000..e41a8db8 --- /dev/null +++ b/lib/src/core/exceptions/web_idl.dart @@ -0,0 +1,20 @@ +// 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 "../exceptions.dart"; + +/// Indicates that an I/O read operation failed. +/// +/// Corresponds with the Web IDL exception type [NotReadableError]. +/// +/// [NotReadableError]: https://webidl.spec.whatwg.org/#notreadableerror +final class NotReadableException extends DartWotException { + /// Instantiates a new [NotReadableException] with the given [message]. + NotReadableException(super.message); + + @override + String get exceptionType => "NotReadableException"; +} diff --git a/lib/src/core/implementation/interaction_output.dart b/lib/src/core/implementation/interaction_output.dart index 7c202d46..1912b0ea 100644 --- a/lib/src/core/implementation/interaction_output.dart +++ b/lib/src/core/implementation/interaction_output.dart @@ -8,6 +8,7 @@ import "dart:typed_data"; import "../definitions/data_schema.dart"; import "../definitions/form.dart"; +import "../exceptions.dart"; import "../scripting_api.dart" as scripting_api; import "content.dart"; import "content_serdes.dart"; @@ -16,17 +17,24 @@ import "content_serdes.dart"; class InteractionOutput implements scripting_api.InteractionOutput { /// Creates a new [InteractionOutput] based on a [Content] object. /// - /// A [ContentSerdes] object has to be passed for decoding the raw - /// payload contained in the [Content] object. + /// A [_contentSerdes] object has to be passed for decoding the raw + /// payload contained in the [_content] object. + /// + /// In contrast to the interface definition in the + /// [Scripting API specification], [_form] is defined as non-nullable here, + /// since other parts of the code never pass a `null` value as an argument for + /// this parameter. + /// + /// [Scripting API specification]: https://w3c.github.io/wot-scripting-api/#the-interactionoutput-interface InteractionOutput( this._content, - this._contentSerdes, [ + this._contentSerdes, this._form, this._schema, - ]) : _data = _content.body; + ) : _data = _content.body; final Content _content; - final Form? _form; + final Form _form; final DataSchema? _schema; final Stream> _data; @@ -38,6 +46,10 @@ class InteractionOutput implements scripting_api.InteractionOutput { @override Future arrayBuffer() async { + if (dataUsed) { + throw NotReadableException("Data has already been read"); + } + _dataUsed = true; return _content.byteBuffer; } @@ -52,8 +64,11 @@ class InteractionOutput implements scripting_api.InteractionOutput { return existingValue.value; } - // TODO(JKRhb): Should a NotReadableError be thrown if schema is null? - // C.f. https://w3c.github.io/wot-scripting-api/#the-value-function + if (schema == null) { + throw NotReadableException( + "Can't convert data to a value because no DataSchema is present.", + ); + } final value = await _contentSerdes.contentToValue( _content, @@ -72,5 +87,5 @@ class InteractionOutput implements scripting_api.InteractionOutput { DataSchema? get schema => _schema; @override - Form? get form => _form; + Form get form => _form; } diff --git a/lib/src/core/scripting_api/interaction_output.dart b/lib/src/core/scripting_api/interaction_output.dart index 4a8326e6..8fd66a2b 100644 --- a/lib/src/core/scripting_api/interaction_output.dart +++ b/lib/src/core/scripting_api/interaction_output.dart @@ -30,8 +30,19 @@ abstract interface class InteractionOutput { /// Asyncronously creates a [ByteBuffer] representation of the value of /// of the [InteractionOutput]. + /// + /// Follows the algorithm defined for the `arrayBuffer()` function in the + /// Scripting API specification. + /// + /// [algorithm]: https://w3c.github.io/wot-scripting-api/#the-arraybuffer-function Future arrayBuffer(); /// The parsed value of the [InteractionOutput]. + /// + /// + /// Follows the algorithm defined for the `arrayBuffer()` function in the + /// Scripting API specification. + /// + /// [algorithm]: https://w3c.github.io/wot-scripting-api/#the-value-function Future value(); } diff --git a/test/core/exceptions_test.dart b/test/core/exceptions_test.dart index 40b45a12..812ee427 100644 --- a/test/core/exceptions_test.dart +++ b/test/core/exceptions_test.dart @@ -29,6 +29,11 @@ void main() { DiscoveryException("test").toString(), "DiscoveryException: test", ); + + expect( + NotReadableException("test").toString(), + "NotReadableException: test", + ); }); }); } diff --git a/test/core/interaction_output_test.dart b/test/core/interaction_output_test.dart index c3c978ef..0f4d41f9 100644 --- a/test/core/interaction_output_test.dart +++ b/test/core/interaction_output_test.dart @@ -24,7 +24,12 @@ void main() { ]), ); - final interactionOutput = InteractionOutput(content, contentSerdes); + final interactionOutput = InteractionOutput( + content, + contentSerdes, + Form(Uri.parse("http://example.org")), + const DataSchema(), + ); final value1 = await interactionOutput.value(); expect(value1, inputValue); @@ -46,7 +51,12 @@ void main() { ]), ); - final interactionOutput = InteractionOutput(content, contentSerdes); + final interactionOutput = InteractionOutput( + content, + contentSerdes, + Form(Uri.parse("http://example.org")), + const DataSchema(), + ); final value1 = await interactionOutput.value(); expect(value1, inputValue); @@ -54,5 +64,76 @@ void main() { final value2 = await interactionOutput.value(); expect(value1, value2); }); + + test( + "throw a NotReadableException when calling the arrayBuffer() method " + "twice", () async { + final contentSerdes = ContentSerdes(); + final content = Content( + "text/plain", + const Stream.empty(), + ); + + final interactionOutput = InteractionOutput( + content, + contentSerdes, + Form(Uri.parse("http://example.org")), + const DataSchema(), + ); + + await interactionOutput.arrayBuffer(); + + final result = interactionOutput.arrayBuffer(); + await expectLater( + result, + throwsA( + isA(), + ), + ); + }); + }); + + test( + "throw a NotReadableException in the value() method when no schema is " + "defined", () async { + final contentSerdes = ContentSerdes(); + final content = Content( + "text/plain", + const Stream.empty(), + ); + + final interactionOutput = InteractionOutput( + content, + contentSerdes, + Form(Uri.parse("http://example.org")), + null, + ); + + final result = interactionOutput.value(); + await expectLater( + result, + throwsA( + isA(), + ), + ); + }); + + test("allow accessing the form field", () async { + final contentSerdes = ContentSerdes(); + final content = Content( + "text/plain", + const Stream.empty(), + ); + + final uri = Uri.parse("http://example.org"); + + final interactionOutput = InteractionOutput( + content, + contentSerdes, + Form(uri), + const DataSchema(), + ); + + expect(interactionOutput.form.href, uri); }); }