pub trait ResultExt<T, E> {
    fn map_err_into<U: From<E>>(self) -> Result<T, U>;
    
    fn or_else_ok<F>(self, f: F) -> Result<T, E>
    where
        F: FnOnce(E) -> T;
    
    fn and_then_async<F, Fut, U, E2>(self, f: F) -> impl std::future::Future<Output = Result<U, E2>>
    where
        F: FnOnce(T) -> Fut,
        Fut: std::future::Future<Output = Result<U, E2>>,
        E: Into<E2>;
}

impl<T, E> ResultExt<T, E> for Result<T, E> {
    fn map_err_into<U: From<E>>(self) -> Result<T, U> {
        self.map_err(Into::into)
    }
    
    fn or_else_ok<F>(self, f: F) -> Result<T, E>
    where
        F: FnOnce(E) -> T,
    {
        self.or_else(|e| Ok(f(e)))
    }
    
    fn and_then_async<F, Fut, U, E2>(self, f: F) -> impl std::future::Future<Output = Result<U, E2>>
    where
        F: FnOnce(T) -> Fut,
        Fut: std::future::Future<Output = Result<U, E2>>,
        E: Into<E2>,
    {
        async move {
            match self {
                Ok(val) => f(val).await,
                Err(e) => Err(e.into()),
            }
        }
    }
}

pub fn combine_results<T, E>(results: Vec<Result<T, E>>) -> Result<Vec<T>, Vec<E>> {
    let mut values = Vec::new();
    let mut errors = Vec::new();
    
    for result in results {
        match result {
            Ok(val) => values.push(val),
            Err(e) => errors.push(e),
        }
    }
    
    if errors.is_empty() {
        Ok(values)
    } else {
        Err(errors)
    }
}