From e8b58ea0c813b3b445aea9bd9f5f47e6ec8170cc Mon Sep 17 00:00:00 2001 From: alexlapa <36732824+alexlapa@users.noreply.github.com> Date: Fri, 16 Jul 2021 16:07:09 +0300 Subject: [PATCH] Extend Rust to Dart error propagation (#213) - throw MediaStateTransitionException from RoomHandle enable / disable / mute / unmute local / remote audio / video methods - throw MediaSettingsUpdateException from RoomHandle.set_local_media_settings() - throw InternalException in cases of programmatic errors or unexpected platform failures --- Cargo.lock | 107 +++++--- .../example/integration_test/jason.dart | 36 ++- jason/flutter/lib/ffi/exceptions.dart | 96 ++++++- jason/flutter/lib/ffi/foreign_value.dart | 2 +- jason/flutter/lib/room_handle.dart | 121 +++++++-- jason/src/api/dart/jason_error.rs | 58 ----- jason/src/api/dart/mod.rs | 102 ++++---- jason/src/api/dart/reconnect_handle.rs | 62 ++++- jason/src/api/dart/room_handle.rs | 196 ++++++++++---- jason/src/api/dart/unimplemented.rs | 17 -- jason/src/api/dart/utils/err.rs | 246 +++++++++++++++--- jason/src/api/dart/utils/mod.rs | 6 +- jason/src/api/wasm/connection_handle.rs | 10 +- .../api/wasm/constraints_update_exception.rs | 14 +- jason/src/api/wasm/media_manager_handle.rs | 6 +- jason/src/api/wasm/mod.rs | 2 +- jason/src/api/wasm/reconnect_handle.rs | 6 +- jason/src/api/wasm/room_handle.rs | 42 ++- jason/src/peer/component/watchers.rs | 2 +- jason/src/peer/media/mod.rs | 4 +- jason/src/peer/mod.rs | 19 +- jason/src/room.rs | 44 ++-- jason/src/rpc/mod.rs | 2 +- jason/tests/room/room.rs | 12 +- jason/tests/web.rs | 2 +- 25 files changed, 858 insertions(+), 356 deletions(-) delete mode 100644 jason/src/api/dart/jason_error.rs delete mode 100644 jason/src/api/dart/unimplemented.rs diff --git a/Cargo.lock b/Cargo.lock index 6858aec92..50fea794a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -244,7 +244,7 @@ dependencies = [ "ahash", "bytes 1.0.1", "cfg-if 1.0.0", - "cookie 0.15.0", + "cookie 0.15.1", "derive_more", "either", "encoding_rs", @@ -370,9 +370,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61" +checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486" [[package]] name = "arc-swap" @@ -465,7 +465,7 @@ dependencies = [ "base64 0.13.0", "bytes 1.0.1", "cfg-if 1.0.0", - "cookie 0.15.0", + "cookie 0.15.1", "derive_more", "futures-core", "itoa", @@ -609,9 +609,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" +checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" dependencies = [ "jobserver", ] @@ -735,9 +735,9 @@ dependencies = [ [[package]] name = "cookie" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf8865bac3d9a3bde5bde9088ca431b11f5d37c7a578b8086af77248b76627" +checksum = "d5f1c7727e460397e56abc4bddc1d49e07a1ad78fc98eb2e1c8f032a58a2f80d" dependencies = [ "percent-encoding", "time 0.2.27", @@ -999,13 +999,14 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.14" +version = "0.99.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc7b9cef1e351660e5443924e4f43ab25fbbed3e9a5f052df3677deb4d6b320" +checksum = "40eebddd2156ce1bb37b20bbe5151340a31828b1f2d22ba4141f3531710e38df" dependencies = [ "convert_case", "proc-macro2 1.0.27", "quote 1.0.9", + "rustc_version 0.3.3", "syn 1.0.73", ] @@ -1127,7 +1128,7 @@ dependencies = [ "futures-core", "futures-util", "http", - "hyper 0.14.9", + "hyper 0.14.10", "hyper-tls", "mime", "serde 1.0.126", @@ -1611,9 +1612,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.9" +version = "0.14.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07d6baa1b441335f3ce5098ac421fb6547c46dda735ca1bc6d0153c838f9dd83" +checksum = "7728a72c4c7d72665fde02204bcbd93b247721025b222ef78606f14513e0fd03" dependencies = [ "bytes 1.0.1", "futures-channel", @@ -1640,7 +1641,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes 1.0.1", - "hyper 0.14.9", + "hyper 0.14.10", "native-tls", "tokio 1.8.1", "tokio-native-tls", @@ -1699,9 +1700,9 @@ checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" [[package]] name = "instant" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -1816,9 +1817,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.97" +version = "0.2.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" +checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" [[package]] name = "linked-hash-map" @@ -1894,7 +1895,7 @@ dependencies = [ "function_name", "futures", "humantime-serde", - "hyper 0.14.9", + "hyper 0.14.10", "lazy_static", "medea-client-api-proto 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "medea-control-api-proto", @@ -2198,9 +2199,9 @@ dependencies = [ [[package]] name = "mockall" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfd9a2de7b4bd932854c776ce60880caf8fa41095a7907e3accc52ef6678895b" +checksum = "6ab571328afa78ae322493cacca3efac6a0f2e0a67305b4df31fd439ef129ac0" dependencies = [ "cfg-if 1.0.0", "downcast", @@ -2213,9 +2214,9 @@ dependencies = [ [[package]] name = "mockall_derive" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07174e9c5ffb2ff849187641c48fc66f5588f012f1d248e55c3a68cd462bd29" +checksum = "e7e25b214433f669161f414959594216d8e6ba83b6679d3db96899c0b4639033" dependencies = [ "cfg-if 1.0.0", "proc-macro2 1.0.27", @@ -2446,6 +2447,15 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + [[package]] name = "petgraph" version = "0.5.1" @@ -2833,7 +2843,7 @@ dependencies = [ "futures-util", "http", "http-body 0.4.2", - "hyper 0.14.9", + "hyper 0.14.10", "hyper-tls", "ipnet", "js-sys", @@ -2903,7 +2913,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver", + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", ] [[package]] @@ -2978,7 +2997,16 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ - "semver-parser", + "semver-parser 0.7.0", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser 0.10.2", ] [[package]] @@ -2987,6 +3015,15 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + [[package]] name = "serde" version = "0.8.23" @@ -3301,7 +3338,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" dependencies = [ "discard", - "rustc_version", + "rustc_version 0.2.3", "stdweb-derive", "stdweb-internal-macros", "stdweb-internal-runtime", @@ -3357,9 +3394,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "subtle" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" @@ -3385,9 +3422,9 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +checksum = "474aaa926faa1603c40b7885a9eaea29b444d1cb2850cb7c0e37bb1a4182f4fa" dependencies = [ "proc-macro2 1.0.27", "quote 1.0.9", @@ -3677,7 +3714,7 @@ dependencies = [ "h2 0.3.3", "http", "http-body 0.4.2", - "hyper 0.14.9", + "hyper 0.14.10", "percent-encoding", "pin-project 1.0.7", "prost", @@ -3816,6 +3853,12 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + [[package]] name = "unicase" version = "2.6.0" diff --git a/jason/flutter/example/integration_test/jason.dart b/jason/flutter/example/integration_test/jason.dart index 9fec0a5e8..f61d1bcaf 100644 --- a/jason/flutter/example/integration_test/jason.dart +++ b/jason/flutter/example/integration_test/jason.dart @@ -340,6 +340,18 @@ void main() { isFormatException, predicate( (e) => e.message.contains('relative URL without a base')))); + + var localMediaErr = Completer(); + room.onFailedLocalMedia((err) { + localMediaErr.complete(err); + }); + var err = await localMediaErr.future; + expect( + err, + predicate((e) => + e is MediaStateTransitionException && + e.message == 'SimpleTracksRequest should have at least one track' && + e.nativeStackTrace.contains('at jason/src'))); }); testWidgets('ReconnectHandle', (WidgetTester tester) async { @@ -406,9 +418,9 @@ void main() { () => returnsRpcClientException('Dart err cause1').unwrap(), throwsA(predicate((e) => e is RpcClientException && - e.kind == RpcClientExceptionKind.InternalError && + e.kind == RpcClientExceptionKind.ConnectionLost && e.cause == 'Dart err cause1' && - e.message == 'RpcClientException::InternalError' && + e.message == 'RpcClientException::ConnectionLost' && e.nativeStackTrace.contains('at jason/src')))); var exception5; @@ -486,4 +498,24 @@ void main() { str.free(); num.free(); }); + + testWidgets('Complex arguments validation', (WidgetTester tester) async { + final _muteVideo = dl.lookupFunction('RoomHandle__mute_video'); + + var jason = Jason(); + var room = jason.initRoom(); + + var err; + var arg = ForeignValue.fromInt(123); + try { + await (_muteVideo(room.ptr.getInnerPtr(), arg.ref) as Future); + } catch (e) { + err = e as ArgumentError; + } finally { + arg.free(); + } + expect(err.invalidValue, equals(123)); + expect(err.name, 'kind'); + }); } diff --git a/jason/flutter/lib/ffi/exceptions.dart b/jason/flutter/lib/ffi/exceptions.dart index 2e5cddd53..91eb97a5a 100644 --- a/jason/flutter/lib/ffi/exceptions.dart +++ b/jason/flutter/lib/ffi/exceptions.dart @@ -32,6 +32,19 @@ void registerFunctions(DynamicLibrary dl) { 'register_new_rpc_client_exception_caller')(Pointer.fromFunction< Handle Function(Uint8, Pointer, ForeignValue, Pointer)>( _newRpcClientException)); + dl.lookupFunction( + 'register_new_media_state_transition_exception_caller')( + Pointer.fromFunction, Pointer)>( + _newMediaStateTransitionException)); + dl.lookupFunction( + 'register_new_internal_exception_caller')(Pointer.fromFunction< + Handle Function(Pointer, ForeignValue, Pointer)>( + _newInternalException)); + dl.lookupFunction( + 'register_new_media_settings_update_exception_caller')( + Pointer.fromFunction< + Handle Function(Pointer, Pointer, + Uint8)>(_newMediaSettingsUpdateException)); } /// Creates a new [ArgumentError] from the provided invalid [value], its [name] @@ -82,6 +95,30 @@ Object _newRpcClientException(int kind, Pointer message, stacktrace.nativeStringToDartString()); } +/// Creates a new [MediaStateTransitionException] with the provided error +/// [message] and [stacktrace]. +Object _newMediaStateTransitionException( + Pointer message, Pointer stacktrace) { + return MediaStateTransitionException(message.nativeStringToDartString(), + stacktrace.nativeStringToDartString()); +} + +/// Creates a new [InternalException] with the provided error [message], error +/// [cause] and [stacktrace]. +Object _newInternalException( + Pointer message, ForeignValue cause, Pointer stacktrace) { + return InternalException(message.nativeStringToDartString(), cause.toDart(), + stacktrace.nativeStringToDartString()); +} + +/// Creates a new [MediaSettingsUpdateException] with the provided error +/// [message], error [cause] and [rolledBack] property. +Object _newMediaSettingsUpdateException( + Pointer message, Pointer cause, int rolledBack) { + return MediaSettingsUpdateException(message.nativeStringToDartString(), + unboxDartHandle(cause), rolledBack > 0); +} + /// Exception thrown when local media acquisition fails. class LocalMediaInitException implements Exception { /// Concrete error kind of this [LocalMediaInitException]. @@ -171,9 +208,62 @@ enum RpcClientExceptionKind { /// RPC session has been finished. This is a terminal state. SessionFinished, +} + +/// Exception thrown when the requested media state transition could not be +/// performed. +class MediaStateTransitionException implements Exception { + /// Error message describing the problem. + late String message; + + /// Native stacktrace. + late String nativeStackTrace; - /// Internal error that is not meant to be handled by external users. + /// Instantiates a new [MediaStateTransitionException]. + MediaStateTransitionException(this.message, this.nativeStackTrace); +} + +/// Jason's internal exception. +/// +/// This is either a programmatic error or some unexpected platform component +/// failure that cannot be handled in any way. +class InternalException implements Exception { + /// Error message describing the problem. + late String message; + + /// Dart [Exception] or [Error] that caused this [InternalException]. + late Object? cause; + + /// Native stacktrace. + late String nativeStackTrace; + + /// Instantiates a new [InternalException]. + InternalException(this.message, this.cause, this.nativeStackTrace); +} + +/// Exception that might happen when updating local media settings via +/// `RoomHandle.setLocalMediaSettings`. +class MediaSettingsUpdateException implements Exception { + /// Error message describing the problem. + late String message; + + /// The reason why media settings update failed. /// - /// This is a programmatic error. - InternalError, + /// Possible exception kinds are: + /// - [StateError] if an underlying `RoomHandle` object has been disposed. + /// - [LocalMediaInitException] if a request of platform media devices access + /// failed. + /// - [MediaStateTransitionException] if transition is prohibited by tracks + /// configuration or explicitly denied by server. + /// - [InternalException] in case of a programmatic error or some unexpected + /// platform component failure. + late Object updateException; + + /// Whether media settings were successfully rolled back after new settings + /// application failed. + late bool rolledBack; + + /// Instantiates a new [MediaSettingsUpdateException]. + MediaSettingsUpdateException( + this.message, this.updateException, this.rolledBack); } diff --git a/jason/flutter/lib/ffi/foreign_value.dart b/jason/flutter/lib/ffi/foreign_value.dart index 78d94fc26..4e82456d0 100644 --- a/jason/flutter/lib/ffi/foreign_value.dart +++ b/jason/flutter/lib/ffi/foreign_value.dart @@ -64,7 +64,7 @@ class ForeignValue extends Struct { static Pointer fromString(String str) { var fVal = calloc(); fVal.ref._tag = 3; - fVal.ref._payload.ptr = str.toNativeUtf8(); + fVal.ref._payload.string = str.toNativeUtf8(); return fVal; } diff --git a/jason/flutter/lib/room_handle.dart b/jason/flutter/lib/room_handle.dart index 2e3d9d8f7..8e69f2b5f 100644 --- a/jason/flutter/lib/room_handle.dart +++ b/jason/flutter/lib/room_handle.dart @@ -31,6 +31,10 @@ typedef _onConnectionLoss_C = Result Function(Pointer, Handle); typedef _onConnectionLoss_Dart = Result Function( Pointer, void Function(Pointer)); +typedef _onFailedLocalMedia_C = Result Function(Pointer, Handle); +typedef _onFailedLocalMedia_Dart = Result Function( + Pointer, void Function(Pointer)); + typedef _join_C = Handle Function(Pointer, Pointer); typedef _join_Dart = Object Function(Pointer, Pointer); @@ -91,6 +95,10 @@ final _onConnectionLoss = dl.lookupFunction<_onConnectionLoss_C, _onConnectionLoss_Dart>( 'RoomHandle__on_connection_loss'); +final _onFailedLocalMedia = + dl.lookupFunction<_onFailedLocalMedia_C, _onFailedLocalMedia_Dart>( + 'RoomHandle__on_failed_local_media'); + final _join = dl.lookupFunction<_join_C, _join_Dart>('RoomHandle__join'); final _setLocalMediaSettings = @@ -192,6 +200,8 @@ class RoomHandle { /// If recovering from fail state isn't possible then affected media types /// will be disabled. /// + /// Throws a [MediaSettingsUpdateException] if settings could not be updated. + /// /// [1]: https://w3.org/TR/mediacapture-streams#dom-mediadevices-getusermedia Future setLocalMediaSettings( MediaStreamSettings settings, bool stopFirst, bool rollbackOnFail) async { @@ -199,26 +209,49 @@ class RoomHandle { stopFirst ? 1 : 0, rollbackOnFail ? 1 : 0) as Future); } - // TODO: Add throws docs when all errros are implemented. /// Mutes outbound audio in this `Room`. + /// + /// Throws a [StateError] if the underlying [Pointer] has been freed. + /// + /// Throws a `MediaStateTransitionException` if [RoomHandle.unmuteAudio] was + /// called while muting or a media server didn't approve this state + /// transition. Future muteAudio() async { await (_muteAudio(ptr.getInnerPtr()) as Future); } - // TODO: Add throws docs when all errros are implemented. /// Unmutes outbound audio in this `Room`. + /// + /// Throws a [StateError] if the underlying [Pointer] has been freed. + /// + /// Throws a `MediaStateTransitionException` if [RoomHandle.muteAudio] was + /// called while unmuting or a media server didn't approve this state + /// transition. Future unmuteAudio() async { await (_unmuteAudio(ptr.getInnerPtr()) as Future); } - // TODO: Add throws docs when all errros are implemented. /// Enables outbound audio in this `Room`. + /// + /// Throws a [StateError] if the underlying [Pointer] has been freed. + /// + /// Throws a `MediaStateTransitionException` if [RoomHandle.disableAudio] was + /// called while enabling or a media server didn't approve this state + /// transition. + /// + /// Throws a `LocalMediaInitException` if a request of platform media devices + /// access failed. Future enableAudio() async { await (_enableAudio(ptr.getInnerPtr()) as Future); } - // TODO: Add throws docs when all errros are implemented. /// Disables outbound audio in this `Room`. + /// + /// Throws a [StateError] if the underlying [Pointer] has been freed. + /// + /// Throws a `MediaStateTransitionException` if [RoomHandle.enableAudio] was + /// called while disabling or a media server didn't approve this state + /// transition. Future disableAudio() async { await (_disableAudio(ptr.getInnerPtr()) as Future); } @@ -226,6 +259,12 @@ class RoomHandle { /// Mutes outbound video in this `Room`. /// /// Affects only video with specific [MediaSourceKind] if specified. + /// + /// Throws a [StateError] if the underlying [Pointer] has been freed. + /// + /// Throws a `MediaStateTransitionException` if [RoomHandle.unmuteVideo] was + /// called while muting or a media server didn't approve this state + /// transition. Future muteVideo([MediaSourceKind? kind]) async { var kind_arg = kind == null ? ForeignValue.none() : ForeignValue.fromInt(kind.index); @@ -239,6 +278,12 @@ class RoomHandle { /// Unmutes outbound video in this `Room`. /// /// Affects only video with specific [MediaSourceKind] if specified. + /// + /// Throws a [StateError] if the underlying [Pointer] has been freed. + /// + /// Throws a `MediaStateTransitionException` if [RoomHandle.muteVideo] was + /// called while unmuting or a media server didn't approve this state + /// transition. Future unmuteVideo([MediaSourceKind? kind]) async { var kind_arg = kind == null ? ForeignValue.none() : ForeignValue.fromInt(kind.index); @@ -252,6 +297,15 @@ class RoomHandle { /// Enables outbound video. /// /// Affects only video with specific [MediaSourceKind] if specified. + /// + /// Throws a [StateError] if the underlying [Pointer] has been freed. + /// + /// Throws a `MediaStateTransitionException` if [RoomHandle.disableVideo] was + /// called while enabling or a media server didn't approve this state + /// transition. + /// + /// Throws a `LocalMediaInitException` if a request of platform media devices + /// access failed. Future enableVideo([MediaSourceKind? kind]) async { var kind_arg = kind == null ? ForeignValue.none() : ForeignValue.fromInt(kind.index); @@ -265,6 +319,12 @@ class RoomHandle { /// Disables outbound video. /// /// Affects only video with specific [MediaSourceKind] if specified. + /// + /// Throws a [StateError] if the underlying [Pointer] has been freed. + /// + /// Throws a `MediaStateTransitionException` if [RoomHandle.enableVideo] was + /// called while disabling or a media server didn't approve this state + /// transition. Future disableVideo([MediaSourceKind? kind]) async { var kind_arg = kind == null ? ForeignValue.none() : ForeignValue.fromInt(kind.index); @@ -275,26 +335,46 @@ class RoomHandle { } } - // TODO: Add throws docs when all errros are implemented. /// Enables inbound audio in this `Room`. + /// + /// Throws a [StateError] if the underlying [Pointer] has been freed. + /// + /// Throws a `MediaStateTransitionException` if + /// [RoomHandle.disableRemoteAudio] was called while enabling or a media + /// server didn't approve this state transition. Future enableRemoteAudio() async { await (_enableRemoteAudio(ptr.getInnerPtr()) as Future); } - // TODO: Add throws docs when all errros are implemented. /// Disables inbound audio in this `Room`. + /// + /// Throws a [StateError] if the underlying [Pointer] has been freed. + /// + /// Throws a `MediaStateTransitionException` if [RoomHandle.enableRemoteAudio] + /// was called while disabling or a media server didn't approve this state + /// transition. Future disableRemoteAudio() async { await (_disableRemoteAudio(ptr.getInnerPtr()) as Future); } - // TODO: Add throws docs when all errros are implemented. /// Enables inbound video in this `Room`. + /// + /// Throws a [StateError] if the underlying [Pointer] has been freed. + /// + /// Throws a `MediaStateTransitionException` if + /// [RoomHandle.disableRemoteVideo] was called while enabling or a media + /// server didn't approve this state transition. Future enableRemoteVideo() async { await (_enableRemoteVideo(ptr.getInnerPtr()) as Future); } - // TODO: Add throws docs when all errros are implemented. /// Disables inbound video in this `Room`. + /// + /// Throws a [StateError] if the underlying [Pointer] has been freed. + /// + /// Throws a `MediaStateTransitionException` if + /// [RoomHandle.enableRemoteVideo] was called while disabling or a media + /// server didn't approve this state transition. Future disableRemoteVideo() async { await (_disableRemoteVideo(ptr.getInnerPtr()) as Future); } @@ -324,9 +404,10 @@ class RoomHandle { /// /// This might happen in the following cases: /// 1. Media server initiates a media request. - /// 2. [RoomHandle.enableAudio()]/[RoomHandle.enableVideo()] is called. + /// 2. [RoomHandle.enableAudio] or [RoomHandle.enableVideo] call resulted in + /// new media track acquisition. /// 3. [MediaStreamSettings] were updated via - /// [RoomHandle.setLocalMediaSettings()] method. + /// [RoomHandle.setLocalMediaSettings] method. /// /// Throws [StateError] if the underlying [Pointer] has been freed. void onLocalTrack(void Function(LocalMediaTrack) f) { @@ -345,18 +426,14 @@ class RoomHandle { }).unwrap(); } - // TODO: Implement. - // /// Sets `on_failed_local_media` callback, invoked on a local media - // /// acquisition failures. - // /// - // /// # Errors - // /// - // /// Throws [StateError] if the underlying [Pointer] has been freed. - // void onFailedLocalMedia(void Function(ReconnectHandle) f) { - // _onConnectionLoss(ptr.getInnerPtr(), (t) { - // f(ReconnectHandle(NullablePointer(t))); - // }).unwrap(); - // } + /// Sets callback, invoked on a local media acquisition failures. + /// + /// Throws [StateError] if the underlying [Pointer] has been freed. + void onFailedLocalMedia(void Function(Object) f) { + _onFailedLocalMedia(ptr.getInnerPtr(), (err) { + f(err); + }).unwrap(); + } /// Drops the associated Rust struct and nulls the local [Pointer] to it. @moveSemantics diff --git a/jason/src/api/dart/jason_error.rs b/jason/src/api/dart/jason_error.rs deleted file mode 100644 index fd280727b..000000000 --- a/jason/src/api/dart/jason_error.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! App error exported to JS side. -// TODO: This is just a copy of wasm version, Rust to Dart error propagation -// will be implemented later. - -use std::fmt::{Debug, Display}; - -use derive_more::{Display, From}; -use tracerr::{Trace, Traced}; - -use crate::{platform, utils::JsCaused}; - -use super::ForeignClass; - -/// Representation of an app error exported to JS side. -/// -/// Contains JS side error if it's the cause, and a trace information. -#[derive(From, Clone, Debug, Display)] -#[display(fmt = "{}: {}\n{}", name, message, trace)] -pub struct JasonError { - /// Name of this [`JasonError`]. - name: &'static str, - - /// Message describing this [`JasonError`]. - message: String, - - /// [`Trace`] information of this [`JasonError`]. - trace: Trace, - - /// Optional cause of this [`JasonError`] as a JS side error. - source: Option, -} - -impl ForeignClass for JasonError {} - -impl From<(E, Trace)> for JasonError -where - E::Error: Into, -{ - #[inline] - fn from((err, trace): (E, Trace)) -> Self { - Self { - name: err.name(), - message: err.to_string(), - trace, - source: err.js_cause().map(Into::into), - } - } -} - -impl From> for JasonError -where - E::Error: Into, -{ - #[inline] - fn from(traced: Traced) -> Self { - Self::from(traced.into_parts()) - } -} diff --git a/jason/src/api/dart/mod.rs b/jason/src/api/dart/mod.rs index 7556f9737..ace70551c 100644 --- a/jason/src/api/dart/mod.rs +++ b/jason/src/api/dart/mod.rs @@ -13,7 +13,6 @@ pub mod device_video_track_constraints; pub mod display_video_track_constraints; pub mod input_device_info; pub mod jason; -pub mod jason_error; pub mod local_media_track; pub mod media_manager_handle; pub mod media_stream_settings; @@ -21,13 +20,12 @@ pub mod reconnect_handle; pub mod remote_media_track; pub mod room_close_reason; pub mod room_handle; -mod unimplemented; pub mod utils; use std::{convert::TryFrom, ffi::c_void, marker::PhantomData, ptr}; use dart_sys::Dart_Handle; -use derive_more::From; +use derive_more::Display; use libc::c_char; use crate::{ @@ -42,12 +40,13 @@ pub use self::{ connection_handle::ConnectionHandle, device_video_track_constraints::DeviceVideoTrackConstraints, display_video_track_constraints::DisplayVideoTrackConstraints, - input_device_info::InputDeviceInfo, jason::Jason, jason_error::JasonError, + input_device_info::InputDeviceInfo, jason::Jason, local_media_track::LocalMediaTrack, media_manager_handle::MediaManagerHandle, media_stream_settings::MediaStreamSettings, reconnect_handle::ReconnectHandle, remote_media_track::RemoteMediaTrack, room_close_reason::RoomCloseReason, room_handle::RoomHandle, + utils::DartError as Error, }; /// Rust structure having wrapper class in Dart. @@ -236,10 +235,10 @@ impl TryFrom> for ptr::NonNull { fn try_from(value: DartValueArg) -> Result { match value.0 { DartValue::Ptr(ptr) => Ok(ptr), - _ => Err(DartValueCastError(format!( - "expected `NonNull`, actual: `{:?}`", - value.0, - ))), + _ => Err(DartValueCastError { + expectation: "NonNull", + value: value.0, + }), } } } @@ -251,10 +250,10 @@ impl TryFrom> for Option> { match value.0 { DartValue::None => Ok(None), DartValue::Ptr(ptr) => Ok(Some(ptr)), - _ => Err(DartValueCastError(format!( - "expected `Option>`, actual: `{:?}`", - value.0, - ))), + _ => Err(DartValueCastError { + expectation: "Option>", + value: value.0, + }), } } } @@ -265,10 +264,10 @@ impl TryFrom> for String { fn try_from(value: DartValueArg) -> Result { match value.0 { DartValue::String(c_str) => unsafe { Ok(c_str_into_string(c_str)) }, - _ => Err(DartValueCastError(format!( - "expected `String`, actual: `{:?}`", - value.0, - ))), + _ => Err(DartValueCastError { + expectation: "String", + value: value.0, + }), } } } @@ -284,10 +283,10 @@ impl TryFrom>> for Option { DartValue::String(c_str) => unsafe { Ok(Some(c_str_into_string(c_str))) }, - _ => Err(DartValueCastError(format!( - "expected `Option`, actual: `{:?}`", - value.0, - ))), + _ => Err(DartValueCastError { + expectation: "Option", + value: value.0, + }), } } } @@ -298,10 +297,10 @@ impl TryFrom> for ptr::NonNull { fn try_from(value: DartValueArg) -> Result { match value.0 { DartValue::Handle(c_str) => Ok(c_str), - _ => Err(DartValueCastError(format!( - "expected `NonNull`, actual: `{:?}`", - value.0, - ))), + _ => Err(DartValueCastError { + expectation: "NonNull", + value: value.0, + }), } } } @@ -313,10 +312,10 @@ impl TryFrom> for Option> { match value.0 { DartValue::None => Ok(None), DartValue::Handle(c_str) => Ok(Some(c_str)), - _ => Err(DartValueCastError(format!( - "expected `Option>`, actual: `{:?}`", - value.0, - ))), + _ => Err(DartValueCastError { + expectation: "Option>", + value: value.0, + }), } } } @@ -327,10 +326,10 @@ impl TryFrom> for i64 { fn try_from(value: DartValueArg) -> Result { match value.0 { DartValue::Int(num) => Ok(num), - _ => Err(DartValueCastError(format!( - "expected `i64`, actual: `{:?}`", - value.0, - ))), + _ => Err(DartValueCastError { + expectation: "i64", + value: value.0, + }), } } } @@ -342,26 +341,41 @@ impl TryFrom> for Option { match value.0 { DartValue::None => Ok(None), DartValue::Int(num) => Ok(Some(num)), - _ => Err(DartValueCastError(format!( - "expected `Option`, actual: `{:?}`", - value.0, - ))), + _ => Err(DartValueCastError { + expectation: "Option", + value: value.0, + }), } } } /// Error of converting a [`DartValue`] to the concrete type. -#[derive(Debug, From)] -#[from(forward)] -pub struct DartValueCastError(String); +#[derive(Debug, Display)] +#[display(fmt = "expected `{}`, but got: `{:?}`", expectation, value)] +pub struct DartValueCastError { + /// Expected type description. Like a [`String`] or an `Option`. + expectation: &'static str, + + /// [`DartValue`] that cannot be casted. + value: DartValue, +} + +impl DartValueCastError { + /// Returns [`DartValue`] that could not be casted. + fn into_value(self) -> DartValue { + self.value + } +} + +impl TryFrom for MediaSourceKind { + type Error = i64; -impl From for MediaSourceKind { #[inline] - fn from(value: i64) -> Self { + fn try_from(value: i64) -> Result { match value { - 0 => Self::Device, - 1 => Self::Display, - _ => unreachable!(), + 0 => Ok(Self::Device), + 1 => Ok(Self::Display), + _ => Err(value), } } } diff --git a/jason/src/api/dart/reconnect_handle.rs b/jason/src/api/dart/reconnect_handle.rs index 7246dc837..d1521a3a0 100644 --- a/jason/src/api/dart/reconnect_handle.rs +++ b/jason/src/api/dart/reconnect_handle.rs @@ -5,11 +5,13 @@ use tracerr::Traced; use crate::{ api::dart::{ utils::{ - ArgumentError, DartFuture, IntoDartFuture, RpcClientException, + ArgumentError, DartFuture, InternalException, IntoDartFuture, + RpcClientException, RpcClientExceptionKind, }, DartValueArg, }, - rpc::ReconnectError, + rpc::{rpc_session::ConnectionLostReason, ReconnectError, SessionError}, + utils::JsCaused as _, }; use super::{ @@ -34,12 +36,49 @@ impl From> for DartError { StateError::new("ReconnectHandle is in detached state.").into() } ReconnectError::Session(err) => { - RpcClientException::from(Traced::from_parts(err, trace)).into() + Traced::from_parts(err, trace).into() } } } } +impl From> for DartError { + #[allow(clippy::option_if_let_else)] + fn from(err: Traced) -> Self { + use ConnectionLostReason as Reason; + use RpcClientExceptionKind as Kind; + use SessionError as SE; + + let (err, trace) = err.into_parts(); + let message = err.to_string(); + + let mut cause = None; + let kind = match err { + SE::SessionFinished(_) => Some(Kind::SessionFinished), + SE::NoCredentials + | SE::SessionUnexpectedlyDropped + | SE::NewConnectionInfo => None, + SE::RpcClient(err) => { + cause = err.js_cause(); + None + } + SE::AuthorizationFailed => Some(Kind::AuthorizationFailed), + SE::ConnectionLost(reason) => { + if let Reason::ConnectError(err) = reason { + cause = err.into_inner().js_cause() + }; + Some(Kind::ConnectionLost) + } + }; + + if let Some(rpc_kind) = kind { + RpcClientException::new(rpc_kind, message, cause, trace).into() + } else { + InternalException::new(message, cause, trace).into() + } + } +} + /// Tries to reconnect a [`Room`] after the provided delay in milliseconds. /// /// If the [`Room`] is already reconnecting then new reconnection attempt won't @@ -110,10 +149,15 @@ pub unsafe extern "C" fn ReconnectHandle__reconnect_with_backoff( let max_delay = u32::try_from(max_delay).map_err(|_| { ArgumentError::new(max_delay, "maxDelay", "Expected u32") })?; - // TODO: Remove unwrap when propagating fatal errors from Rust to Dart - // is implemented. - let max_elapsed_time_ms = >::try_from(max_elapsed_time_ms) - .unwrap() + let max_elapsed_time_ms = Option::::try_from(max_elapsed_time_ms) + .map_err(|err| { + let message = err.to_string(); + ArgumentError::new( + err.into_value(), + "maxElapsedTimeMs", + message, + ) + })? .map(|v| { u32::try_from(v).map_err(|_| { ArgumentError::new(v, "maxElapsedTimeMs", "Expected u32") @@ -193,8 +237,8 @@ mod mock { cause: Dart_Handle, ) -> DartResult { let err = RpcClientException::new( - RpcClientExceptionKind::InternalError, - "RpcClientException::InternalError", + RpcClientExceptionKind::ConnectionLost, + "RpcClientException::ConnectionLost", Some(platform::Error::from(cause)), Trace::new(vec![tracerr::new_frame!()]), ); diff --git a/jason/src/api/dart/room_handle.rs b/jason/src/api/dart/room_handle.rs index 7c68cd557..55461948f 100644 --- a/jason/src/api/dart/room_handle.rs +++ b/jason/src/api/dart/room_handle.rs @@ -1,4 +1,7 @@ -use std::{convert::TryFrom as _, ptr}; +use std::{ + convert::{TryFrom, TryInto as _}, + ptr, +}; use dart_sys::Dart_Handle; use tracerr::Traced; @@ -6,12 +9,18 @@ use tracerr::Traced; use crate::{ api::dart::{ utils::{ - c_str_into_string, DartFuture, DartResult, FormatException, - IntoDartFuture as _, RpcClientException, StateError, + c_str_into_string, ArgumentError, DartFuture, DartResult, + FormatException, InternalException, IntoDartFuture as _, + MediaSettingsUpdateException, MediaStateTransitionException, + StateError, }, DartValueArg, ForeignClass, }, media::MediaSourceKind, + peer::{ + media::sender::CreateError, InsertLocalTracksError, LocalMediaError, + UpdateLocalStreamError, + }, platform, room::{ ChangeMediaStateError, ConstraintsUpdateError, HandleDetachedError, @@ -49,7 +58,39 @@ impl From> for DartError { FormatException::new(message).into() } RoomJoinError::SessionError(err) => { - RpcClientException::from(Traced::from_parts(err, trace)).into() + Traced::from_parts(err, trace).into() + } + } + } +} + +impl From> for DartError { + fn from(err: Traced) -> Self { + use InsertLocalTracksError as IE; + use LocalMediaError as ME; + use UpdateLocalStreamError as UE; + + let (err, trace) = err.into_parts(); + let message = err.to_string(); + + match err { + ME::UpdateLocalStreamError(err) => match err { + UE::CouldNotGetLocalMedia(err) => { + Traced::from_parts(err, trace).into() + } + UE::InvalidLocalTracks(_) + | UE::InsertLocalTracksError( + IE::InvalidMediaTrack | IE::NotEnoughTracks, + ) => MediaStateTransitionException::new(message, trace).into(), + UE::InsertLocalTracksError(IE::CouldNotInsertLocalTrack(_)) => { + InternalException::new(message, None, trace).into() + } + }, + ME::SenderCreateError(CreateError::TransceiverNotFound(_)) => { + InternalException::new(message, None, trace).into() + } + ME::SenderCreateError(CreateError::CannotDisableRequiredSender) => { + MediaStateTransitionException::new(message, trace).into() } } } @@ -58,25 +99,42 @@ impl From> for DartError { impl From> for DartError { #[inline] fn from(err: Traced) -> Self { - match err.into_inner() { + let (err, trace) = err.into_parts(); + let message = err.to_string(); + + match err { ChangeMediaStateError::Detached => { StateError::new("RoomHandle is in detached state.").into() } - ChangeMediaStateError::InvalidLocalTracks(_) - | ChangeMediaStateError::CouldNotGetLocalMedia(_) - | ChangeMediaStateError::InsertLocalTracksError(_) - | ChangeMediaStateError::ProhibitedState(_) - | ChangeMediaStateError::TransitionIntoOppositeState(_) => { - todo!() + ChangeMediaStateError::CouldNotGetLocalMedia(err) => { + Traced::from_parts(err, trace).into() + } + ChangeMediaStateError::ProhibitedState(_) + | ChangeMediaStateError::TransitionIntoOppositeState(_) + | ChangeMediaStateError::InvalidLocalTracks(_) => { + MediaStateTransitionException::new(message, trace).into() + } + ChangeMediaStateError::InsertLocalTracksError(_) => { + InternalException::new(message, None, trace).into() } } } } -impl From> for DartError { +impl From for DartError { #[inline] - fn from(_: Traced) -> Self { - todo!() + fn from(err: ConstraintsUpdateError) -> Self { + let message = err.to_string(); + + let (err, rolled_back) = match err { + ConstraintsUpdateError::Recovered(err) => (err, true), + ConstraintsUpdateError::RecoverFailed { + recover_reason, .. + } => (recover_reason, false), + ConstraintsUpdateError::Errored(err) => (err, false), + }; + + MediaSettingsUpdateException::new(message, err, rolled_back).into() } } @@ -134,16 +192,13 @@ pub unsafe extern "C" fn RoomHandle__set_local_media_settings( settings: ptr::NonNull, stop_first: bool, rollback_on_fail: bool, -) -> DartFuture>> { +) -> DartFuture> { let this = this.as_ref().clone(); let settings = settings.as_ref().clone(); async move { - // TODO: Remove unwrap when ConstraintsUpdateException bindings will be - // implemented. this.set_local_media_settings(settings, stop_first, rollback_on_fail) - .await - .unwrap(); + .await?; Ok(()) } .into_dart_future() @@ -222,16 +277,11 @@ pub unsafe extern "C" fn RoomHandle__disable_audio( pub unsafe extern "C" fn RoomHandle__mute_video( this: ptr::NonNull, source_kind: DartValueArg>, -) -> DartFuture>> { - // TODO: Remove unwraps when propagating fatal errors from Rust to Dart is - // implemented. +) -> DartFuture> { let this = this.as_ref().clone(); - let source_kind = Option::::try_from(source_kind) - .unwrap() - .map(MediaSourceKind::from); async move { - this.mute_video(source_kind).await?; + this.mute_video(source_kind.try_into()?).await?; Ok(()) } .into_dart_future() @@ -246,16 +296,11 @@ pub unsafe extern "C" fn RoomHandle__mute_video( pub unsafe extern "C" fn RoomHandle__unmute_video( this: ptr::NonNull, source_kind: DartValueArg>, -) -> DartFuture>> { +) -> DartFuture> { let this = this.as_ref().clone(); - // TODO: Remove unwraps when propagating fatal errors from Rust to Dart is - // implemented. - let source_kind = Option::::try_from(source_kind) - .unwrap() - .map(MediaSourceKind::from); async move { - this.unmute_video(source_kind).await?; + this.unmute_video(source_kind.try_into()?).await?; Ok(()) } .into_dart_future() @@ -268,16 +313,11 @@ pub unsafe extern "C" fn RoomHandle__unmute_video( pub unsafe extern "C" fn RoomHandle__enable_video( this: ptr::NonNull, source_kind: DartValueArg>, -) -> DartFuture>> { +) -> DartFuture> { let this = this.as_ref().clone(); - // TODO: Remove unwraps when propagating fatal errors from Rust to Dart is - // implemented. - let source_kind = Option::::try_from(source_kind) - .unwrap() - .map(MediaSourceKind::from); async move { - this.enable_video(source_kind).await?; + this.enable_video(source_kind.try_into()?).await?; Ok(()) } .into_dart_future() @@ -290,16 +330,11 @@ pub unsafe extern "C" fn RoomHandle__enable_video( pub unsafe extern "C" fn RoomHandle__disable_video( this: ptr::NonNull, source_kind: DartValueArg>, -) -> DartFuture>> { - // TODO: Remove unwraps when propagating fatal errors from Rust to Dart is - // implemented. +) -> DartFuture> { let this = this.as_ref().clone(); - let source_kind = Option::::try_from(source_kind) - .unwrap() - .map(MediaSourceKind::from); async move { - this.disable_video(source_kind).await?; + this.disable_video(source_kind.try_into()?).await?; Ok(()) } .into_dart_future() @@ -438,6 +473,19 @@ pub unsafe extern "C" fn RoomHandle__on_connection_loss( .into() } +/// Sets callback, invoked on local media acquisition failures. +#[no_mangle] +pub unsafe extern "C" fn RoomHandle__on_failed_local_media( + this: ptr::NonNull, + cb: Dart_Handle, +) -> DartResult { + let this = this.as_ref(); + + this.on_failed_local_media(platform::Function::new(cb)) + .map_err(DartError::from) + .into() +} + /// Frees the data behind the provided pointer. /// /// # Safety @@ -449,16 +497,44 @@ pub unsafe extern "C" fn RoomHandle__free(this: ptr::NonNull) { drop(RoomHandle::from_ptr(this)); } +impl TryFrom>> + for Option +{ + type Error = DartError; + + fn try_from( + source_kind: DartValueArg>, + ) -> Result { + Option::::try_from(source_kind) + .map_err(|err| { + let message = err.to_string(); + ArgumentError::new(err.into_value(), "kind", message) + })? + .map(MediaSourceKind::try_from) + .transpose() + .map_err(|err| { + ArgumentError::new( + err, + "kind", + "could not build `MediaSourceKind` enum from the \ + provided value", + ) + .into() + }) + } +} + #[cfg(feature = "mockable")] mod mock { use tracerr::Traced; use crate::{ api::{ - ConnectionHandle, LocalMediaTrack, MediaStreamSettings, - ReconnectHandle, + dart::utils::DartError, ConnectionHandle, LocalMediaTrack, + MediaStreamSettings, ReconnectHandle, }, media::MediaSourceKind, + peer::{LocalMediaError, TracksRequestError, UpdateLocalStreamError}, platform, room::{ ChangeMediaStateError, ConstraintsUpdateError, HandleDetachedError, @@ -517,19 +593,27 @@ mod mock { .map(drop) } - // pub fn on_failed_local_media( - // &self, - // f: Callback, - // ) { - // // Result<(), JasonError> - // } + pub fn on_failed_local_media( + &self, + cb: platform::Function, + ) -> Result<(), Traced> { + cb.call1( + tracerr::new!(LocalMediaError::UpdateLocalStreamError( + UpdateLocalStreamError::InvalidLocalTracks( + TracksRequestError::NoTracks, + ), + )) + .into(), + ); + Ok(()) + } pub async fn set_local_media_settings( &self, _settings: MediaStreamSettings, _stop_first: bool, _rollback_on_fail: bool, - ) -> Result<(), Traced> { + ) -> Result<(), ConstraintsUpdateError> { Ok(()) } diff --git a/jason/src/api/dart/unimplemented.rs b/jason/src/api/dart/unimplemented.rs deleted file mode 100644 index e4365b613..000000000 --- a/jason/src/api/dart/unimplemented.rs +++ /dev/null @@ -1,17 +0,0 @@ -// struct ConstrainsUpdateException {} -// -// impl ConstrainsUpdateException { -// pub fn name(&self) -> String -// pub fn recover_reason(&self) -> Option -// pub fn recover_fail_reasons(&self) -> Option -// pub fn error(&self) -> Option -// } -// -// struct JasonError {} -// -// impl JasonError { -// pub fn name(&self) -> String -// pub fn message(&self) -> String -// pub fn trace(&self) -> String -// pub fn source(&self) -> Option -// } diff --git a/jason/src/api/dart/utils/err.rs b/jason/src/api/dart/utils/err.rs index 88a578c44..07987a735 100644 --- a/jason/src/api/dart/utils/err.rs +++ b/jason/src/api/dart/utils/err.rs @@ -10,8 +10,7 @@ use tracerr::{Trace, Traced}; use crate::{ api::dart::{utils::string_into_c_str, DartValue}, platform, - rpc::SessionError, - utils::JsCaused as _, + room::ChangeMediaStateError, }; /// Pointer to an extern function that returns a new Dart [`ArgumentError`] with @@ -66,6 +65,31 @@ type NewRpcClientExceptionCaller = extern "C" fn( stacktrace: ptr::NonNull, ) -> Dart_Handle; +/// Pointer to an extern function that returns a new Dart +/// [`MediaStateTransitionException`] with the provided error `message` and +/// `stacktrace`. +type NewMediaStateTransitionExceptionCaller = extern "C" fn( + message: ptr::NonNull, + stacktrace: ptr::NonNull, +) -> Dart_Handle; + +/// Pointer to an extern function that returns a new Dart [`InternalException`] +/// with the provided error `message`, `cause` and `stacktrace`. +type NewInternalExceptionCaller = extern "C" fn( + message: ptr::NonNull, + cause: DartValue, + stacktrace: ptr::NonNull, +) -> Dart_Handle; + +/// Pointer to an extern function that returns a new Dart +/// [`MediaSettingsUpdateException`] with the provided error `message`, `cause` +/// and `rolled_back` property. +type NewMediaSettingsUpdateExceptionCaller = extern "C" fn( + message: ptr::NonNull, + cause: DartError, + rolled_back: u8, +) -> Dart_Handle; + /// Stores pointer to the [`NewArgumentErrorCaller`] extern function. /// /// Must be initialized by Dart during FFI initialization phase. @@ -103,6 +127,28 @@ static mut NEW_RPC_CLIENT_EXCEPTION_CALLER: Option< NewRpcClientExceptionCaller, > = None; +/// Stores pointer to the [`NewMediaStateTransitionExceptionCaller`] extern +/// function. +/// +/// Must be initialized by Dart during FFI initialization phase. +static mut NEW_MEDIA_STATE_TRANSITION_EXCEPTION_CALLER: Option< + NewMediaStateTransitionExceptionCaller, +> = None; + +/// Stores pointer to the [`NewInternalExceptionCaller`] extern function. +/// +/// Must be initialized by Dart during FFI initialization phase. +static mut NEW_INTERNAL_EXCEPTION_CALLER: Option = + None; + +/// Stores pointer to the [`NewMediaSettingsUpdateExceptionCaller`] extern +/// function. +/// +/// Must be initialized by Dart during FFI initialization phase. +static mut NEW_MEDIA_SETTINGS_UPDATE_EXCEPTION_CALLER: Option< + NewMediaSettingsUpdateExceptionCaller, +> = None; + /// Registers the provided [`NewArgumentErrorCaller`] as /// [`NEW_ARGUMENT_ERROR_CALLER`]. /// @@ -181,6 +227,45 @@ pub unsafe extern "C" fn register_new_rpc_client_exception_caller( NEW_RPC_CLIENT_EXCEPTION_CALLER = Some(f); } +/// Registers the provided [`NewMediaStateTransitionExceptionCaller`] as +/// [`NEW_MEDIA_STATE_TRANSITION_EXCEPTION_CALLER`]. +/// +/// # Safety +/// +/// Must ONLY be called by Dart during FFI initialization. +#[no_mangle] +pub unsafe extern "C" fn register_new_media_state_transition_exception_caller( + f: NewMediaStateTransitionExceptionCaller, +) { + NEW_MEDIA_STATE_TRANSITION_EXCEPTION_CALLER = Some(f); +} + +/// Registers the provided [`NewInternalExceptionCaller`] as +/// [`NEW_INTERNAL_EXCEPTION_CALLER`]. +/// +/// # Safety +/// +/// Must ONLY be called by Dart during FFI initialization. +#[no_mangle] +pub unsafe extern "C" fn register_new_internal_exception_caller( + f: NewInternalExceptionCaller, +) { + NEW_INTERNAL_EXCEPTION_CALLER = Some(f); +} + +/// Registers the provided [`NewMediaSettingsUpdateExceptionCaller`] as +/// [`NEW_MEDIA_SETTINGS_UPDATE_EXCEPTION_CALLER`]. +/// +/// # Safety +/// +/// Must ONLY be called by Dart during FFI initialization. +#[no_mangle] +pub unsafe extern "C" fn register_new_media_settings_update_exception_caller( + f: NewMediaSettingsUpdateExceptionCaller, +) { + NEW_MEDIA_SETTINGS_UPDATE_EXCEPTION_CALLER = Some(f); +} + /// An error that can be returned from Rust to Dart. #[derive(Into)] #[repr(transparent)] @@ -420,11 +505,6 @@ pub enum RpcClientExceptionKind { /// RPC session has been finished. This is a terminal state. SessionFinished, - - /// Internal error that is not meant to be handled by external users. - /// - /// This is a programmatic error. - InternalError, } /// Exceptions thrown from an RPC client that implements messaging with media @@ -477,28 +557,134 @@ impl From for DartError { } } -impl From> for RpcClientException { - fn from(err: Traced) -> Self { - use RpcClientExceptionKind as Kind; - use SessionError as SE; - - let (err, trace) = err.into_parts(); - let message = err.to_string(); - - let mut cause = None; - let kind = match err { - SE::SessionFinished(_) => Kind::SessionFinished, - SE::NoCredentials - | SE::SessionUnexpectedlyDropped - | SE::NewConnectionInfo => Kind::InternalError, - SE::RpcClient(err) => { - cause = err.js_cause(); - Kind::InternalError - } - SE::AuthorizationFailed => Kind::AuthorizationFailed, - SE::ConnectionLost(_) => Kind::ConnectionLost, - }; - - RpcClientException::new(kind, message, cause, trace) +/// Exception thrown when the requested media state transition could not be +/// performed. +pub struct MediaStateTransitionException { + /// Error message describing the problem. + message: Cow<'static, str>, + + /// Stacktrace of this [`MediaStateTransitionException`]. + trace: Trace, +} + +impl MediaStateTransitionException { + /// Creates a new [`MediaStateTransitionException`] from the provided error + /// `message` and `trace`. + #[inline] + #[must_use] + pub fn new>>(message: T, trace: Trace) -> Self { + Self { + message: message.into(), + trace, + } + } +} + +impl From for DartError { + #[inline] + fn from(err: MediaStateTransitionException) -> Self { + unsafe { + Self::new(NEW_MEDIA_STATE_TRANSITION_EXCEPTION_CALLER.unwrap()( + string_into_c_str(err.message.into_owned()), + string_into_c_str(err.trace.to_string()), + )) + } + } +} + +/// Jason's internal exception. +/// +/// This is either a programmatic error or some unexpected platform component +/// failure that cannot be handled in any way. +/// +/// It can be converted into a [`DartError`] and passed to Dart. +pub struct InternalException { + /// Error message describing the problem. + message: Cow<'static, str>, + + /// [`platform::Error`] that caused this [`RpcClientException`]. + cause: Option, + + /// Stacktrace of this [`InternalException`]. + trace: Trace, +} + +impl InternalException { + /// Creates a new [`InternalException`] from the provided error `message`, + /// `trace` and an optional `cause`. + #[inline] + #[must_use] + pub fn new>>( + message: T, + cause: Option, + trace: Trace, + ) -> Self { + Self { + message: message.into(), + trace, + cause, + } + } +} + +impl From for DartError { + #[inline] + fn from(err: InternalException) -> Self { + unsafe { + Self::new(NEW_INTERNAL_EXCEPTION_CALLER.unwrap()( + string_into_c_str(err.message.into_owned()), + err.cause.map(DartError::from).into(), + string_into_c_str(err.trace.to_string()), + )) + } + } +} + +/// Errors occurring in [`RoomHandle::set_local_media_settings()`][1] method. +/// +/// It can be converted into a [`DartError`] and passed to Dart. +/// +/// [1]: crate::api::RoomHandle::set_local_media_settings +pub struct MediaSettingsUpdateException { + /// Error message describing the problem. + message: Cow<'static, str>, + + /// Original [`ChangeMediaStateError`] that was encountered while updating + /// local media settings. + cause: Traced, + + /// Whether media settings were successfully rolled back after new settings + /// application failed. + rolled_back: bool, +} + +impl MediaSettingsUpdateException { + /// Creates a new [`MediaSettingsUpdateException`] from the provided error + /// `message`, `cause` and `rolled_back` property. + #[inline] + #[must_use] + pub fn new>>( + message: T, + cause: Traced, + rolled_back: bool, + ) -> Self { + Self { + message: message.into(), + rolled_back, + cause, + } + } +} + +impl From for DartError { + #[inline] + fn from(err: MediaSettingsUpdateException) -> Self { + unsafe { + Self::new(NEW_MEDIA_SETTINGS_UPDATE_EXCEPTION_CALLER.unwrap()( + string_into_c_str(err.message.into_owned()), + err.cause.into(), + err.rolled_back as u8, + )) + } } } diff --git a/jason/src/api/dart/utils/mod.rs b/jason/src/api/dart/utils/mod.rs index 28ed0765d..3d10bebeb 100644 --- a/jason/src/api/dart/utils/mod.rs +++ b/jason/src/api/dart/utils/mod.rs @@ -16,8 +16,10 @@ pub use self::{ arrays::PtrArray, err::{ ArgumentError, DartError, EnumerateDevicesException, FormatException, - LocalMediaInitException, LocalMediaInitExceptionKind, - RpcClientException, RpcClientExceptionKind, StateError, + InternalException, LocalMediaInitException, + LocalMediaInitExceptionKind, MediaSettingsUpdateException, + MediaStateTransitionException, RpcClientException, + RpcClientExceptionKind, StateError, }, result::DartResult, string::{c_str_into_string, string_into_c_str}, diff --git a/jason/src/api/wasm/connection_handle.rs b/jason/src/api/wasm/connection_handle.rs index 08198f137..6796ce88d 100644 --- a/jason/src/api/wasm/connection_handle.rs +++ b/jason/src/api/wasm/connection_handle.rs @@ -3,7 +3,7 @@ use derive_more::From; use wasm_bindgen::prelude::*; -use crate::{api::JasonError, connection}; +use crate::{api, connection}; /// Connection with a specific remote `Member`, that is used on JS side. /// @@ -22,7 +22,7 @@ impl ConnectionHandle { pub fn on_close(&self, cb: js_sys::Function) -> Result<(), JsValue> { self.0 .on_close(cb.into()) - .map_err(JasonError::from) + .map_err(api::Error::from) .map_err(JsValue::from) } @@ -30,7 +30,7 @@ impl ConnectionHandle { pub fn get_remote_member_id(&self) -> Result { self.0 .get_remote_member_id() - .map_err(JasonError::from) + .map_err(api::Error::from) .map_err(JsValue::from) } @@ -45,7 +45,7 @@ impl ConnectionHandle { ) -> Result<(), JsValue> { self.0 .on_remote_track_added(cb.into()) - .map_err(JasonError::from) + .map_err(api::Error::from) .map_err(JsValue::from) } @@ -57,7 +57,7 @@ impl ConnectionHandle { ) -> Result<(), JsValue> { self.0 .on_quality_score_update(cb.into()) - .map_err(JasonError::from) + .map_err(api::Error::from) .map_err(JsValue::from) } } diff --git a/jason/src/api/wasm/constraints_update_exception.rs b/jason/src/api/wasm/constraints_update_exception.rs index ca5350692..864dbda65 100644 --- a/jason/src/api/wasm/constraints_update_exception.rs +++ b/jason/src/api/wasm/constraints_update_exception.rs @@ -7,7 +7,7 @@ use wasm_bindgen::prelude::*; use crate::room; -use super::JasonError; +use super::Error; /// Exception returned from [`RoomHandle::set_local_media_settings()`][1]. /// @@ -25,32 +25,32 @@ impl ConstraintsUpdateException { self.0.name() } - /// Returns [`JasonError`] if this [`ConstraintsUpdateException`] represents + /// Returns an [`Error`] if this [`ConstraintsUpdateException`] represents /// a `RecoveredException` or a `RecoverFailedException`. /// /// Returns `undefined` otherwise. - pub fn recover_reason(&self) -> Option { + pub fn recover_reason(&self) -> Option { self.0.recover_reason().map(Into::into) } - /// Returns [`js_sys::Array`] with the [`JasonError`]s if this + /// Returns [`js_sys::Array`] with an [`Error`]s if this /// [`ConstraintsUpdateException`] represents a `RecoverFailedException`. #[must_use] pub fn recover_fail_reasons(&self) -> JsValue { self.0 .recover_fail_reasons() .into_iter() - .map(JasonError::from) + .map(Error::from) .map(JsValue::from) .collect::() .into() } - /// Returns [`JasonError`] if this [`ConstraintsUpdateException`] represents + /// Returns [`Error`] if this [`ConstraintsUpdateException`] represents /// an `ErroredException`. /// /// Returns `undefined` otherwise. - pub fn error(&self) -> Option { + pub fn error(&self) -> Option { self.0.error().map(Into::into) } } diff --git a/jason/src/api/wasm/media_manager_handle.rs b/jason/src/api/wasm/media_manager_handle.rs index 0010a33bf..a1fbf55f2 100644 --- a/jason/src/api/wasm/media_manager_handle.rs +++ b/jason/src/api/wasm/media_manager_handle.rs @@ -12,7 +12,7 @@ use crate::{ media, }; -use super::JasonError; +use super::Error; /// [`MediaManagerHandle`] is a weak reference to a [`MediaManager`]. /// @@ -58,7 +58,7 @@ impl MediaManagerHandle { }) .into() }) - .map_err(JasonError::from) + .map_err(Error::from) .map_err(JsValue::from) }) } @@ -83,7 +83,7 @@ impl MediaManagerHandle { }) .into() }) - .map_err(JasonError::from) + .map_err(Error::from) .map_err(JsValue::from) }) } diff --git a/jason/src/api/wasm/mod.rs b/jason/src/api/wasm/mod.rs index d3c2f72bd..117a140ff 100644 --- a/jason/src/api/wasm/mod.rs +++ b/jason/src/api/wasm/mod.rs @@ -26,7 +26,7 @@ pub use self::{ constraints_update_exception::ConstraintsUpdateException, input_device_info::InputDeviceInfo, jason::Jason, - jason_error::JasonError, + jason_error::JasonError as Error, local_media_track::LocalMediaTrack, media_manager_handle::MediaManagerHandle, media_stream_settings::{ diff --git a/jason/src/api/wasm/reconnect_handle.rs b/jason/src/api/wasm/reconnect_handle.rs index 2302dbe92..9e78096fb 100644 --- a/jason/src/api/wasm/reconnect_handle.rs +++ b/jason/src/api/wasm/reconnect_handle.rs @@ -7,7 +7,7 @@ use wasm_bindgen_futures::future_to_promise; use crate::rpc; -use super::JasonError; +use super::Error; /// Handle that JS side can reconnect to a media server with when a connection /// is lost. @@ -37,7 +37,7 @@ impl ReconnectHandle { future_to_promise(async move { this.reconnect_with_delay(delay_ms) .await - .map_err(JasonError::from)?; + .map_err(Error::from)?; Ok(JsValue::UNDEFINED) }) } @@ -82,7 +82,7 @@ impl ReconnectHandle { max_elapsed_time_ms, ) .await - .map_err(JasonError::from)?; + .map_err(Error::from)?; Ok(JsValue::UNDEFINED) }) } diff --git a/jason/src/api/wasm/room_handle.rs b/jason/src/api/wasm/room_handle.rs index 51a34cce5..3d48d8ec0 100644 --- a/jason/src/api/wasm/room_handle.rs +++ b/jason/src/api/wasm/room_handle.rs @@ -12,7 +12,7 @@ use crate::{ room, }; -use super::JasonError; +use super::Error; /// JS side handle to a [`Room`] where all the media happens. /// @@ -48,7 +48,7 @@ impl RoomHandle { let this = self.0.clone(); future_to_promise(async move { - this.join(token).await.map_err(JasonError::from)?; + this.join(token).await.map_err(Error::from)?; Ok(JsValue::UNDEFINED) }) } @@ -63,7 +63,7 @@ impl RoomHandle { ) -> Result<(), JsValue> { self.0 .on_new_connection(cb.into()) - .map_err(JasonError::from) + .map_err(Error::from) .map_err(JsValue::from) } @@ -75,7 +75,7 @@ impl RoomHandle { pub fn on_close(&self, cb: js_sys::Function) -> Result<(), JsValue> { self.0 .on_close(cb.into()) - .map_err(JasonError::from) + .map_err(Error::from) .map_err(JsValue::from) } @@ -92,7 +92,7 @@ impl RoomHandle { pub fn on_local_track(&self, cb: js_sys::Function) -> Result<(), JsValue> { self.0 .on_local_track(cb.into()) - .map_err(JasonError::from) + .map_err(Error::from) .map_err(JsValue::from) } @@ -104,7 +104,7 @@ impl RoomHandle { ) -> Result<(), JsValue> { self.0 .on_failed_local_media(cb.into()) - .map_err(JasonError::from) + .map_err(Error::from) .map_err(JsValue::from) } @@ -116,7 +116,7 @@ impl RoomHandle { ) -> Result<(), JsValue> { self.0 .on_connection_loss(cb.into()) - .map_err(JasonError::from) + .map_err(Error::from) .map_err(JsValue::from) } @@ -181,7 +181,7 @@ impl RoomHandle { let this = self.0.clone(); future_to_promise(async move { - this.mute_audio().await.map_err(JasonError::from)?; + this.mute_audio().await.map_err(Error::from)?; Ok(JsValue::UNDEFINED) }) } @@ -199,7 +199,7 @@ impl RoomHandle { let this = self.0.clone(); future_to_promise(async move { - this.unmute_audio().await.map_err(JasonError::from)?; + this.unmute_audio().await.map_err(Error::from)?; Ok(JsValue::UNDEFINED) }) } @@ -219,7 +219,7 @@ impl RoomHandle { future_to_promise(async move { this.mute_video(source_kind.map(Into::into)) .await - .map_err(JasonError::from)?; + .map_err(Error::from)?; Ok(JsValue::UNDEFINED) }) } @@ -242,7 +242,7 @@ impl RoomHandle { future_to_promise(async move { this.unmute_video(source_kind.map(Into::into)) .await - .map_err(JasonError::from)?; + .map_err(Error::from)?; Ok(JsValue::UNDEFINED) }) } @@ -261,7 +261,7 @@ impl RoomHandle { let this = self.0.clone(); future_to_promise(async move { - this.disable_audio().await.map_err(JasonError::from)?; + this.disable_audio().await.map_err(Error::from)?; Ok(JsValue::UNDEFINED) }) } @@ -282,7 +282,7 @@ impl RoomHandle { let this = self.0.clone(); future_to_promise(async move { - this.enable_audio().await.map_err(JasonError::from)?; + this.enable_audio().await.map_err(Error::from)?; Ok(JsValue::UNDEFINED) }) } @@ -306,7 +306,7 @@ impl RoomHandle { future_to_promise(async move { this.disable_video(source_kind.map(Into::into)) .await - .map_err(JasonError::from)?; + .map_err(Error::from)?; Ok(JsValue::UNDEFINED) }) } @@ -332,7 +332,7 @@ impl RoomHandle { future_to_promise(async move { this.enable_video(source_kind.map(Into::into)) .await - .map_err(JasonError::from)?; + .map_err(Error::from)?; Ok(JsValue::UNDEFINED) }) } @@ -350,9 +350,7 @@ impl RoomHandle { let this = self.0.clone(); future_to_promise(async move { - this.disable_remote_audio() - .await - .map_err(JasonError::from)?; + this.disable_remote_audio().await.map_err(Error::from)?; Ok(JsValue::UNDEFINED) }) } @@ -370,9 +368,7 @@ impl RoomHandle { let this = self.0.clone(); future_to_promise(async move { - this.disable_remote_video() - .await - .map_err(JasonError::from)?; + this.disable_remote_video().await.map_err(Error::from)?; Ok(JsValue::UNDEFINED) }) } @@ -390,7 +386,7 @@ impl RoomHandle { let this = self.0.clone(); future_to_promise(async move { - this.enable_remote_audio().await.map_err(JasonError::from)?; + this.enable_remote_audio().await.map_err(Error::from)?; Ok(JsValue::UNDEFINED) }) } @@ -408,7 +404,7 @@ impl RoomHandle { let this = self.0.clone(); future_to_promise(async move { - this.enable_remote_video().await.map_err(JasonError::from)?; + this.enable_remote_video().await.map_err(Error::from)?; Ok(JsValue::UNDEFINED) }) } diff --git a/jason/src/peer/component/watchers.rs b/jason/src/peer/component/watchers.rs index be2c832ec..5ae804d02 100644 --- a/jason/src/peer/component/watchers.rs +++ b/jason/src/peer/component/watchers.rs @@ -170,7 +170,7 @@ impl Component { .map_err(|e| { drop(peer.peer_events_sender.unbounded_send( PeerEvent::FailedLocalMedia { - error: e.clone().into(), + error: tracerr::map_from(e.clone()), }, )); e diff --git a/jason/src/peer/media/mod.rs b/jason/src/peer/media/mod.rs index fbe307a29..a8c13fc8f 100644 --- a/jason/src/peer/media/mod.rs +++ b/jason/src/peer/media/mod.rs @@ -211,7 +211,7 @@ pub enum TrackDirection { } /// Error occurring when media state transition is not allowed. -#[derive(Clone, Debug, Display, From)] +#[derive(Clone, Debug, Display)] pub enum ProhibitedStateError { /// [`Sender`] cannot be disabled because it's required. #[display(fmt = "MediaExchangeState of Sender can't transit to \ @@ -232,7 +232,7 @@ pub enum InsertLocalTracksError { NotEnoughTracks, /// Insertion of a [`local::Track`] into a [`Sender`] fails. - CouldNotInsertLocalTrack(sender::InsertTrackError), + CouldNotInsertLocalTrack(#[js(cause)] sender::InsertTrackError), } /// Errors occurring in [`MediaConnections::get_mids()`] method. diff --git a/jason/src/peer/mod.rs b/jason/src/peer/mod.rs index d64f52927..86264bc02 100644 --- a/jason/src/peer/mod.rs +++ b/jason/src/peer/mod.rs @@ -26,7 +26,6 @@ use medea_macro::dispatchable; use tracerr::Traced; use crate::{ - api::JasonError, connection::Connections, media::{ track::{local, remote}, @@ -91,6 +90,20 @@ pub enum TrackEvent { }, } +/// Local media update errors that [`PeerConnection`] reports in +/// [`PeerEvent::FailedLocalMedia`] messages. +#[derive(Clone, Debug, Display, From, JsCaused)] +#[js(error = "platform::Error")] +pub enum LocalMediaError { + /// Error occurred in [`PeerConnection::update_local_stream()`] method. + UpdateLocalStreamError(#[js(cause)] UpdateLocalStreamError), + + /// Error occurred when creating a new [`Sender`]. + /// + /// [`Sender`]: sender::Sender + SenderCreateError(sender::CreateError), +} + /// Events emitted from [`platform::RtcPeerConnection`]. #[dispatchable(self: &Self, async_trait(?Send))] #[derive(Clone)] @@ -181,7 +194,7 @@ pub enum PeerEvent { /// `on_failed_local_stream` callback should be called. FailedLocalMedia { /// Reasons of local media updating fail. - error: JasonError, + error: Traced, }, /// [`Component`] generated a new SDP answer. @@ -681,7 +694,7 @@ impl PeerConnection { self.inner_update_local_stream(criteria).await.map_err(|e| { drop(self.peer_events_sender.unbounded_send( PeerEvent::FailedLocalMedia { - error: JasonError::from(e.clone()), + error: tracerr::map_from(e.clone()), }, )); e diff --git a/jason/src/room.rs b/jason/src/room.rs index d7a01e375..bac2af626 100644 --- a/jason/src/room.rs +++ b/jason/src/room.rs @@ -20,7 +20,6 @@ use tracerr::Traced; use crate::{ api, - api::JasonError, connection::Connections, media::{ track::{local, remote}, @@ -29,9 +28,9 @@ use crate::{ }, peer::{ self, media::ProhibitedStateError, media_exchange_state, mute_state, - InsertLocalTracksError, LocalStreamUpdateCriteria, MediaState, - PeerConnection, PeerEvent, PeerEventHandler, TrackDirection, - TracksRequestError, UpdateLocalStreamError, + InsertLocalTracksError, LocalMediaError, LocalStreamUpdateCriteria, + MediaState, PeerConnection, PeerEvent, PeerEventHandler, + TrackDirection, TracksRequestError, UpdateLocalStreamError, }, platform, rpc::{ @@ -317,7 +316,7 @@ impl RoomHandle { /// See [`HandleDetachedError`] for details. pub fn on_failed_local_media( &self, - f: platform::Function, + f: platform::Function, ) -> Result<(), Traced> { upgrade_inner!(self.0) .map(|inner| inner.on_failed_local_media.set_func(f)) @@ -958,7 +957,7 @@ struct InnerRoom { /// Callback invoked when failed obtain [`local::Track`]s from /// [`MediaManager`] or failed inject stream into [`PeerConnection`]. - on_failed_local_media: Rc>, + on_failed_local_media: Rc>, /// Callback invoked when a [`RpcSession`] loses connection. on_connection_loss: platform::Callback, @@ -983,10 +982,7 @@ pub enum ConstraintsUpdateError { /// accordingly to the provided recover policy /// (`rollback_on_fail`/`stop_first` arguments). #[display(fmt = "RecoveredException")] - Recovered { - /// [`ChangeMediaStateError`] due to which recovery has happened. - recover_reason: Traced, - }, + Recovered(Traced), /// New [`MediaStreamSettings`] set failed and state recovering also /// failed. @@ -1019,9 +1015,7 @@ impl ConstraintsUpdateError { pub fn recover_reason(&self) -> Option> { match &self { Self::RecoverFailed { recover_reason, .. } - | Self::Recovered { recover_reason, .. } => { - Some(recover_reason.clone()) - } + | Self::Recovered(recover_reason) => Some(recover_reason.clone()), Self::Errored(_) => None, } } @@ -1055,7 +1049,7 @@ impl ConstraintsUpdateError { #[inline] #[must_use] fn recovered(recover_reason: Traced) -> Self { - Self::Recovered { recover_reason } + Self::Recovered(recover_reason) } /// Converts this [`ChangeMediaStateError`] to the @@ -1063,7 +1057,7 @@ impl ConstraintsUpdateError { #[must_use] fn recovery_failed(self, reason: Traced) -> Self { match self { - Self::Recovered { recover_reason } => Self::RecoverFailed { + Self::Recovered(recover_reason) => Self::RecoverFailed { recover_reason: reason, recover_fail_reasons: vec![recover_reason], }, @@ -1313,13 +1307,12 @@ impl InnerRoom { .media_manager .get_tracks(req) .await - .map_err(tracerr::map_from_and_wrap!()) .map_err(|e| { - self.on_failed_local_media - .call1(JasonError::from(e.clone())); + self.on_failed_local_media.call1(e.clone()); e - })?; + }) + .map_err(tracerr::map_from_and_wrap!())?; for (track, is_new) in tracks { if is_new { self.on_local_track @@ -1356,7 +1349,7 @@ impl InnerRoom { } /// Updates [`MediaState`]s to the provided `states_update` and disables all - /// [`Sender`]s which are doesn't have [`local::Track`]. + /// [`Sender`]s which doesn't have [`local::Track`]. /// /// [`Sender`]: peer::media::Sender async fn disable_senders_without_tracks( @@ -1393,8 +1386,8 @@ impl InnerRoom { /// Media obtaining/injection errors are fired to `on_failed_local_media` /// callback. /// - /// Will update [`media_exchange_state::Stable`]s of the [`Sender`]s which - /// are should be enabled or disabled. + /// Will update [`media_exchange_state::Stable`]s of the [`Sender`]s that + /// should be enabled or disabled. /// /// If `stop_first` set to `true` then affected [`local::Track`]s will be /// dropped before new [`MediaStreamSettings`] is applied. This is usually @@ -1808,8 +1801,11 @@ impl PeerEventHandler for InnerRoom { /// Handles [`PeerEvent::FailedLocalMedia`] event by invoking /// `on_failed_local_media` [`Room`]'s callback. - async fn on_failed_local_media(&self, error: JasonError) -> Self::Output { - self.on_failed_local_media.call1(error); + async fn on_failed_local_media( + &self, + error: Traced, + ) -> Self::Output { + self.on_failed_local_media.call1(api::Error::from(error)); Ok(()) } diff --git a/jason/src/rpc/mod.rs b/jason/src/rpc/mod.rs index b702fd8c8..ffc6b24b6 100644 --- a/jason/src/rpc/mod.rs +++ b/jason/src/rpc/mod.rs @@ -3,7 +3,7 @@ mod backoff_delayer; mod heartbeat; mod reconnect_handle; -mod rpc_session; +pub mod rpc_session; pub mod websocket; use std::str::FromStr; diff --git a/jason/tests/room/room.rs b/jason/tests/room/room.rs index 7a23db2c6..2a8063cb7 100644 --- a/jason/tests/room/room.rs +++ b/jason/tests/room/room.rs @@ -139,8 +139,8 @@ async fn error_get_local_stream_on_new_peer() { .await .unwrap(); - let (cb, test_result) = js_callback!(|err: api::JasonError| { - cb_assert_eq!(&err.name(), "CouldNotGetLocalMedia"); + let (cb, test_result) = js_callback!(|err: api::Error| { + cb_assert_eq!(&err.name(), "UpdateLocalStreamError"); cb_assert_eq!( &err.message(), "Failed to get local tracks: MediaDevices.getUserMedia() failed: \ @@ -2180,8 +2180,8 @@ mod set_local_media_settings { let (room, _rx) = get_test_room(Box::pin(event_rx)); let room_handle = api::RoomHandle::from(room.new_handle()); - let (cb, test_result) = js_callback!(|err: api::JasonError| { - cb_assert_eq!(&err.name(), "CannotDisableRequiredSender"); + let (cb, test_result) = js_callback!(|err: api::Error| { + cb_assert_eq!(&err.name(), "SenderCreateError"); cb_assert_eq!( err.message(), "MediaExchangeState of Sender cannot transit to \ @@ -2226,8 +2226,8 @@ mod set_local_media_settings { /// 1. `on_failed_local_media` was invoked. #[wasm_bindgen_test] async fn error_inject_invalid_local_stream_into_room_on_exists_peer() { - let (cb, test_result) = js_callback!(|err: api::JasonError| { - cb_assert_eq!(&err.name(), "InvalidLocalTracks"); + let (cb, test_result) = js_callback!(|err: api::Error| { + cb_assert_eq!(&err.name(), "UpdateLocalStreamError"); cb_assert_eq!( &err.message(), "provided multiple device video MediaStreamTracks" diff --git a/jason/tests/web.rs b/jason/tests/web.rs index 5bc2d4f78..08add93e7 100644 --- a/jason/tests/web.rs +++ b/jason/tests/web.rs @@ -140,7 +140,7 @@ extern "C" { #[wasm_bindgen(inline_js = "export const get_jason_error = (err) => err;")] extern "C" { - fn get_jason_error(err: JsValue) -> api::JasonError; + fn get_jason_error(err: JsValue) -> api::Error; } #[wasm_bindgen(