diff --git a/rust_icu_common/src/lib.rs b/rust_icu_common/src/lib.rs index 95d112ea..46f4265d 100644 --- a/rust_icu_common/src/lib.rs +++ b/rust_icu_common/src/lib.rs @@ -145,6 +145,118 @@ impl From for Error { } } +/// Generates a method to wrap ICU4C `uloc` methods that require a resizable output string buffer. +/// +/// The various `uloc` methods of this type have inconsistent signature patterns, with some putting +/// all their input arguments _before_ the `buffer` and its `capacity`, and some splitting the input +/// arguments. +/// +/// Therefore, the macro supports input arguments in both positions. +/// +/// For an invocation of the form +/// +/// ```ignore +/// buffered_string_method_with_retry!( +/// my_method, +/// BUFFER_CAPACITY, +/// [before_arg_a: before_type_a, before_arg_b: before_type_b,], +/// [after_arg_a: after_type_a, after_arg_b: after_type_b,] +/// ); +/// ``` +/// +/// the generated method has a signature of the form +/// +/// ```ignore +/// fn my_method( +/// method_to_call: unsafe extern "C" fn( +/// before_type_a, +/// before_type_b, +/// *mut raw::c_char, +/// i32, +/// after_type_a, +/// after_type_b, +/// *mut sys::UErrorCode, +/// ) -> i32, +/// before_arg_a: before_type_a, +/// before_arg_b: before_type_b, +/// after_arg_a: after_type_a, +/// after_arg_b: after_type_b +/// ) -> Result {} +/// ``` +#[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 { + let mut status = common::Error::OK_CODE; + let mut buf: Vec = vec![0; $buffer_capacity]; + + // Requires that any pointers that are passed in are valid. + 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, + ) + }; + + // ICU methods are inconsistent in whether they silently truncate the output or treat + // the overflow as an error, so we need to check both cases. + 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); + + // Same unsafe requirements as above, plus full_len must be exactly the output + // buffer size. + 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)?; + + // Adjust the size of the buffer here. + 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()) + } + } +} + /// Used to simulate an array of C-style strings. #[derive(Debug)] pub struct CStringVec { diff --git a/rust_icu_uloc/src/lib.rs b/rust_icu_uloc/src/lib.rs index 6bc4174e..151d896f 100644 --- a/rust_icu_uloc/src/lib.rs +++ b/rust_icu_uloc/src/lib.rs @@ -14,8 +14,10 @@ use { rust_icu_common as common, + rust_icu_common::buffered_string_method_with_retry, rust_icu_sys::versioned_function, rust_icu_sys::*, + rust_icu_sys as sys, rust_icu_uenum::Enumeration, std::{ cmp::Ordering, @@ -65,114 +67,6 @@ impl TryFrom<&ffi::CStr> for ULoc { } } -/// Generates a method to wrap ICU4C `uloc` methods that require a resizable output string buffer. -/// -/// The various `uloc` methods of this type have inconsistent signature patterns, with some putting -/// all their input arguments _before_ the `buffer` and its `capacity`, and some splitting the input -/// arguments. -/// -/// Therefore, the macro supports input arguments in both positions. -/// -/// For an invocation of the form -/// ``` -/// buffered_string_method_with_retry!( -/// my_method, -/// BUFFER_CAPACITY, -/// [before_arg_a: before_type_a, before_arg_b: before_type_b,], -/// [after_arg_a: after_type_a, after_arg_b: after_type_b,] -/// ); -/// ``` -/// the generated method has a signature of the form -/// ``` -/// fn my_method( -/// uloc_method: unsafe extern "C" fn( -/// before_type_a, -/// before_type_b, -/// *mut raw::c_char, -/// i32, -/// after_type_a, -/// after_type_b, -/// *mut UErrorCode, -/// ) -> i32, -/// before_arg_a: before_type_a, -/// before_arg_b: before_type_b, -/// after_arg_a: after_type_a, -/// after_arg_b: after_type_b -/// ) -> Result {} -/// ``` -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( - uloc_method: unsafe extern "C" fn( - $($before_arg_type,)* - *mut raw::c_char, - i32, - $($after_arg_type,)* - *mut UErrorCode, - ) -> i32, - $($before_arg: $before_arg_type,)* - $($after_arg: $after_arg_type,)* - ) -> Result { - let mut status = common::Error::OK_CODE; - let mut buf: Vec = vec![0; $buffer_capacity]; - - // Requires that any pointers that are passed in are valid. - let full_len: i32 = unsafe { - assert!(common::Error::is_ok(status)); - uloc_method( - $($before_arg,)* - buf.as_mut_ptr() as *mut raw::c_char, - $buffer_capacity as i32, - $($after_arg,)* - &mut status, - ) - }; - - // `uloc` methods are inconsistent in whether they silently truncate the output or treat - // the overflow as an error, so we need to check both cases. - if status == 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); - - // Same unsafe requirements as above, plus full_len must be exactly the output - // buffer size. - unsafe { - assert!(common::Error::is_ok(status)); - uloc_method( - $($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)?; - - // Adjust the size of the buffer here. - 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()) - } - } -} - impl ULoc { /// Implements `uloc_getLanguage`. pub fn language(&self) -> Option {