use crate::{with_tmp_pool, Error};
pub const SVN_ERR_ITER_BREAK: i32 = 200020;
pub type IterResult<T> = Result<T, IterBreak>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IterBreak;
impl std::fmt::Display for IterBreak {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "iteration break")
}
}
impl std::error::Error for IterBreak {}
pub unsafe fn iter_hash<F>(
hash: *mut apr_sys::apr_hash_t,
mut callback: F,
) -> Result<bool, Error<'static>>
where
F: FnMut(&[u8], *mut std::ffi::c_void) -> IterResult<()>,
{
with_tmp_pool(|pool| {
let mut completed: subversion_sys::svn_boolean_t = 0;
let mut callback_wrapper =
|key: &[u8], value: *mut std::ffi::c_void| -> IterResult<()> { callback(key, value) };
extern "C" fn c_callback(
baton: *mut std::ffi::c_void,
key: *const std::ffi::c_void,
klen: apr_sys::apr_ssize_t,
val: *mut std::ffi::c_void,
_pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
let callback = unsafe {
&mut *(baton as *mut &mut dyn FnMut(&[u8], *mut std::ffi::c_void) -> IterResult<()>)
};
let key_slice = unsafe { std::slice::from_raw_parts(key as *const u8, klen as usize) };
match callback(key_slice, val) {
Ok(()) => std::ptr::null_mut(),
Err(IterBreak) => unsafe { subversion_sys::svn_iter__break() },
}
}
let callback_trait: &mut dyn FnMut(&[u8], *mut std::ffi::c_void) -> IterResult<()> =
&mut callback_wrapper;
let baton = &callback_trait as *const _ as *mut std::ffi::c_void;
unsafe {
let err = subversion_sys::svn_iter_apr_hash(
&mut completed,
hash,
Some(c_callback),
baton,
pool.as_mut_ptr(),
);
if !err.is_null()
&& (*err).apr_err == subversion_sys::svn_errno_t_SVN_ERR_ITER_BREAK as i32
{
} else {
Error::from_raw(err)?;
}
}
Ok(completed != 0)
})
}
pub unsafe fn iter_array<F>(
array: *const apr_sys::apr_array_header_t,
mut callback: F,
) -> Result<bool, Error<'static>>
where
F: FnMut(*mut std::ffi::c_void) -> IterResult<()>,
{
with_tmp_pool(|pool| {
let mut completed: subversion_sys::svn_boolean_t = 0;
let mut callback_wrapper =
|item: *mut std::ffi::c_void| -> IterResult<()> { callback(item) };
extern "C" fn c_callback(
baton: *mut std::ffi::c_void,
item: *mut std::ffi::c_void,
_pool: *mut apr_sys::apr_pool_t,
) -> *mut subversion_sys::svn_error_t {
let callback = unsafe {
&mut *(baton as *mut &mut dyn FnMut(*mut std::ffi::c_void) -> IterResult<()>)
};
match callback(item) {
Ok(()) => std::ptr::null_mut(),
Err(IterBreak) => unsafe { subversion_sys::svn_iter__break() },
}
}
let callback_trait: &mut dyn FnMut(*mut std::ffi::c_void) -> IterResult<()> =
&mut callback_wrapper;
let baton = &callback_trait as *const _ as *mut std::ffi::c_void;
unsafe {
let err = subversion_sys::svn_iter_apr_array(
&mut completed,
array,
Some(c_callback),
baton,
pool.as_mut_ptr(),
);
if !err.is_null()
&& (*err).apr_err == subversion_sys::svn_errno_t_SVN_ERR_ITER_BREAK as i32
{
} else {
Error::from_raw(err)?;
}
}
Ok(completed != 0)
})
}
pub fn break_iteration() -> IterBreak {
IterBreak
}
pub trait HashIterExt {
fn iter_entries<F>(&self, callback: F) -> Result<bool, Error<'static>>
where
F: FnMut(&[u8], *mut std::ffi::c_void) -> IterResult<()>;
}
impl HashIterExt for apr::hash::Hash<'_> {
fn iter_entries<F>(&self, callback: F) -> Result<bool, Error<'static>>
where
F: FnMut(&[u8], *mut std::ffi::c_void) -> IterResult<()>,
{
unsafe { iter_hash(self.as_ptr() as *mut _, callback) }
}
}
pub trait ArrayIterExt {
fn iter_items<F>(&self, callback: F) -> Result<bool, Error<'static>>
where
F: FnMut(*mut std::ffi::c_void) -> IterResult<()>;
}
impl<T: Copy> ArrayIterExt for apr::tables::TypedArray<'_, T> {
fn iter_items<F>(&self, callback: F) -> Result<bool, Error<'static>>
where
F: FnMut(*mut std::ffi::c_void) -> IterResult<()>,
{
unsafe { iter_array(self.as_ptr(), callback) }
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[test]
fn test_basic_hash_iteration() {
let pool = apr::Pool::new();
let mut hash = apr::hash::Hash::new(&pool);
let val1 = 42i32;
let val2 = 84i32;
unsafe {
hash.insert(b"key1", &val1 as *const _ as *mut std::ffi::c_void);
}
unsafe {
hash.insert(b"key2", &val2 as *const _ as *mut std::ffi::c_void);
}
let mut collected = HashMap::new();
let completed = unsafe {
iter_hash(hash.as_ptr() as *mut _, |key, value| {
let key_str = String::from_utf8_lossy(key).into_owned();
let value_int = *(value as *const i32);
collected.insert(key_str, value_int);
Ok(())
})
}
.unwrap();
assert!(completed);
assert_eq!(collected.len(), 2);
assert_eq!(collected.get("key1"), Some(&42));
assert_eq!(collected.get("key2"), Some(&84));
}
#[test]
fn test_basic_hash_iteration_break() {
let pool = apr::Pool::new();
let mut hash = apr::hash::Hash::new(&pool);
let val1 = 42i32;
let val2 = 84i32;
unsafe {
hash.insert(b"key1", &val1 as *const _ as *mut std::ffi::c_void);
}
unsafe {
hash.insert(b"key2", &val2 as *const _ as *mut std::ffi::c_void);
}
let mut count = 0;
let completed = unsafe {
iter_hash(hash.as_ptr() as *mut _, |_key, _value| {
count += 1;
if count >= 1 {
Err(break_iteration())
} else {
Ok(())
}
})
}
.unwrap();
assert!(!completed); assert_eq!(count, 1); }
#[test]
fn test_array_iteration() {
let pool = apr::Pool::new();
let mut array = apr::tables::TypedArray::<i32>::new(&pool, 10);
array.push(10);
array.push(20);
array.push(30);
let mut collected = Vec::new();
let completed = array
.iter_items(|item| {
let value = unsafe { *(item as *const i32) };
collected.push(value);
Ok(())
})
.unwrap();
assert!(completed);
assert_eq!(collected, vec![10, 20, 30]);
}
#[test]
fn test_array_iteration_break() {
let pool = apr::Pool::new();
let mut array = apr::tables::TypedArray::<i32>::new(&pool, 10);
array.push(10);
array.push(20);
array.push(30);
let mut collected = Vec::new();
let completed = array
.iter_items(|item| {
let value = unsafe { *(item as *const i32) };
collected.push(value);
if collected.len() >= 2 {
Err(break_iteration())
} else {
Ok(())
}
})
.unwrap();
assert!(!completed); assert_eq!(collected, vec![10, 20]); }
}