rust_icu_uenum/
lib.rs

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.
14
15//! # Rust implementation of the `uenum.h` C API header for ICU.
16
17use {
18    rust_icu_common as common, rust_icu_sys as sys,
19    rust_icu_sys::*,
20    std::{convert::TryFrom, ffi, str},
21};
22
23/// 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>,
31
32    // 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.
35    rep: *mut sys::UEnumeration,
36}
37
38impl Enumeration {
39    /// Internal representation, for ICU4C methods that require it.
40    pub fn repr(&mut self) -> *mut sys::UEnumeration {
41        self.rep
42    }
43
44    /// Creates an empty `Enumeration`.
45    pub fn empty() -> Self {
46        Enumeration::try_from(&vec![][..]).unwrap()
47    }
48}
49
50impl Default for Enumeration {
51    fn default() -> Self {
52        Self::empty()
53    }
54}
55
56/// Creates an enumeration iterator from a vector of UTF-8 strings.
57impl TryFrom<&[&str]> for Enumeration {
58    type Error = common::Error;
59
60    /// Constructs an enumeration from a string slice.
61    ///
62    /// Implements `uenum_openCharStringsEnumeration`
63    fn try_from(v: &[&str]) -> Result<Enumeration, common::Error> {
64        let raw = common::CStringVec::new(v)?;
65        let mut status = common::Error::OK_CODE;
66        let rep: *mut sys::UEnumeration = unsafe {
67            versioned_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
76        assert!(!rep.is_null());
77        Ok(Enumeration {
78            rep,
79            _raw: Some(raw),
80        })
81    }
82}
83
84impl Drop for Enumeration {
85    /// Drops the Enumeration, deallocating its internal representation hopefully correctly.
86    ///
87    /// Implements `uenum_close`
88    fn drop(&mut self) {
89        unsafe { versioned_function!(uenum_close)(self.rep) };
90    }
91}
92
93impl Iterator for Enumeration {
94    type Item = Result<String, common::Error>;
95
96    /// Yields the next element stored in the enumeration.
97    ///
98    /// Implements `uenum_next`
99    fn next(&mut self) -> Option<Self::Item> {
100        let mut len: i32 = 0;
101        let mut status = common::Error::OK_CODE;
102        // Requires that self.rep is a valid pointer to a sys::UEnumeration.
103        assert!(!self.rep.is_null());
104        let raw = unsafe { versioned_function!(uenum_next)(self.rep, &mut len, &mut status) };
105        if raw.is_null() {
106            // No more elements to iterate over.
107            return None;
108        }
109        let result = common::Error::ok_or_warning(status);
110        match result {
111            Ok(()) => {
112                assert!(!raw.is_null());
113                // Requires that raw is a valid pointer to a C string.
114                let cstring = unsafe { ffi::CStr::from_ptr(raw) }; // Borrowing
115                Some(Ok(cstring
116                    .to_str()
117                    .expect("could not convert to string")
118                    .to_string()))
119            }
120            Err(e) => Some(Err(e)),
121        }
122    }
123}
124
125impl 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)]
138    pub unsafe fn from_raw_parts(
139        _raw: Option<common::CStringVec>,
140        rep: *mut sys::UEnumeration,
141    ) -> Enumeration {
142        Enumeration { _raw, rep }
143    }
144}
145
146#[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> {
151    let mut status = common::Error::OK_CODE;
152    let asciiz_country = ffi::CString::new(country)?;
153    // Requires that the asciiz country be a pointer to a valid C string.
154    let raw_enum = unsafe {
155        assert!(common::Error::is_ok(status));
156        versioned_function!(ucal_openCountryTimeZones)(asciiz_country.as_ptr(), &mut status)
157    };
158    common::Error::ok_or_warning(status)?;
159    Ok(Enumeration {
160        _raw: None,
161        rep: raw_enum,
162    })
163}
164
165#[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> {
174    let mut status = common::Error::OK_CODE;
175    let asciiz_region = match region {
176        None => None,
177        Some(region) => Some(ffi::CString::new(region)?),
178    };
179    let mut repr_raw_offset: i32 = raw_offset.unwrap_or_default();
180
181    // asciiz_region should be a valid asciiz pointer. raw_offset is an encoding
182    // of an optional value by a C pointer.
183    let raw_enum = unsafe {
184        assert!(common::Error::is_ok(status));
185        versioned_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.
189            match &asciiz_region {
190                Some(asciiz_region) => asciiz_region.as_ptr(),
191                None => std::ptr::null(),
192            },
193            match raw_offset {
194                Some(_) => &mut repr_raw_offset,
195                None => std::ptr::null_mut(),
196            },
197            &mut status,
198        )
199    };
200    common::Error::ok_or_warning(status)?;
201    Ok(Enumeration {
202        _raw: None,
203        rep: raw_enum,
204    })
205}
206
207#[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> {
214    let mut status = common::Error::OK_CODE;
215    let raw_enum = unsafe {
216        assert!(common::Error::is_ok(status));
217        versioned_function!(ucal_openTimeZones)(&mut status)
218    };
219    common::Error::ok_or_warning(status)?;
220    Ok(Enumeration {
221        _raw: None,
222        rep: raw_enum,
223    })
224}
225
226#[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> {
230    let mut status = common::Error::OK_CODE;
231    let asciiz_locale = ffi::CString::new(locale)?;
232    let raw_enum = unsafe {
233        assert!(common::Error::is_ok(status));
234        versioned_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
238    if raw_enum.is_null() {
239        Ok(Enumeration::empty())
240    } else {
241        Ok(Enumeration {
242            _raw: None,
243            rep: raw_enum,
244        })
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use {super::*, std::convert::TryFrom};
251
252    #[test]
253    fn iter() {
254        let e = Enumeration::try_from(&vec!["hello", "world", "💖"][..]).expect("enumeration?");
255        let mut count = 0;
256        let mut results = vec![];
257        for result in e {
258            let elem = result.expect("no error");
259            count += 1;
260            results.push(elem);
261        }
262        assert_eq!(count, 3, "results: {:?}", results);
263        assert_eq!(
264            results,
265            vec!["hello", "world", "💖"],
266            "results: {:?}",
267            results
268        );
269    }
270
271    #[test]
272    fn error() {
273        // A mutilated sparkle heart from https://doc.rust-lang.org/std/str/fn.from_utf8_unchecked.html
274        let destroyed_sparkle_heart = vec![0, 159, 164, 150];
275        let invalid_utf8 = unsafe { str::from_utf8_unchecked(&destroyed_sparkle_heart) };
276        let e = Enumeration::try_from(&vec!["hello", "world", "💖", invalid_utf8][..]);
277        assert!(e.is_err(), "was: {:?}", e);
278    }
279
280    #[test]
281    fn test_uloc_open_keywords() -> Result<(), common::Error> {
282        let loc = "az-Cyrl-AZ-u-ca-hebrew-fw-sunday-nu-deva-tz-usnyc";
283        #[allow(deprecated)]
284        let keywords: Vec<String> = uloc_open_keywords(loc).unwrap().map(|result| result.unwrap()).collect();
285        assert_eq!(
286            keywords,
287            vec![
288                "calendar".to_string(),
289                "fw".to_string(),
290                "numbers".to_string(),
291                "timezone".to_string()
292            ]
293        );
294        Ok(())
295    }
296}