geos 8.1.0

Rust bindings for GEOS C API
Documentation
use crate::enums::{ByteOrder, OutputDimension};
use crate::error::{Error, GResult};
use geos_sys::*;
use libc::{c_char, c_void, strlen};
use std::convert::TryFrom;
use std::ffi::CStr;
use std::ops::Deref;
use std::slice;
use std::sync::Mutex;

macro_rules! set_callbacks {
    ($c_func:ident, $kind:ident, $callback_name:ident, $last:ident) => {
        fn $kind<'a>(ptr: GEOSContextHandle_t, nf: *mut InnerContext<'a>) {
            unsafe extern "C" fn message_handler_func<'a>(
                message: *const c_char,
                data: *mut c_void,
            ) {
                let inner_context: &InnerContext<'a> = &*(data as *mut _);

                if let Ok(callback) = inner_context.$callback_name.lock() {
                    let bytes = slice::from_raw_parts(message as *const u8, strlen(message));
                    let s = CStr::from_bytes_with_nul_unchecked(bytes);
                    let notif = s.to_str().expect("invalid CStr -> &str conversion");
                    callback(notif);
                    if let Ok(mut last) = inner_context.$last.lock() {
                        *last = Some(notif.to_owned());
                    }
                }
            }

            unsafe {
                $c_func(ptr, Some(message_handler_func), nf as *mut _);
            }
        }
    };
}

set_callbacks!(
    GEOSContext_setNoticeMessageHandler_r,
    set_notif,
    notif_callback,
    last_notification
);
set_callbacks!(
    GEOSContext_setErrorMessageHandler_r,
    set_error,
    error_callback,
    last_error
);

pub(crate) struct PtrWrap<T>(pub T);

impl<T> Deref for PtrWrap<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

unsafe impl<T> Send for PtrWrap<T> {}
unsafe impl<T> Sync for PtrWrap<T> {}

pub(crate) struct InnerContext<'a> {
    last_notification: Mutex<Option<String>>,
    last_error: Mutex<Option<String>>,
    notif_callback: Mutex<Box<dyn Fn(&str) + Send + Sync + 'a>>,
    error_callback: Mutex<Box<dyn Fn(&str) + Send + Sync + 'a>>,
}

pub struct ContextHandle<'a> {
    ptr: PtrWrap<GEOSContextHandle_t>,
    pub(crate) inner: PtrWrap<*mut InnerContext<'a>>,
}

impl<'a> ContextHandle<'a> {
    /// Creates a new `ContextHandle`.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::ContextHandle;
    ///
    /// let context_handle = ContextHandle::init().expect("invalid init");
    /// ```
    pub fn init() -> GResult<Self> {
        Self::init_e(None)
    }

    pub(crate) fn init_e(caller: Option<&str>) -> GResult<Self> {
        let ptr = unsafe { GEOS_init_r() };
        if ptr.is_null() {
            return if let Some(ref caller) = caller {
                Err(Error::GenericError(format!(
                    "GEOS_init_r failed from \"{}\"",
                    caller
                )))
            } else {
                Err(Error::GenericError("GEOS_init_r failed".to_owned()))
            };
        }
        let last_notification = Mutex::new(None);
        let last_error = Mutex::new(None);

        let notif_callback: Mutex<Box<dyn Fn(&str) + Send + Sync + 'a>> =
            Mutex::new(Box::new(|_| {}));
        let error_callback: Mutex<Box<dyn Fn(&str) + Send + Sync + 'a>> =
            Mutex::new(Box::new(|_| {}));

        let inner = Box::into_raw(Box::new(InnerContext {
            last_notification,
            last_error,
            notif_callback,
            error_callback,
        }));

        set_notif(ptr, inner);
        set_error(ptr, inner);

        Ok(ContextHandle {
            ptr: PtrWrap(ptr),
            inner: PtrWrap(inner),
        })
    }

    pub(crate) fn as_raw(&self) -> GEOSContextHandle_t {
        *self.ptr
    }

    pub(crate) fn get_inner(&self) -> &InnerContext<'a> {
        unsafe { &*self.inner.0 }
    }

    /// Allows to set a notice message handler.
    ///
    /// Passing [`None`] as parameter will unset this callback.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::ContextHandle;
    ///
    /// let context_handle = ContextHandle::init().expect("invalid init");
    ///
    /// context_handle.set_notice_message_handler(Some(Box::new(|s| println!("new message: {}", s))));
    /// ```
    pub fn set_notice_message_handler(&self, nf: Option<Box<dyn Fn(&str) + Send + Sync + 'a>>) {
        let inner_context = self.get_inner();
        if let Ok(mut callback) = inner_context.notif_callback.lock() {
            if let Some(nf) = nf {
                *callback = nf;
            } else {
                *callback = Box::new(|_| {});
            }
        }
    }

    /// Allows to set an error message handler.
    ///
    /// Passing [`None`] as parameter will unset this callback.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::ContextHandle;
    ///
    /// let context_handle = ContextHandle::init().expect("invalid init");
    ///
    /// context_handle.set_error_message_handler(Some(Box::new(|s| println!("new message: {}", s))));
    /// ```
    pub fn set_error_message_handler(&self, ef: Option<Box<dyn Fn(&str) + Send + Sync + 'a>>) {
        let inner_context = self.get_inner();
        if let Ok(mut callback) = inner_context.error_callback.lock() {
            if let Some(ef) = ef {
                *callback = ef;
            } else {
                *callback = Box::new(|_| {});
            }
        }
    }

    /// Returns the last error encountered.
    ///
    /// Please note that calling this function will remove the current last error!
    ///
    /// ```
    /// use geos::ContextHandle;
    ///
    /// let context_handle = ContextHandle::init().expect("invalid init");
    /// // make some functions calls...
    /// if let Some(last_error) = context_handle.get_last_error() {
    ///     println!("We have an error: {}", last_error);
    /// } else {
    ///     println!("No error occurred!");
    /// }
    /// ```
    pub fn get_last_error(&self) -> Option<String> {
        let inner_context = self.get_inner();
        if let Ok(mut last) = inner_context.last_error.lock() {
            last.take()
        } else {
            None
        }
    }

    /// Returns the last notification encountered.
    ///
    /// Please note that calling this function will remove the current last notification!
    ///
    /// ```
    /// use geos::ContextHandle;
    ///
    /// let context_handle = ContextHandle::init().expect("invalid init");
    /// // make some functions calls...
    /// if let Some(last_notif) = context_handle.get_last_notification() {
    ///     println!("We have a notification: {}", last_notif);
    /// } else {
    ///     println!("No notifications!");
    /// }
    /// ```
    pub fn get_last_notification(&self) -> Option<String> {
        let inner_context = self.get_inner();
        if let Ok(mut last) = inner_context.last_notification.lock() {
            last.take()
        } else {
            None
        }
    }

    /// Gets WKB output dimensions.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{ContextHandle, OutputDimension};
    ///
    /// let mut context_handle = ContextHandle::init().expect("invalid init");
    ///
    /// context_handle.set_wkb_output_dimensions(OutputDimension::TwoD);
    /// assert_eq!(context_handle.get_wkb_output_dimensions(), Ok(OutputDimension::TwoD));
    /// ```
    pub fn get_wkb_output_dimensions(&self) -> GResult<OutputDimension> {
        unsafe {
            let out = GEOS_getWKBOutputDims_r(self.as_raw());
            OutputDimension::try_from(out).map_err(|e| Error::GenericError(e.to_owned()))
        }
    }

    /// Sets WKB output dimensions.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{ContextHandle, OutputDimension};
    ///
    /// let mut context_handle = ContextHandle::init().expect("invalid init");
    ///
    /// context_handle.set_wkb_output_dimensions(OutputDimension::TwoD);
    /// assert_eq!(context_handle.get_wkb_output_dimensions(), Ok(OutputDimension::TwoD));
    /// ```
    pub fn set_wkb_output_dimensions(
        &mut self,
        dimensions: OutputDimension,
    ) -> GResult<OutputDimension> {
        unsafe {
            let out = GEOS_setWKBOutputDims_r(self.as_raw(), dimensions.into());
            OutputDimension::try_from(out).map_err(|e| Error::GenericError(e.to_owned()))
        }
    }

    /// Gets WKB byte order.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{ContextHandle, ByteOrder};
    ///
    /// let mut context_handle = ContextHandle::init().expect("invalid init");
    ///
    /// context_handle.set_wkb_byte_order(ByteOrder::LittleEndian);
    /// assert!(context_handle.get_wkb_byte_order() == ByteOrder::LittleEndian);
    /// ```
    pub fn get_wkb_byte_order(&self) -> ByteOrder {
        ByteOrder::try_from(unsafe { GEOS_getWKBByteOrder_r(self.as_raw()) })
            .expect("failed to convert to ByteOrder")
    }

    /// Sets WKB byte order.
    ///
    /// # Example
    ///
    /// ```
    /// use geos::{ContextHandle, ByteOrder};
    ///
    /// let mut context_handle = ContextHandle::init().expect("invalid init");
    ///
    /// context_handle.set_wkb_byte_order(ByteOrder::LittleEndian);
    /// assert!(context_handle.get_wkb_byte_order() == ByteOrder::LittleEndian);
    /// ```
    pub fn set_wkb_byte_order(&mut self, byte_order: ByteOrder) -> ByteOrder {
        ByteOrder::try_from(unsafe { GEOS_setWKBByteOrder_r(self.as_raw(), byte_order.into()) })
            .expect("failed to convert to ByteOrder")
    }
}

impl<'a> Drop for ContextHandle<'a> {
    fn drop(&mut self) {
        unsafe {
            if !self.ptr.is_null() {
                GEOS_finish_r(self.as_raw());
            }
            // Now we just have to clear stuff!
            let _inner: Box<InnerContext<'a>> = Box::from_raw(self.inner.0);
        }
    }
}