blob: 66a25b3f7f4e4af9d1283348269bbccde927cb64 [file] [log] [blame]
// 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: 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)]
/// Implements `uloc_openKeywords`.
// This should be in the `uloc` crate, but this is not possible because of the raw enum
// initialization. Tested in `uloc`.
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 = 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);
}
}