use crate::{EmbeddedNulError, InvalidUTF8Error};
use ffizz_passby::Unboxed;
use std::ffi::{CStr, CString, OsString};
use std::path::PathBuf;
#[derive(PartialEq, Eq, Debug, Default)]
pub enum FzString<'a> {
#[default]
Null,
String(String),
CString(CString),
CStr(&'a CStr),
Bytes(Vec<u8>),
}
#[repr(C)]
pub struct fz_string_t {
__reserved: [usize; 4],
}
type UnboxedString<'a> = Unboxed<FzString<'a>, fz_string_t>;
impl<'a> FzString<'a> {
pub fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
pub fn as_str(&mut self) -> Result<Option<&str>, InvalidUTF8Error> {
if let FzString::Bytes(_) = self {
self.bytes_to_string()?;
}
Ok(match self {
FzString::CString(cstring) => {
Some(cstring.as_c_str().to_str().map_err(|_| InvalidUTF8Error)?)
}
FzString::CStr(cstr) => Some(cstr.to_str().map_err(|_| InvalidUTF8Error)?),
FzString::String(ref string) => Some(string.as_ref()),
FzString::Bytes(_) => unreachable!(), FzString::Null => None,
})
}
pub fn as_str_nonnull(&mut self) -> Result<&str, InvalidUTF8Error> {
self.as_str()
.map(|opt| opt.expect("unexpected NULL string"))
}
pub fn as_cstr(&mut self) -> Result<Option<&CStr>, EmbeddedNulError> {
match self {
FzString::String(_) => self.string_to_cstring()?,
FzString::Bytes(_) => self.bytes_to_cstring()?,
_ => {}
}
Ok(match self {
FzString::CString(cstring) => Some(cstring.as_c_str()),
FzString::CStr(cstr) => Some(cstr),
FzString::String(_) => unreachable!(), FzString::Bytes(_) => unreachable!(), FzString::Null => None,
})
}
pub fn as_cstr_nonnull(&mut self) -> Result<&CStr, EmbeddedNulError> {
self.as_cstr()
.map(|opt| opt.expect("unexpected NULL string"))
}
pub fn into_string(mut self) -> Result<Option<String>, InvalidUTF8Error> {
if let FzString::Bytes(_) = self {
self.bytes_to_string()?;
}
Ok(match self {
FzString::CString(cstring) => {
Some(cstring.into_string().map_err(|_| InvalidUTF8Error)?)
}
FzString::CStr(cstr) => Some(
cstr.to_str()
.map(|s| s.to_string())
.map_err(|_| InvalidUTF8Error)?,
),
FzString::String(string) => Some(string),
FzString::Bytes(_) => unreachable!(), FzString::Null => None,
})
}
pub fn into_string_nonnull(self) -> Result<String, InvalidUTF8Error> {
self.into_string()
.map(|opt| opt.expect("unexpected NULL string"))
}
pub fn into_path_buf(self) -> Result<Option<PathBuf>, std::str::Utf8Error> {
#[cfg(unix)]
let path: Option<OsString> = {
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
self.as_bytes()
.map(|bytes| OsStr::from_bytes(bytes).to_os_string())
};
#[cfg(windows)]
let path: Option<OsString> = {
self.into_string()?.map(|s| OsString::from(s))
};
Ok(path.map(|p| p.into()))
}
pub fn into_path_buf_nonnull(self) -> Result<PathBuf, std::str::Utf8Error> {
self.into_path_buf()
.map(|opt| opt.expect("unexpected NULL string"))
}
pub fn as_bytes(&self) -> Option<&[u8]> {
match self {
FzString::CString(cstring) => Some(cstring.as_bytes()),
FzString::CStr(cstr) => Some(cstr.to_bytes()),
FzString::String(string) => Some(string.as_bytes()),
FzString::Bytes(bytes) => Some(bytes.as_ref()),
FzString::Null => None,
}
}
pub fn as_bytes_nonnull(&self) -> &[u8] {
self.as_bytes().expect("unexpected NULL string")
}
#[inline]
pub unsafe fn with_ref<T, F: Fn(&FzString) -> T>(fzstr: *const fz_string_t, f: F) -> T {
unsafe { UnboxedString::with_ref(fzstr, f) }
}
#[inline]
pub unsafe fn with_ref_mut<T, F: Fn(&mut FzString) -> T>(fzstr: *mut fz_string_t, f: F) -> T {
unsafe { UnboxedString::with_ref_mut(fzstr, f) }
}
#[inline]
pub unsafe fn to_out_param(self, fzstr: *mut fz_string_t) {
unsafe { UnboxedString::to_out_param(self, fzstr) }
}
#[inline]
pub unsafe fn to_out_param_nonnull(self, fzstr: *mut fz_string_t) {
unsafe { UnboxedString::to_out_param_nonnull(self, fzstr) }
}
#[inline]
pub unsafe fn return_val(self) -> fz_string_t {
unsafe { UnboxedString::return_val(self) }
}
#[inline]
pub unsafe fn take(fzstr: fz_string_t) -> Self {
unsafe { UnboxedString::take(fzstr) }
}
#[inline]
pub unsafe fn take_ptr(fzstr: *mut fz_string_t) -> Self {
unsafe { UnboxedString::take_ptr(fzstr) }
}
fn bytes_to_string(&mut self) -> Result<(), InvalidUTF8Error> {
if let FzString::Bytes(bytes) = self {
if std::str::from_utf8(bytes).is_err() {
return Err(InvalidUTF8Error);
}
let bytes = std::mem::take(bytes);
let string = unsafe { String::from_utf8_unchecked(bytes) };
*self = FzString::String(string);
Ok(())
} else {
unreachable!()
}
}
fn bytes_to_cstring(&mut self) -> Result<(), EmbeddedNulError> {
if let FzString::Bytes(bytes) = self {
if has_nul_bytes(bytes) {
return Err(EmbeddedNulError);
}
let bytes = std::mem::take(bytes);
let cstring = unsafe { CString::from_vec_unchecked(bytes) };
*self = FzString::CString(cstring);
Ok(())
} else {
unreachable!()
}
}
fn string_to_cstring(&mut self) -> Result<(), EmbeddedNulError> {
if let FzString::String(string) = self {
if has_nul_bytes(string.as_bytes()) {
return Err(EmbeddedNulError);
}
let string = std::mem::take(string);
let cstring = unsafe { CString::from_vec_unchecked(string.into_bytes()) };
*self = FzString::CString(cstring);
Ok(())
} else {
unreachable!()
}
}
}
impl From<String> for FzString<'static> {
fn from(string: String) -> FzString<'static> {
FzString::String(string)
}
}
impl From<&str> for FzString<'static> {
fn from(string: &str) -> FzString<'static> {
FzString::String(string.to_string())
}
}
impl From<Vec<u8>> for FzString<'static> {
fn from(bytes: Vec<u8>) -> FzString<'static> {
FzString::Bytes(bytes)
}
}
impl From<&[u8]> for FzString<'static> {
fn from(bytes: &[u8]) -> FzString<'static> {
FzString::Bytes(bytes.to_vec())
}
}
impl From<Option<String>> for FzString<'static> {
fn from(string: Option<String>) -> FzString<'static> {
match string {
Some(string) => FzString::String(string),
None => FzString::Null,
}
}
}
impl From<Option<&str>> for FzString<'static> {
fn from(string: Option<&str>) -> FzString<'static> {
match string {
Some(string) => FzString::String(string.to_string()),
None => FzString::Null,
}
}
}
impl From<Option<Vec<u8>>> for FzString<'static> {
fn from(bytes: Option<Vec<u8>>) -> FzString<'static> {
match bytes {
Some(bytes) => FzString::Bytes(bytes),
None => FzString::Null,
}
}
}
impl From<Option<&[u8]>> for FzString<'static> {
fn from(bytes: Option<&[u8]>) -> FzString<'static> {
match bytes {
Some(bytes) => FzString::Bytes(bytes.to_vec()),
None => FzString::Null,
}
}
}
fn has_nul_bytes(bytes: &[u8]) -> bool {
bytes.iter().any(|c| *c == b'\x00')
}
#[cfg(test)]
mod test {
use super::*;
const INVALID_UTF8: &[u8] = b"abc\xf0\x28\x8c\x28";
fn make_cstring() -> FzString<'static> {
FzString::CString(CString::new("a string").unwrap())
}
fn make_cstr() -> FzString<'static> {
let cstr = CStr::from_bytes_with_nul(b"a string\x00").unwrap();
FzString::CStr(cstr)
}
fn make_string() -> FzString<'static> {
"a string".into()
}
fn make_string_with_nul() -> FzString<'static> {
"a \x00 nul!".into()
}
fn make_invalid_bytes() -> FzString<'static> {
INVALID_UTF8.into()
}
fn make_nul_bytes() -> FzString<'static> {
(&b"abc\x00123"[..]).into()
}
fn make_bytes() -> FzString<'static> {
(&b"bytes"[..]).into()
}
fn make_null() -> FzString<'static> {
FzString::Null
}
fn cstr(s: &str) -> &CStr {
CStr::from_bytes_with_nul(s.as_bytes()).unwrap()
}
#[test]
fn as_str_cstring() {
assert_eq!(make_cstring().as_str().unwrap(), Some("a string"));
}
#[test]
fn as_str_cstr() {
assert_eq!(make_cstr().as_str().unwrap(), Some("a string"));
}
#[test]
fn as_str_string() {
assert_eq!(make_string().as_str().unwrap(), Some("a string"));
}
#[test]
fn as_str_string_with_nul() {
assert_eq!(
make_string_with_nul().as_str().unwrap(),
Some("a \x00 nul!")
);
}
#[test]
fn as_str_invalid_bytes() {
assert_eq!(make_invalid_bytes().as_str().unwrap_err(), InvalidUTF8Error);
}
#[test]
fn as_str_nul_bytes() {
assert_eq!(make_nul_bytes().as_str().unwrap(), Some("abc\x00123"));
}
#[test]
fn as_str_valid_bytes() {
assert_eq!(make_bytes().as_str().unwrap(), Some("bytes"));
}
#[test]
fn as_str_null() {
assert!(make_null().as_str().unwrap().is_none());
}
#[test]
fn as_str_nonnull_string() {
assert_eq!(make_string().as_str_nonnull().unwrap(), "a string");
}
#[test]
#[should_panic]
fn as_str_nonnull_null() {
let _res = make_null().as_str_nonnull();
}
#[test]
fn as_cstr_cstring() {
assert_eq!(
make_cstring().as_cstr().unwrap(),
Some(cstr("a string\x00"))
);
}
#[test]
fn as_cstr_cstr() {
assert_eq!(make_cstr().as_cstr().unwrap(), Some(cstr("a string\x00")));
}
#[test]
fn as_cstr_string() {
assert_eq!(make_string().as_cstr().unwrap(), Some(cstr("a string\x00")));
}
#[test]
fn as_cstr_string_with_nul() {
assert_eq!(
make_string_with_nul().as_cstr().unwrap_err(),
EmbeddedNulError
);
}
#[test]
fn as_cstr_invalid_bytes() {
let expected = CString::new(INVALID_UTF8).unwrap();
assert_eq!(
make_invalid_bytes().as_cstr().unwrap(),
Some(expected.as_c_str())
);
}
#[test]
fn as_cstr_nul_bytes() {
assert_eq!(make_nul_bytes().as_cstr().unwrap_err(), EmbeddedNulError);
}
#[test]
fn as_cstr_valid_bytes() {
assert_eq!(make_bytes().as_cstr().unwrap(), Some(cstr("bytes\x00")));
}
#[test]
fn as_cstr_null() {
assert_eq!(make_null().as_cstr().unwrap(), None);
}
#[test]
fn as_cstr_nonnull_string() {
assert_eq!(
make_string().as_cstr_nonnull().unwrap(),
cstr("a string\x00")
);
}
#[test]
#[should_panic]
fn as_cstr_nonnull_null() {
let _res = make_null().as_cstr_nonnull();
}
#[test]
fn into_string_cstring() {
assert_eq!(
make_cstring().into_string().unwrap(),
Some(String::from("a string"))
);
}
#[test]
fn into_string_cstr() {
assert_eq!(
make_cstr().into_string().unwrap(),
Some(String::from("a string"))
);
}
#[test]
fn into_string_string() {
assert_eq!(
make_string().into_string().unwrap(),
Some(String::from("a string"))
);
}
#[test]
fn into_string_string_with_nul() {
assert_eq!(
make_string_with_nul().into_string().unwrap(),
Some(String::from("a \x00 nul!"))
)
}
#[test]
fn into_string_invalid_bytes() {
assert_eq!(
make_invalid_bytes().into_string().unwrap_err(),
InvalidUTF8Error
);
}
#[test]
fn into_string_nul_bytes() {
assert_eq!(
make_nul_bytes().into_string().unwrap(),
Some(String::from("abc\x00123"))
);
}
#[test]
fn into_string_valid_bytes() {
assert_eq!(
make_bytes().into_string().unwrap(),
Some(String::from("bytes"))
);
}
#[test]
fn into_string_null() {
assert_eq!(make_null().into_string().unwrap(), None);
}
#[test]
fn into_string_nonnull_string() {
assert_eq!(
make_string().into_string_nonnull().unwrap(),
String::from("a string")
);
}
#[test]
#[should_panic]
fn into_string_nonnull_null() {
let _res = make_null().into_string_nonnull();
}
#[test]
fn into_path_buf_cstring() {
assert_eq!(
make_cstring().into_path_buf().unwrap(),
Some(PathBuf::from("a string"))
);
}
#[test]
fn into_path_buf_cstr() {
assert_eq!(
make_cstr().into_path_buf().unwrap(),
Some(PathBuf::from("a string"))
);
}
#[test]
fn into_path_buf_string() {
assert_eq!(
make_string().into_path_buf().unwrap(),
Some(PathBuf::from("a string"))
);
}
#[test]
fn into_path_buf_string_with_nul() {
assert_eq!(
make_string_with_nul().into_path_buf().unwrap(),
Some(PathBuf::from("a \x00 nul!"))
)
}
#[test]
fn into_path_buf_invalid_bytes() {
#[cfg(windows)] assert!(make_invalid_bytes().into_path_buf().is_err());
#[cfg(unix)] assert!(make_invalid_bytes().into_path_buf().is_ok());
}
#[test]
fn into_path_buf_nul_bytes() {
assert_eq!(
make_nul_bytes().into_path_buf().unwrap(),
Some(PathBuf::from("abc\x00123"))
);
}
#[test]
fn into_path_buf_valid_bytes() {
assert_eq!(
make_bytes().into_path_buf().unwrap(),
Some(PathBuf::from("bytes"))
);
}
#[test]
fn into_path_buf_null() {
assert_eq!(make_null().into_path_buf().unwrap(), None);
}
#[test]
fn into_path_buf_nonnull_string() {
assert_eq!(
make_string().into_path_buf_nonnull().unwrap(),
PathBuf::from("a string")
);
}
#[test]
#[should_panic]
fn into_path_buf_nonnull_null() {
let _res = make_null().into_path_buf_nonnull();
}
#[test]
fn as_bytes_cstring() {
assert_eq!(make_cstring().as_bytes().unwrap(), b"a string");
}
#[test]
fn as_bytes_cstr() {
assert_eq!(make_cstr().as_bytes().unwrap(), b"a string");
}
#[test]
fn as_bytes_string() {
assert_eq!(make_string().as_bytes().unwrap(), b"a string");
}
#[test]
fn as_bytes_string_with_nul() {
assert_eq!(make_string_with_nul().as_bytes().unwrap(), b"a \x00 nul!");
}
#[test]
fn as_bytes_invalid_bytes() {
assert_eq!(make_invalid_bytes().as_bytes().unwrap(), INVALID_UTF8);
}
#[test]
fn as_bytes_null_bytes() {
assert_eq!(make_nul_bytes().as_bytes().unwrap(), b"abc\x00123");
}
#[test]
fn as_bytes_null() {
assert_eq!(make_null().as_bytes(), None);
}
#[test]
fn as_bytes_nonnul_string() {
assert_eq!(make_string().as_bytes_nonnull(), b"a string");
}
#[test]
#[should_panic]
fn as_bytes_nonnull_null() {
let _res = make_null().as_bytes_nonnull();
}
#[test]
fn from_string() {
assert_eq!(
FzString::from(String::from("hello")),
FzString::String(String::from("hello"))
);
}
#[test]
fn from_str() {
assert_eq!(
FzString::from("hello"),
FzString::String(String::from("hello"))
);
}
#[test]
fn from_vec() {
assert_eq!(FzString::from(vec![1u8, 2u8]), FzString::Bytes(vec![1, 2]));
}
#[test]
fn from_bytes() {
assert_eq!(FzString::from(INVALID_UTF8), make_invalid_bytes());
}
#[test]
fn from_option_string() {
assert_eq!(FzString::from(None as Option<String>), FzString::Null);
assert_eq!(
FzString::from(Some(String::from("hello"))),
FzString::String(String::from("hello")),
);
}
#[test]
fn from_option_str() {
assert_eq!(FzString::from(None as Option<&str>), FzString::Null);
assert_eq!(
FzString::from(Some("hello")),
FzString::String(String::from("hello")),
);
}
#[test]
fn from_option_vec() {
assert_eq!(FzString::from(None as Option<Vec<u8>>), FzString::Null);
assert_eq!(
FzString::from(Some(vec![1u8, 2u8])),
FzString::Bytes(vec![1, 2])
);
}
#[test]
fn from_option_bytes() {
assert_eq!(FzString::from(None as Option<&[u8]>), FzString::Null);
assert_eq!(
FzString::from(Some(INVALID_UTF8)),
FzString::Bytes(INVALID_UTF8.into())
);
}
}