rust_icu_uenum 5.7.0

Native bindings to the ICU4C library from Unicode. Implements `uenum.h` from the ICU4C API.
Documentation
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! # Rust implementation of the `uenum.h` C API header for ICU.

use {
    rust_icu_common as common, rust_icu_sys as sys,
    rust_icu_sys::*,
    std::{convert::TryFrom, ffi, str},
};

/// Rust wrapper for the UEnumeration iterator.
///
/// Implements `UEnumeration`
#[derive(Debug)]
pub struct Enumeration {
    // The raw underlying character array, in case the underlying char array is
    // owned by this enumeration.
    _raw: Option<common::CStringVec>,

    // Internal low-level representation of the enumeration.  The internal
    // representation relies on `raw` and `len` above and must live at most as
    // long.
    rep: *mut sys::UEnumeration,
}

impl Enumeration {
    /// Internal representation, for ICU4C methods that require it.
    pub fn repr(&mut self) -> *mut sys::UEnumeration {
        self.rep
    }

    /// Creates an empty `Enumeration`.
    pub fn empty() -> Self {
        Enumeration::try_from(&vec![][..]).unwrap()
    }
}

impl Default for Enumeration {
    fn default() -> Self {
        Self::empty()
    }
}

/// Creates an enumeration iterator from a vector of UTF-8 strings.
impl TryFrom<&[&str]> for Enumeration {
    type Error = common::Error;

    /// Constructs an enumeration from a string slice.
    ///
    /// Implements `uenum_openCharStringsEnumeration`
    fn try_from(v: &[&str]) -> Result<Enumeration, common::Error> {
        let raw = common::CStringVec::new(v)?;
        let mut status = common::Error::OK_CODE;
        let rep: *mut sys::UEnumeration = unsafe {
            versioned_function!(uenum_openCharStringsEnumeration)(
                raw.as_c_array(),
                raw.len() as i32,
                &mut status,
            )
        };
        common::Error::ok_or_warning(status)?;
        // rep should not be null without an error set, but:
        // https://unicode-org.atlassian.net/browse/ICU-20918
        assert!(!rep.is_null());
        Ok(Enumeration {
            rep,
            _raw: Some(raw),
        })
    }
}

impl Drop for Enumeration {
    /// Drops the Enumeration, deallocating its internal representation hopefully correctly.
    ///
    /// Implements `uenum_close`
    fn drop(&mut self) {
        unsafe { versioned_function!(uenum_close)(self.rep) };
    }
}

impl Iterator for Enumeration {
    type Item = Result<String, common::Error>;

    /// Yields the next element stored in the enumeration.
    ///
    /// Implements `uenum_next`
    fn next(&mut self) -> Option<Self::Item> {
        let mut len: i32 = 0;
        let mut status = common::Error::OK_CODE;
        // Requires that self.rep is a valid pointer to a sys::UEnumeration.
        assert!(!self.rep.is_null());
        let raw = unsafe { versioned_function!(uenum_next)(self.rep, &mut len, &mut status) };
        if raw.is_null() {
            // No more elements to iterate over.
            return None;
        }
        let result = common::Error::ok_or_warning(status);
        match result {
            Ok(()) => {
                assert!(!raw.is_null());
                // Requires that raw is a valid pointer to a C string.
                let cstring = unsafe { ffi::CStr::from_ptr(raw) }; // Borrowing
                Some(Ok(cstring
                    .to_str()
                    .expect("could not convert to string")
                    .to_string()))
            }
            Err(e) => Some(Err(e)),
        }
    }
}

impl Enumeration {
    /// Constructs an [Enumeration] from a raw pointer.
    ///
    /// **DO NOT USE THIS FUNCTION UNLESS THERE IS NO OTHER CHOICE!**
    ///
    /// We tried to keep this function hidden to avoid the
    /// need to have a such a powerful unsafe function in the
    /// public API.  It worked up to a point for free functions.
    ///
    /// It no longer works on high-level methods that return
    /// enumerations, since then we'd need to depend on them to
    /// create an [Enumeration].
    #[doc(hidden)]
    pub unsafe fn from_raw_parts(
        _raw: Option<common::CStringVec>,
        rep: *mut sys::UEnumeration,
    ) -> Enumeration {
        Enumeration { _raw, rep }
    }
}

#[doc(hidden)]
/// Implements `ucal_openCountryTimeZones`.
// This should be in the `ucal` crate, but not possible because of the raw enum initialization.
// Tested in `ucal`.
pub fn ucal_open_country_time_zones(country: &str) -> Result<Enumeration, common::Error> {
    let mut status = common::Error::OK_CODE;
    let asciiz_country = ffi::CString::new(country)?;
    // Requires that the asciiz country be a pointer to a valid C string.
    let raw_enum = unsafe {
        assert!(common::Error::is_ok(status));
        versioned_function!(ucal_openCountryTimeZones)(asciiz_country.as_ptr(), &mut status)
    };
    common::Error::ok_or_warning(status)?;
    Ok(Enumeration {
        _raw: None,
        rep: raw_enum,
    })
}

#[doc(hidden)]
/// Implements `ucal_openTimeZoneIDEnumeration`
// This should be in the `ucal` crate, but not possible because of the raw enum initialization.
// Tested in `ucal`.
pub fn ucal_open_time_zone_id_enumeration(
    zone_type: sys::USystemTimeZoneType,
    region: Option<&str>,
    raw_offset: Option<i32>,
) -> Result<Enumeration, common::Error> {
    let mut status = common::Error::OK_CODE;
    let asciiz_region = match region {
        None => None,
        Some(region) => Some(ffi::CString::new(region)?),
    };
    let mut repr_raw_offset: i32 = raw_offset.unwrap_or_default();

    // asciiz_region should be a valid asciiz pointer. raw_offset is an encoding
    // of an optional value by a C pointer.
    let raw_enum = unsafe {
        assert!(common::Error::is_ok(status));
        versioned_function!(ucal_openTimeZoneIDEnumeration)(
            zone_type,
            // Note that for the string pointer to remain valid, we must borrow the CString from
            // asciiz_region, not move the CString out.
            match &asciiz_region {
                Some(asciiz_region) => asciiz_region.as_ptr(),
                None => std::ptr::null(),
            },
            match raw_offset {
                Some(_) => &mut repr_raw_offset,
                None => std::ptr::null_mut(),
            },
            &mut status,
        )
    };
    common::Error::ok_or_warning(status)?;
    Ok(Enumeration {
        _raw: None,
        rep: raw_enum,
    })
}

#[doc(hidden)]
/// Opens a list of available time zones.
///
/// Implements `ucal_openTimeZones`
// This should be in the `ucal` crate, but not possible because of the raw enum initialization.
// Tested in `ucal`.
pub fn open_time_zones() -> Result<Enumeration, common::Error> {
    let mut status = common::Error::OK_CODE;
    let raw_enum = unsafe {
        assert!(common::Error::is_ok(status));
        versioned_function!(ucal_openTimeZones)(&mut status)
    };
    common::Error::ok_or_warning(status)?;
    Ok(Enumeration {
        _raw: None,
        rep: raw_enum,
    })
}

#[doc(hidden)]
// This has been moved to the `uloc` crate
#[deprecated(since="4.2.4", note="please use `ULoc::open_keywords` instead")]
pub fn uloc_open_keywords(locale: &str) -> Result<Enumeration, common::Error> {
    let mut status = common::Error::OK_CODE;
    let asciiz_locale = ffi::CString::new(locale)?;
    let raw_enum = unsafe {
        assert!(common::Error::is_ok(status));
        versioned_function!(uloc_openKeywords)(asciiz_locale.as_ptr(), &mut status)
    };
    common::Error::ok_or_warning(status)?;
    // "No error but null" means that there are no keywords
    if raw_enum.is_null() {
        Ok(Enumeration::empty())
    } else {
        Ok(Enumeration {
            _raw: None,
            rep: raw_enum,
        })
    }
}

#[cfg(test)]
mod tests {
    use {super::*, std::convert::TryFrom};

    #[test]
    fn iter() {
        let e = Enumeration::try_from(&vec!["hello", "world", "💖"][..]).expect("enumeration?");
        let mut count = 0;
        let mut results = vec![];
        for result in e {
            let elem = result.expect("no error");
            count += 1;
            results.push(elem);
        }
        assert_eq!(count, 3, "results: {:?}", results);
        assert_eq!(
            results,
            vec!["hello", "world", "💖"],
            "results: {:?}",
            results
        );
    }

    #[test]
    fn error() {
        // A mutilated sparkle heart from https://doc.rust-lang.org/std/str/fn.from_utf8_unchecked.html
        let destroyed_sparkle_heart = vec![0, 159, 164, 150];
        let invalid_utf8 = unsafe { str::from_utf8_unchecked(&destroyed_sparkle_heart) };
        let e = Enumeration::try_from(&vec!["hello", "world", "💖", invalid_utf8][..]);
        assert!(e.is_err(), "was: {:?}", e);
    }

    #[test]
    fn test_uloc_open_keywords() -> Result<(), common::Error> {
        let loc = "az-Cyrl-AZ-u-ca-hebrew-fw-sunday-nu-deva-tz-usnyc";
        #[allow(deprecated)]
        let keywords: Vec<String> = uloc_open_keywords(loc).unwrap().map(|result| result.unwrap()).collect();
        assert_eq!(
            keywords,
            vec![
                "calendar".to_string(),
                "fw".to_string(),
                "numbers".to_string(),
                "timezone".to_string()
            ]
        );
        Ok(())
    }
}