Expose Swift errors as `anyhow::Result` in live_kit

Antonio Scandurra created

Change summary

Cargo.lock                                                              |  1 
crates/capture/src/main.rs                                              |  6 
crates/live_kit/Cargo.toml                                              |  1 
crates/live_kit/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift | 12 
crates/live_kit/src/live_kit.rs                                         | 28 
5 files changed, 29 insertions(+), 19 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -2940,6 +2940,7 @@ dependencies = [
 name = "live_kit"
 version = "0.1.0"
 dependencies = [
+ "anyhow",
  "core-foundation",
  "core-graphics",
  "futures",

crates/capture/src/main.rs 🔗

@@ -45,13 +45,15 @@ fn main() {
         cx.foreground()
             .spawn(async move {
                 println!("connecting...");
-                room.connect("wss://zed.livekit.cloud", &token).await;
+                room.connect("wss://zed.livekit.cloud", &token)
+                    .await
+                    .unwrap();
                 let windows = live_kit::list_windows();
                 println!("connected! {:?}", windows);
 
                 let window_id = windows.iter().next().unwrap().id;
                 let track = LocalVideoTrack::screen_share_for_window(window_id);
-                room.publish_video_track(&track).await;
+                room.publish_video_track(&track).await.unwrap();
             })
             .detach();
 

crates/live_kit/Cargo.toml 🔗

@@ -9,6 +9,7 @@ path = "src/live_kit.rs"
 doctest = false
 
 [dependencies]
+anyhow = "1.0.38"
 core-foundation = "0.9.3"
 core-graphics = "0.22.3"
 futures = "0.3"

crates/live_kit/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift 🔗

@@ -12,24 +12,24 @@ public func LKRoomCreate() -> UnsafeMutableRawPointer  {
 }
 
 @_cdecl("LKRoomConnect")
-public func LKRoomConnect(room: UnsafeRawPointer, url: CFString, token: CFString, callback: @escaping @convention(c) (UnsafeRawPointer) -> Void, callback_data: UnsafeRawPointer) {
+public func LKRoomConnect(room: UnsafeRawPointer, url: CFString, token: CFString, callback: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void, callback_data: UnsafeRawPointer) {
     let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue();
 
     room.connect(url as String, token as String).then { _ in
-        callback(callback_data);
+        callback(callback_data, UnsafeRawPointer(nil) as! CFString?);
     }.catch { error in
-        print(error);
+        callback(callback_data, error.localizedDescription as CFString);
     };
 }
 
 @_cdecl("LKRoomPublishVideoTrack")
-public func LKRoomPublishVideoTrack(room: UnsafeRawPointer, track: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer) -> Void, callback_data: UnsafeRawPointer) {
+public func LKRoomPublishVideoTrack(room: UnsafeRawPointer, track: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void, callback_data: UnsafeRawPointer) {
     let room = Unmanaged<Room>.fromOpaque(room).takeUnretainedValue();
     let track = Unmanaged<LocalVideoTrack>.fromOpaque(track).takeUnretainedValue();
     room.localParticipant?.publishVideoTrack(track: track).then { _ in
-        callback(callback_data);
+        callback(callback_data, UnsafeRawPointer(nil) as! CFString?);
     }.catch { error in
-        print(error);
+        callback(callback_data, error.localizedDescription as CFString);
     };
 }
 

crates/live_kit/src/live_kit.rs 🔗

@@ -1,3 +1,4 @@
+use anyhow::{anyhow, Context, Result};
 use core_foundation::{
     array::CFArray,
     base::{TCFType, TCFTypeRef},
@@ -20,13 +21,13 @@ extern "C" {
         room: *const c_void,
         url: CFStringRef,
         token: CFStringRef,
-        callback: extern "C" fn(*mut c_void) -> (),
+        callback: extern "C" fn(*mut c_void, CFStringRef) -> (),
         callback_data: *mut c_void,
     );
     fn LKRoomPublishVideoTrack(
         room: *const c_void,
         track: *const c_void,
-        callback: extern "C" fn(*mut c_void) -> (),
+        callback: extern "C" fn(*mut c_void, CFStringRef) -> (),
         callback_data: *mut c_void,
     );
 
@@ -40,7 +41,7 @@ impl Room {
         Self(unsafe { LKRoomCreate() })
     }
 
-    pub fn connect(&self, url: &str, token: &str) -> impl Future<Output = ()> {
+    pub fn connect(&self, url: &str, token: &str) -> impl Future<Output = Result<()>> {
         let url = CFString::new(url);
         let token = CFString::new(token);
         let (did_connect, tx, rx) = Self::build_done_callback();
@@ -54,10 +55,10 @@ impl Room {
             )
         }
 
-        async { rx.await.unwrap() }
+        async { rx.await.unwrap().context("error connecting to room") }
     }
 
-    pub fn publish_video_track(&self, track: &LocalVideoTrack) -> impl Future<Output = ()> {
+    pub fn publish_video_track(&self, track: &LocalVideoTrack) -> impl Future<Output = Result<()>> {
         let (did_publish, tx, rx) = Self::build_done_callback();
         unsafe {
             LKRoomPublishVideoTrack(
@@ -67,18 +68,23 @@ impl Room {
                 Box::into_raw(Box::new(tx)) as *mut c_void,
             )
         }
-        async { rx.await.unwrap() }
+        async { rx.await.unwrap().context("error publishing video track") }
     }
 
     fn build_done_callback() -> (
-        extern "C" fn(*mut c_void),
+        extern "C" fn(*mut c_void, CFStringRef),
         *mut c_void,
-        oneshot::Receiver<()>,
+        oneshot::Receiver<Result<()>>,
     ) {
         let (tx, rx) = oneshot::channel();
-        extern "C" fn done_callback(tx: *mut c_void) {
-            let tx = unsafe { Box::from_raw(tx as *mut oneshot::Sender<()>) };
-            let _ = tx.send(());
+        extern "C" fn done_callback(tx: *mut c_void, error: CFStringRef) {
+            let tx = unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<()>>) };
+            if error.is_null() {
+                let _ = tx.send(Ok(()));
+            } else {
+                let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
+                let _ = tx.send(Err(anyhow!(error)));
+            }
         }
         (
             done_callback,