isolator 0.1.2

A lightweight library for isolating Rust functions
Documentation
use std::future::Future;
use std::panic::{catch_unwind, AssertUnwindSafe};
use thiserror::Error;
use tokio::task;
use tokio::runtime::Runtime;

#[derive(Error, Debug)]
pub enum IsolationError {
    #[error("Function panicked: {0}")]
    Panic(String),
    #[error("Function failed: {0}")]
    Error(#[from] Box<dyn std::error::Error + Send + Sync>),
}

pub type IsolationResult<T> = Result<T, IsolationError>;

pub fn isolate<F, T>(f: F) -> IsolationResult<T>
where
    F: FnOnce() -> T + std::panic::UnwindSafe + Send + 'static,
    T: Send + 'static,
{
    let rt = Runtime::new().unwrap();
    rt.block_on(task::spawn_blocking(move || {
        catch_unwind(f).map_err(|e| {
            let msg = if let Some(s) = e.downcast_ref::<String>() {
                s.clone()
            } else if let Some(s) = e.downcast_ref::<&str>() {
                s.to_string()
            } else {
                "Unknown panic".to_string()
            };
            IsolationError::Panic(msg)
        })
    }))
    .unwrap()
}

pub async fn isolate_async<F, Fut, T>(f: F) -> IsolationResult<T>
where
    F: FnOnce() -> Fut + std::panic::UnwindSafe + Send + 'static,
    Fut: Future<Output = T> + std::panic::UnwindSafe + Send + 'static,
    T: Send + 'static,
{
    let future = catch_unwind(AssertUnwindSafe(f))
        .map_err(|e| IsolationError::Panic(format!("{:?}", e)))?;
    
    Ok(task::spawn(AssertUnwindSafe(future)).await.unwrap())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_isolate_success() {
        let result = isolate(|| {
            println!("Running in separate thread!");
            42
        });
        assert_eq!(result.unwrap(), 42);
    }

    #[test]
    fn test_isolate_panic() {
        let result = isolate(|| panic!("test panic"));
        assert!(matches!(result, Err(IsolationError::Panic(_))));
    }

    #[tokio::test]
    async fn test_isolate_async_success() {
        let result = isolate_async(|| async {
            println!("Running async in separate task!");
            42
        }).await;
        assert_eq!(result.unwrap(), 42);
    }

    #[tokio::test]
    async fn test_isolate_async_panic() {
        let result = isolate_async(|| async { panic!("test panic") }).await;
        assert!(matches!(result, Err(IsolationError::Panic(_))));
    }
}