1// Copyright 2019 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
1415//! # Rust implementation of the `uenum.h` C API header for ICU.
1617use {
18 rust_icu_common as common, rust_icu_sys as sys,
19 rust_icu_sys::*,
20 std::{convert::TryFrom, ffi, str},
21};
2223/// Rust wrapper for the UEnumeration iterator.
24///
25/// Implements `UEnumeration`
26#[derive(Debug)]
27pub struct Enumeration {
28// The raw underlying character array, in case the underlying char array is
29 // owned by this enumeration.
30_raw: Option<common::CStringVec>,
3132// Internal low-level representation of the enumeration. The internal
33 // representation relies on `raw` and `len` above and must live at most as
34 // long.
35rep: *mut sys::UEnumeration,
36}
3738impl Enumeration {
39/// Internal representation, for ICU4C methods that require it.
40pub fn repr(&mut self) -> *mut sys::UEnumeration {
41self.rep
42 }
4344/// Creates an empty `Enumeration`.
45pub fn empty() -> Self {
46 Enumeration::try_from(&vec![][..]).unwrap()
47 }
48}
4950impl Default for Enumeration {
51fn default() -> Self {
52Self::empty()
53 }
54}
5556/// Creates an enumeration iterator from a vector of UTF-8 strings.
57impl TryFrom<&[&str]> for Enumeration {
58type Error = common::Error;
5960/// Constructs an enumeration from a string slice.
61 ///
62 /// Implements `uenum_openCharStringsEnumeration`
63fn try_from(v: &[&str]) -> Result<Enumeration, common::Error> {
64let raw = common::CStringVec::new(v)?;
65let mut status = common::Error::OK_CODE;
66let rep: *mut sys::UEnumeration = unsafe {
67versioned_function!(uenum_openCharStringsEnumeration)(
68 raw.as_c_array(),
69 raw.len() as i32,
70&mut status,
71 )
72 };
73 common::Error::ok_or_warning(status)?;
74// rep should not be null without an error set, but:
75 // https://unicode-org.atlassian.net/browse/ICU-20918
76assert!(!rep.is_null());
77Ok(Enumeration {
78 rep,
79 _raw: Some(raw),
80 })
81 }
82}
8384impl Drop for Enumeration {
85/// Drops the Enumeration, deallocating its internal representation hopefully correctly.
86 ///
87 /// Implements `uenum_close`
88fn drop(&mut self) {
89unsafe { versioned_function!(uenum_close)(self.rep) };
90 }
91}
9293impl Iterator for Enumeration {
94type Item = Result<String, common::Error>;
9596/// Yields the next element stored in the enumeration.
97 ///
98 /// Implements `uenum_next`
99fn next(&mut self) -> Option<Self::Item> {
100let mut len: i32 = 0;
101let mut status = common::Error::OK_CODE;
102// Requires that self.rep is a valid pointer to a sys::UEnumeration.
103assert!(!self.rep.is_null());
104let raw = unsafe { versioned_function!(uenum_next)(self.rep, &mut len, &mut status) };
105if raw.is_null() {
106// No more elements to iterate over.
107return None;
108 }
109let result = common::Error::ok_or_warning(status);
110match result {
111Ok(()) => {
112assert!(!raw.is_null());
113// Requires that raw is a valid pointer to a C string.
114let cstring = unsafe { ffi::CStr::from_ptr(raw) }; // Borrowing
115Some(Ok(cstring
116 .to_str()
117 .expect("could not convert to string")
118 .to_string()))
119 }
120Err(e) => Some(Err(e)),
121 }
122 }
123}
124125impl Enumeration {
126/// Constructs an [Enumeration] from a raw pointer.
127 ///
128 /// **DO NOT USE THIS FUNCTION UNLESS THERE IS NO OTHER CHOICE!**
129 ///
130 /// We tried to keep this function hidden to avoid the
131 /// need to have a such a powerful unsafe function in the
132 /// public API. It worked up to a point for free functions.
133 ///
134 /// It no longer works on high-level methods that return
135 /// enumerations, since then we'd need to depend on them to
136 /// create an [Enumeration].
137#[doc(hidden)]
138pub unsafe fn from_raw_parts(
139 _raw: Option<common::CStringVec>,
140 rep: *mut sys::UEnumeration,
141 ) -> Enumeration {
142 Enumeration { _raw, rep }
143 }
144}
145146#[doc(hidden)]
147/// Implements `ucal_openCountryTimeZones`.
148// This should be in the `ucal` crate, but not possible because of the raw enum initialization.
149// Tested in `ucal`.
150pub fn ucal_open_country_time_zones(country: &str) -> Result<Enumeration, common::Error> {
151let mut status = common::Error::OK_CODE;
152let asciiz_country = ffi::CString::new(country)?;
153// Requires that the asciiz country be a pointer to a valid C string.
154let raw_enum = unsafe {
155assert!(common::Error::is_ok(status));
156versioned_function!(ucal_openCountryTimeZones)(asciiz_country.as_ptr(), &mut status)
157 };
158 common::Error::ok_or_warning(status)?;
159Ok(Enumeration {
160 _raw: None,
161 rep: raw_enum,
162 })
163}
164165#[doc(hidden)]
166/// Implements `ucal_openTimeZoneIDEnumeration`
167// This should be in the `ucal` crate, but not possible because of the raw enum initialization.
168// Tested in `ucal`.
169pub fn ucal_open_time_zone_id_enumeration(
170 zone_type: sys::USystemTimeZoneType,
171 region: Option<&str>,
172 raw_offset: Option<i32>,
173) -> Result<Enumeration, common::Error> {
174let mut status = common::Error::OK_CODE;
175let asciiz_region = match region {
176None => None,
177Some(region) => Some(ffi::CString::new(region)?),
178 };
179let mut repr_raw_offset: i32 = raw_offset.unwrap_or_default();
180181// asciiz_region should be a valid asciiz pointer. raw_offset is an encoding
182 // of an optional value by a C pointer.
183let raw_enum = unsafe {
184assert!(common::Error::is_ok(status));
185versioned_function!(ucal_openTimeZoneIDEnumeration)(
186 zone_type,
187// Note that for the string pointer to remain valid, we must borrow the CString from
188 // asciiz_region, not move the CString out.
189match &asciiz_region {
190Some(asciiz_region) => asciiz_region.as_ptr(),
191None => std::ptr::null(),
192 },
193match raw_offset {
194Some(_) => &mut repr_raw_offset,
195None => std::ptr::null_mut(),
196 },
197&mut status,
198 )
199 };
200 common::Error::ok_or_warning(status)?;
201Ok(Enumeration {
202 _raw: None,
203 rep: raw_enum,
204 })
205}
206207#[doc(hidden)]
208/// Opens a list of available time zones.
209///
210/// Implements `ucal_openTimeZones`
211// This should be in the `ucal` crate, but not possible because of the raw enum initialization.
212// Tested in `ucal`.
213pub fn open_time_zones() -> Result<Enumeration, common::Error> {
214let mut status = common::Error::OK_CODE;
215let raw_enum = unsafe {
216assert!(common::Error::is_ok(status));
217versioned_function!(ucal_openTimeZones)(&mut status)
218 };
219 common::Error::ok_or_warning(status)?;
220Ok(Enumeration {
221 _raw: None,
222 rep: raw_enum,
223 })
224}
225226#[doc(hidden)]
227// This has been moved to the `uloc` crate
228#[deprecated(since="4.2.4", note="please use `ULoc::open_keywords` instead")]
229pub fn uloc_open_keywords(locale: &str) -> Result<Enumeration, common::Error> {
230let mut status = common::Error::OK_CODE;
231let asciiz_locale = ffi::CString::new(locale)?;
232let raw_enum = unsafe {
233assert!(common::Error::is_ok(status));
234versioned_function!(uloc_openKeywords)(asciiz_locale.as_ptr(), &mut status)
235 };
236 common::Error::ok_or_warning(status)?;
237// "No error but null" means that there are no keywords
238if raw_enum.is_null() {
239Ok(Enumeration::empty())
240 } else {
241Ok(Enumeration {
242 _raw: None,
243 rep: raw_enum,
244 })
245 }
246}
247248#[cfg(test)]
249mod tests {
250use {super::*, std::convert::TryFrom};
251252#[test]
253fn iter() {
254let e = Enumeration::try_from(&vec!["hello", "world", "💖"][..]).expect("enumeration?");
255let mut count = 0;
256let mut results = vec![];
257for result in e {
258let elem = result.expect("no error");
259 count += 1;
260 results.push(elem);
261 }
262assert_eq!(count, 3, "results: {:?}", results);
263assert_eq!(
264 results,
265vec!["hello", "world", "💖"],
266"results: {:?}",
267 results
268 );
269 }
270271#[test]
272fn error() {
273// A mutilated sparkle heart from https://doc.rust-lang.org/std/str/fn.from_utf8_unchecked.html
274let destroyed_sparkle_heart = vec![0, 159, 164, 150];
275let invalid_utf8 = unsafe { str::from_utf8_unchecked(&destroyed_sparkle_heart) };
276let e = Enumeration::try_from(&vec!["hello", "world", "💖", invalid_utf8][..]);
277assert!(e.is_err(), "was: {:?}", e);
278 }
279280#[test]
281fn test_uloc_open_keywords() -> Result<(), common::Error> {
282let loc = "az-Cyrl-AZ-u-ca-hebrew-fw-sunday-nu-deva-tz-usnyc";
283#[allow(deprecated)]
284let keywords: Vec<String> = uloc_open_keywords(loc).unwrap().map(|result| result.unwrap()).collect();
285assert_eq!(
286 keywords,
287vec![
288"calendar".to_string(),
289"fw".to_string(),
290"numbers".to_string(),
291"timezone".to_string()
292 ]
293 );
294Ok(())
295 }
296}