Skip to content

Commit

Permalink
Moves the retry macro to common.
Browse files Browse the repository at this point in the history
Looks like a piece of functionality that could be
reused.

See issue google#85.
  • Loading branch information
filmil committed May 12, 2020
1 parent 9ae9089 commit ae457c0
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 108 deletions.
112 changes: 112 additions & 0 deletions rust_icu_common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,118 @@ impl From<std::string::FromUtf8Error> 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<String, common::Error> {}
/// ```
#[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];

// 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 {
Expand Down
110 changes: 2 additions & 108 deletions rust_icu_uloc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<String, common::Error> {}
/// ```
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<String, common::Error> {
let mut status = common::Error::OK_CODE;
let mut buf: Vec<u8> = 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<String> {
Expand Down

0 comments on commit ae457c0

Please sign in to comment.