use crate::{EmbeddedNulError, InvalidUTF8Error};
use ffizz_passby::OpaqueStruct;
use std::ffi::{CStr, CString};
#[derive(PartialEq, Eq, Debug)]
pub enum FzString<'a> {
Null,
String(String),
CString(CString),
CStr(&'a CStr),
Bytes(Vec<u8>),
}
#[repr(C)]
pub struct fz_string_t {
__reserved: [u64; 4],
}
impl OpaqueStruct for FzString<'_> {
type CType = fz_string_t;
fn null_value() -> Self {
FzString::Null
}
}
impl Default for FzString<'_> {
fn default() -> Self {
FzString::Null
}
}
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_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 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 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,
}
}
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'\0')
}
#[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\0").unwrap();
FzString::CStr(cstr)
}
fn make_string() -> FzString<'static> {
"a string".into()
}
fn make_string_with_nul() -> FzString<'static> {
"a \0 nul!".into()
}
fn make_invalid_bytes() -> FzString<'static> {
INVALID_UTF8.into()
}
fn make_nul_bytes() -> FzString<'static> {
(&b"abc\0123"[..]).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 \0 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\0123"));
}
#[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_cstr_cstring() {
assert_eq!(make_cstring().as_cstr().unwrap(), Some(cstr("a string\0")));
}
#[test]
fn as_cstr_cstr() {
assert_eq!(make_cstr().as_cstr().unwrap(), Some(cstr("a string\0")));
}
#[test]
fn as_cstr_string() {
assert_eq!(make_string().as_cstr().unwrap(), Some(cstr("a string\0")));
}
#[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\0")));
}
#[test]
fn as_cstr_null() {
assert_eq!(make_null().as_cstr().unwrap(), None);
}
#[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 \0 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\0123"))
);
}
#[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 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 \0 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\0123");
}
#[test]
fn as_bytes_null() {
assert_eq!(make_null().as_bytes(), None);
}
#[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())
);
}
}