librados 0.2.0

Idiomatic (async) rust bindings for librados
Documentation
use std::{ffi::CString, pin::Pin};

use crate::{
    IoCtx, RadosCompletion, RadosError, Result,
    error::maybe_err,
    librados::{
        rados_aio_read_op_operate, rados_create_read_op, rados_read_op_operate, rados_read_op_t,
        rados_release_read_op,
    },
};

struct ReadOpPtr(rados_read_op_t);

impl ReadOpPtr {
    fn new() -> Result<Self> {
        let read_op = unsafe { rados_create_read_op() };

        (!read_op.is_null())
            .then_some(Self(read_op))
            .ok_or(RadosError::Unknown(-1))
    }

    fn get(&self) -> rados_read_op_t {
        self.0
    }
}

impl Drop for ReadOpPtr {
    fn drop(&mut self) {
        unsafe { rados_release_read_op(self.0) };
    }
}

pub(crate) struct ReadOpExecutor<'rados, 'ioctx, T>
where
    T: ReadOp,
{
    operation: T,
    inner: ReadOpPtr,
    ioctx: &'ioctx IoCtx<'rados>,
}

impl<'rados, 'ioctx, T> ReadOpExecutor<'rados, 'ioctx, T>
where
    T: ReadOp,
{
    pub fn new(ioctx: &'ioctx IoCtx<'rados>, operation: T) -> Result<Self> {
        Ok(Self {
            inner: ReadOpPtr::new()?,
            operation,
            ioctx,
        })
    }

    pub fn execute(self, object: &str) -> Result<T::Output> {
        let object = CString::new(object).expect("Object ID had interior NUL.");
        let mut output = T::OperationState::default();

        let pinned = unsafe { Pin::new_unchecked(&mut output) };

        self.operation
            .construct_in_place(self.inner.get(), pinned)?;

        let result = unsafe {
            rados_read_op_operate(self.inner.get(), self.ioctx.inner(), object.as_ptr(), 0)
        };

        maybe_err(result)?;

        T::complete(output)
    }

    pub async fn execute_async(self, object: &str) -> Result<T::Output>
    where
        T::OperationState: 'static + Unpin,
    {
        let object = CString::new(object).expect("Object ID had interior NUL");
        let state = T::OperationState::default();

        let completion = unsafe {
            RadosCompletion::new_with(false, (object, state), |completion, mut full_state| {
                let pinned = &mut full_state.1;
                let op_state = core::pin::Pin::new_unchecked(pinned);

                self.operation
                    .construct_in_place(self.inner.get(), op_state)?;

                maybe_err(rados_aio_read_op_operate(
                    self.inner.get(),
                    self.ioctx.inner(),
                    completion,
                    full_state.0.as_ptr(),
                    0,
                ))
            })?
        };

        let (_, (_, output)) = completion.wait_for().await?;

        T::complete(output)
    }
}

pub trait ReadOp
where
    Self: Sized,
{
    type OperationState: Default;
    type Output;

    fn construct_in_place(
        &self,
        read_op: rados_read_op_t,
        state: Pin<&mut Self::OperationState>,
    ) -> Result<()>;

    fn complete(state: Self::OperationState) -> Result<Self::Output>;
}