use {
anyhow::anyhow,
rust_icu_sys as sys,
std::{ffi, os},
thiserror::Error,
};
#[derive(Error, Debug)]
pub enum Error {
#[error("ICU error code: {}", _0)]
Sys(sys::UErrorCode),
#[error(transparent)]
Wrapper(#[from] anyhow::Error),
}
impl Error {
pub const OK_CODE: sys::UErrorCode = sys::UErrorCode::U_ZERO_ERROR;
pub fn is_ok(code: sys::UErrorCode) -> bool {
code == Self::OK_CODE
}
pub fn ok_or_warning(status: sys::UErrorCode) -> Result<(), Self> {
if Self::is_ok(status) || status < Self::OK_CODE {
Ok(())
} else {
Err(Error::Sys(status))
}
}
pub fn ok_preflight(status: sys::UErrorCode) -> Result<(), Self> {
if status > Self::OK_CODE && status != sys::UErrorCode::U_BUFFER_OVERFLOW_ERROR {
Err(Error::Sys(status))
} else {
Ok(())
}
}
pub fn is_code(&self, code: sys::UErrorCode) -> bool {
if let Error::Sys(c) = self {
return *c == code;
}
false
}
pub fn is_err(&self) -> bool {
match self {
Error::Sys(code) => *code > sys::UErrorCode::U_ZERO_ERROR,
Error::Wrapper(_) => true,
}
}
pub fn is_preflight_err(&self) -> bool {
self.is_err() && !self.is_code(sys::UErrorCode::U_BUFFER_OVERFLOW_ERROR)
}
pub fn is_warn(&self) -> bool {
match self {
Error::Sys(c) => *c < sys::UErrorCode::U_ZERO_ERROR,
_ => false,
}
}
pub fn wrapper(source: impl Into<anyhow::Error>) -> Self {
Self::Wrapper(source.into())
}
}
impl From<ffi::NulError> for Error {
fn from(e: ffi::NulError) -> Self {
Self::wrapper(e)
}
}
impl From<std::str::Utf8Error> for Error {
fn from(e: std::str::Utf8Error) -> Self {
Self::wrapper(e)
}
}
impl From<std::string::FromUtf8Error> for Error {
fn from(e: std::string::FromUtf8Error) -> Self {
Self::wrapper(e)
}
}
impl Into<std::fmt::Error> for Error {
fn into(self) -> std::fmt::Error {
eprintln!("error while formatting: {:?}", &self);
std::fmt::Error {}
}
}
#[macro_export]
macro_rules! simple_drop_impl {
($type_name:ty, $impl_function_name:ident) => {
impl Drop for $type_name {
fn drop(&mut self) {
unsafe {
versioned_function!($impl_function_name)(self.rep.as_ptr());
}
}
}
};
}
#[macro_export]
macro_rules! buffered_string_method_with_retry {
($method_name:ident, $buffer_capacity:expr,
[$($before_arg:ident: $before_arg_type:ty,)*],
[$($after_arg:ident: $after_arg_type:ty,)*]) => {
fn $method_name(
method_to_call: unsafe extern "C" fn(
$($before_arg_type,)*
*mut raw::c_char,
i32,
$($after_arg_type,)*
*mut sys::UErrorCode,
) -> i32,
$($before_arg: $before_arg_type,)*
$($after_arg: $after_arg_type,)*
) -> Result<String, common::Error> {
let mut status = common::Error::OK_CODE;
let mut buf: Vec<u8> = vec![0; $buffer_capacity];
let full_len: i32 = unsafe {
assert!(common::Error::is_ok(status));
method_to_call(
$($before_arg,)*
buf.as_mut_ptr() as *mut raw::c_char,
$buffer_capacity as i32,
$($after_arg,)*
&mut status,
)
};
if status == sys::UErrorCode::U_BUFFER_OVERFLOW_ERROR ||
(common::Error::is_ok(status) &&
full_len > $buffer_capacity
.try_into()
.map_err(|e| common::Error::wrapper(e))?) {
assert!(full_len > 0);
let full_len: usize = full_len
.try_into()
.map_err(|e| common::Error::wrapper(e))?;
buf.resize(full_len, 0);
unsafe {
assert!(common::Error::is_ok(status));
method_to_call(
$($before_arg,)*
buf.as_mut_ptr() as *mut raw::c_char,
full_len as i32,
$($after_arg,)*
&mut status,
)
};
}
common::Error::ok_or_warning(status)?;
if (full_len >= 0) {
let full_len: usize = full_len
.try_into()
.map_err(|e| common::Error::wrapper(e))?;
buf.resize(full_len, 0);
}
String::from_utf8(buf).map_err(|e| e.utf8_error().into())
}
}
}
#[macro_export]
macro_rules! format_ustring_for_type{
($method_name:ident, $function_name:ident, $type_decl:ty) => (
pub fn $method_name(&self, number: $type_decl) -> Result<String, common::Error> {
let result = paste::item! {
self. [< $method_name _ustring>] (number)?
};
String::try_from(&result)
}
paste::item! {
pub fn [<$method_name _ustring>] (&self, param: $type_decl) -> Result<ustring::UChar, common::Error> {
const CAPACITY: usize = 200;
buffered_uchar_method_with_retry!(
[< $method_name _ustring_impl >],
CAPACITY,
[ rep: *const sys::UNumberFormat, param: $type_decl, ],
[ field: *mut sys::UFieldPosition, ]
);
[<$method_name _ustring_impl>](
versioned_function!($function_name),
self.rep.as_ptr(),
param,
0 as *mut sys::UFieldPosition,
)
}
}
)
}
#[macro_export]
macro_rules! generalized_fallible_getter{
($top_level_method_name:ident, $impl_name:ident, [ $( $arg:ident: $arg_type:ty ,)* ], $ret_type:ty) => (
pub fn $top_level_method_name(&self, $( $arg: $arg_type, )* ) -> Result<$ret_type, common::Error> {
let mut status = common::Error::OK_CODE;
let result: $ret_type = unsafe {
assert!(common::Error::is_ok(status));
versioned_function!($impl_name)(self.rep.as_ptr(), $( $arg, )* &mut status)
};
common::Error::ok_or_warning(status)?;
Ok(result)
}
)
}
#[macro_export]
macro_rules! generalized_fallible_setter{
($top_level_method_name:ident, $impl_name:ident, [ $( $arg:ident : $arg_type:ty, )* ]) => (
generalized_fallible_getter!(
$top_level_method_name,
$impl_name,
[ $( $arg: $arg_type, )* ],
());
)
}
#[derive(Debug)]
pub struct CStringVec {
rep: Vec<ffi::CString>,
c_rep: Vec<*const os::raw::c_char>,
}
impl CStringVec {
pub fn new(strings: &[&str]) -> Result<Self, Error> {
let mut rep = Vec::with_capacity(strings.len());
for elem in strings {
let asciiz = ffi::CString::new(*elem)?;
rep.push(asciiz);
}
let c_rep = rep.iter().map(|e| e.as_ptr()).collect();
Ok(CStringVec { rep, c_rep })
}
pub fn as_c_array(&self) -> *const *const os::raw::c_char {
self.c_rep.as_ptr() as *const *const os::raw::c_char
}
pub fn len(&self) -> usize {
self.rep.len()
}
pub fn is_empty(&self) -> bool {
self.rep.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_code() {
let error = Error::ok_or_warning(sys::UErrorCode::U_BUFFER_OVERFLOW_ERROR)
.err()
.unwrap();
assert!(error.is_code(sys::UErrorCode::U_BUFFER_OVERFLOW_ERROR));
assert!(!error.is_preflight_err());
assert!(!error.is_code(sys::UErrorCode::U_ZERO_ERROR));
}
#[test]
fn test_into_char_array() {
let values = vec!["eenie", "meenie", "minie", "moe"];
let c_array = CStringVec::new(&values).expect("success");
assert_eq!(c_array.len(), 4);
}
#[test]
fn test_with_embedded_nul_byte() {
let values = vec!["hell\0x00o"];
let _c_array = CStringVec::new(&values).expect_err("should fail");
}
#[test]
fn test_parser_error_ok() {
let tests = vec![
sys::UParseError {
line: 0,
offset: 0,
preContext: [0; 16usize],
postContext: [0; 16usize],
},
sys::UParseError {
line: -1,
offset: 0,
preContext: [0; 16usize],
postContext: [0; 16usize],
},
sys::UParseError {
line: 0,
offset: -1,
preContext: [0; 16usize],
postContext: [0; 16usize],
},
];
for test in tests {
assert!(parse_ok(test).is_ok(), "for test: {:?}", test.clone());
}
}
#[test]
fn test_parser_error_not_ok() {
let tests = vec![
sys::UParseError {
line: 1,
offset: 0,
preContext: [0; 16usize],
postContext: [0; 16usize],
},
sys::UParseError {
line: 0,
offset: 1,
preContext: [0; 16usize],
postContext: [0; 16usize],
},
sys::UParseError {
line: -1,
offset: 1,
preContext: [0; 16usize],
postContext: [0; 16usize],
},
];
for test in tests {
assert!(parse_ok(test).is_err(), "for test: {:?}", test.clone());
}
}
}
pub static NO_PARSE_ERROR: sys::UParseError = sys::UParseError {
line: 0,
offset: 0,
preContext: [0; 16usize],
postContext: [0; 16usize],
};
pub fn parse_ok(e: sys::UParseError) -> Result<(), crate::Error> {
if e.line > 0 || e.offset > 0 {
return Err(Error::Wrapper(anyhow!(
"parse error: line: {}, offset: {}",
e.line,
e.offset
)));
}
Ok(())
}