paho-mqtt 0.12.0

The Official Eclipse Paho MQTT Rust Client Library. This is a wrapper around the Paho C library.
Documentation
// string_collection.rs
//
// This file is part of the Eclipse Paho MQTT Rust Client library.
//
// A string_collection is a helper to bridge between a collection of
// strings in Rust to an array of NUL terminated char string pointers
// that  the C library expects.
//
// It is useful when a C API takes a `const char *arg[]` parameter.
//

/*******************************************************************************
 * Copyright (c) 2017-2020 Frank Pagliughi <fpagliughi@mindspring.com>
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Eclipse Distribution License v1.0 which accompany this distribution.
 *
 * The Eclipse Public License is available at
 *    http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 *   http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *    Frank Pagliughi - initial implementation and documentation
 *******************************************************************************/

use std::{ffi::CString, os::raw::c_char, pin::Pin};

/// A collection of C-compatible (NUL-terminated) strings that is useful
/// with C API's that require an array of strings, normally specified as:
/// `const char* arr[]` or  `const char** arr`
#[derive(Debug)]
pub struct StringCollection {
    /// A vector cache of pointers into the data `coll`
    /// This must be updated any time the data is modified.
    c_coll: Vec<*const c_char>,
    /// A vector cache of mut pointers into the data `coll`
    /// This must be updated any time the data is modified.
    c_mut_coll: Vec<*mut c_char>,
    /// The pinned data cache
    data: Pin<Box<StringCollectionData>>,
}

#[derive(Debug, Default, Clone)]
struct StringCollectionData {
    /// The owned NUL-terminated strings
    coll: Vec<CString>,
}

impl StringCollection {
    /// Creates a StringCollection from a vector of string references.
    ///
    /// # Arguments
    ///
    /// `coll` A collection of string references.
    ///
    pub fn new<T>(coll: &[T]) -> Self
    where
        T: AsRef<str>,
    {
        let data = StringCollectionData {
            coll: Self::to_cstring(coll),
        };
        Self::from_data(data)
    }

    // Convert a collection of string references to a vector of CStrings.
    fn to_cstring<T>(coll: &[T]) -> Vec<CString>
    where
        T: AsRef<str>,
    {
        coll.iter()
            .map(|s| CString::new(s.as_ref()).unwrap())
            .collect()
    }

    // Convert a collection of CString's to a vector of C char pointers.
    //
    // Note that the pointers are invalidated if the original vector or
    // any of the strings in it change.
    fn to_c_vec(sv: &[CString]) -> Vec<*const c_char> {
        sv.iter().map(|cs| cs.as_ptr()).collect()
    }

    // Convert a collection of CString's to a vector of C char pointers.
    //
    // Note that the pointers are invalidated if the original vector or
    // any of the strings in it change.
    fn to_c_mut_vec(sv: &[CString]) -> Vec<*mut c_char> {
        sv.iter().map(|cs| cs.as_ptr() as *mut c_char).collect()
    }

    // Updates the cached vector to correspond to the string.
    fn from_data(data: StringCollectionData) -> Self {
        let data = Box::pin(data);
        let c_coll = Self::to_c_vec(&data.coll);
        let c_mut_coll = Self::to_c_mut_vec(&data.coll);
        Self {
            c_coll,
            c_mut_coll,
            data,
        }
    }

    /// Returns true if the collection contains elements.
    pub fn is_empty(&self) -> bool {
        self.data.coll.is_empty()
    }

    /// Gets the number of strings in the collection.
    pub fn len(&self) -> usize {
        self.data.coll.len()
    }

    /// Gets the collection as a pointer to const C string pointers.
    ///
    /// This returns a pointer that can be sent to a C API that takes a
    /// pointer to an array of char pointers, like `const char* arr[]`
    /// This function is inherently unsafe. The pointer it returns is only
    /// valid while the collection remains unmodified. In general, it
    /// should be requested when needed and not stored for future use.
    pub fn as_c_arr_ptr(&self) -> *const *const c_char {
        self.c_coll.as_ptr()
    }

    /// Gets the collection as a pointer to mutable C string pointers.
    ///
    /// This returns a pointer that can be sent to a C API that takes a
    /// pointer to an array of mutable char pointers, like `char* arr[]`
    /// This function is inherently unsafe. The pointer it returns is only
    /// valid while the collection remains unmodified. In general, it
    /// should be requested when needed and not stored for future use.
    ///
    /// This function is required due to the lax nature of the use of
    /// const strings in the C API. Hopefully the API will be fixed and
    /// this function can be removed
    pub fn as_c_arr_mut_ptr(&self) -> *const *mut c_char {
        self.c_mut_coll.as_ptr()
    }
}

impl Default for StringCollection {
    fn default() -> Self {
        Self::from_data(StringCollectionData::default())
    }
}

impl Clone for StringCollection {
    fn clone(&self) -> Self {
        Self::from_data((*self.data).clone())
    }
}

/////////////////////////////////////////////////////////////////////////////
//                              Unit Tests
/////////////////////////////////////////////////////////////////////////////

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

    macro_rules! vec_of_strings {
        ($($x:expr),*) => (vec![$($x.to_string()),*]);
    }

    #[test]
    fn test_default() {
        let sc = StringCollection::default();
        assert_eq!(0, sc.len());
    }

    #[test]
    fn test_new() {
        let v = ["string0", "string1", "string2"];
        let n = v.len();

        let sc = StringCollection::new(&v);

        assert_eq!(n, sc.len());
        assert_eq!(n, sc.c_coll.len());
        assert_eq!(n, sc.c_mut_coll.len());
        assert_eq!(n, sc.data.coll.len());

        assert_eq!(v[0].as_bytes(), sc.data.coll[0].as_bytes());
        assert_eq!(v[1].as_bytes(), sc.data.coll[1].as_bytes());
        assert_eq!(v[2].as_bytes(), sc.data.coll[2].as_bytes());

        assert_eq!(sc.data.coll[0].as_ptr(), sc.c_coll[0]);
        assert_eq!(sc.data.coll[1].as_ptr(), sc.c_coll[1]);
        assert_eq!(sc.data.coll[2].as_ptr(), sc.c_coll[2]);
    }

    #[test]
    fn test_new_from_vec_strings() {
        let v = vec_of_strings!["string0", "string1", "string2"];
        let n = v.len();

        let sc = StringCollection::new(&v);

        assert_eq!(n, sc.len());
        assert_eq!(n, sc.c_coll.len());
        assert_eq!(n, sc.c_mut_coll.len());
        assert_eq!(n, sc.data.coll.len());

        assert_eq!(v[0].as_bytes(), sc.data.coll[0].as_bytes());
        assert_eq!(v[1].as_bytes(), sc.data.coll[1].as_bytes());
        assert_eq!(v[2].as_bytes(), sc.data.coll[2].as_bytes());

        assert_eq!(sc.data.coll[0].as_ptr(), sc.c_coll[0]);
        assert_eq!(sc.data.coll[1].as_ptr(), sc.c_coll[1]);
        assert_eq!(sc.data.coll[2].as_ptr(), sc.c_coll[2]);
    }

    #[test]
    fn test_assign() {
        let v = ["string0", "string1", "string2"];
        let n = v.len();

        let org_sc = StringCollection::new(&v);

        let sc = org_sc;

        assert_eq!(n, sc.len());
        assert_eq!(n, sc.c_coll.len());
        assert_eq!(n, sc.c_mut_coll.len());
        assert_eq!(n, sc.data.coll.len());

        assert_eq!(v[0].as_bytes(), sc.data.coll[0].as_bytes());
        assert_eq!(v[1].as_bytes(), sc.data.coll[1].as_bytes());
        assert_eq!(v[2].as_bytes(), sc.data.coll[2].as_bytes());

        assert_eq!(sc.data.coll[0].as_ptr(), sc.c_coll[0]);
        assert_eq!(sc.data.coll[1].as_ptr(), sc.c_coll[1]);
        assert_eq!(sc.data.coll[2].as_ptr(), sc.c_coll[2]);
    }

    #[test]
    fn test_clone() {
        let v = ["string0", "string1", "string2"];
        let n = v.len();

        let sc = {
            let org_sc = StringCollection::new(&v);
            org_sc.clone()
        };

        assert_eq!(n, sc.len());
        assert_eq!(n, sc.c_coll.len());
        assert_eq!(n, sc.c_mut_coll.len());
        assert_eq!(n, sc.data.coll.len());

        assert_eq!(v[0].as_bytes(), sc.data.coll[0].as_bytes());
        assert_eq!(v[1].as_bytes(), sc.data.coll[1].as_bytes());
        assert_eq!(v[2].as_bytes(), sc.data.coll[2].as_bytes());

        assert_eq!(sc.data.coll[0].as_ptr(), sc.c_coll[0]);
        assert_eq!(sc.data.coll[1].as_ptr(), sc.c_coll[1]);
        assert_eq!(sc.data.coll[2].as_ptr(), sc.c_coll[2]);
    }
}