ffizz_string/
fzstring.rs

1use crate::{EmbeddedNulError, InvalidUTF8Error};
2use ffizz_passby::Unboxed;
3use std::ffi::{CStr, CString, OsString};
4use std::path::PathBuf;
5
6/// A FzString carries a single string between Rust and C code, represented from the C side as
7/// an opaque struct.
8///
9/// The two environments carry some different requirements: C generally requires that strings be
10/// NUL-terminated, while Rust requires that strings be valid UTF-8.  Rust also permits NUL
11/// characters in the middle of a string.
12///
13/// This type accepts whatever kind of data it receives without error, and converts -- potentially
14/// with an error -- when output of a different kind is required.
15///
16/// A number of `From<T>` implementations are provided to convert from common Rust types. The
17/// `fz_string_..` utility functions provide conversions from various string formats.
18///
19/// FzStrings also have a special "Null" state, similar to the None variant of Option.  For user
20/// convenience, a NULL pointer is treated as a pointer to the Null variant wherever a pointer is
21/// accepted.  Rust code should use the `_nonnull` methods where the Null variant is not allowed.
22/// Note that the Null variant is not necessarily represented with an all-zero byte pattern.
23///
24/// A FzString points to allocated memory, and must be freed to avoid memory leaks.
25#[derive(PartialEq, Eq, Debug, Default)]
26pub enum FzString<'a> {
27    /// An un-set FzString.
28    #[default]
29    Null,
30    /// An owned Rust string (not NUL-terminated, valid UTF-8).
31    String(String),
32    /// An owned C String (NUL-terminated, may contain invalid UTF-8).
33    CString(CString),
34    /// A borrowed C string.
35    CStr(&'a CStr),
36    /// An owned bunch of bytes (not NUL-terminated, may contain invalid UTF-8).
37    Bytes(Vec<u8>),
38}
39
40/// fz_string_t represents a string suitable for use with this crate, as an opaque stack-allocated
41/// value.
42///
43/// This value can contain either a string or a special "Null" variant indicating there is no
44/// string.  When functions take a `fz_string_t*` as an argument, the NULL pointer is treated as
45/// the Null variant.  Note that the Null variant is not necessarily represented as the zero value
46/// of the struct.
47///
48/// # Safety
49///
50/// A fz_string_t must always be initialized before it is passed as an argument.  Functions
51/// returning a `fz_string_t` return an initialized value.
52///
53/// Each initialized fz_string_t must be freed, either by calling fz_string_free or by
54/// passing the string to a function which takes ownership of the string.
55///
56/// For a given fz_string_t value, API functions must not be called concurrently.  This includes
57/// "read only" functions such as fz_string_content.
58///
59/// ```c
60/// typedef struct fz_string_t {
61///     size_t __reserved[4];
62/// } fz_string_t;
63/// ```
64#[repr(C)]
65pub struct fz_string_t {
66    // size for a determinant, pointer, length, and capacity; conservatively assuming
67    // each is at least as large as a pointer (usize) and aligned at the pointer size.
68    __reserved: [usize; 4],
69}
70
71type UnboxedString<'a> = Unboxed<FzString<'a>, fz_string_t>;
72
73impl<'a> FzString<'a> {
74    /// Check if this is a Null FzString.
75    pub fn is_null(&self) -> bool {
76        matches!(self, Self::Null)
77    }
78
79    /// Convert this value to `&str`.
80    ///
81    /// If required, the FzString is converted in-place to a String variant. If this conversion
82    /// fails because the content is not valid UTF-8, an error is returned.
83    ///
84    /// The Null FzString is represented as None.
85    pub fn as_str(&mut self) -> Result<Option<&str>, InvalidUTF8Error> {
86        // first, convert in-place from bytes
87        if let FzString::Bytes(_) = self {
88            self.bytes_to_string()?;
89        }
90
91        Ok(match self {
92            FzString::CString(cstring) => {
93                Some(cstring.as_c_str().to_str().map_err(|_| InvalidUTF8Error)?)
94            }
95            FzString::CStr(cstr) => Some(cstr.to_str().map_err(|_| InvalidUTF8Error)?),
96            FzString::String(ref string) => Some(string.as_ref()),
97            FzString::Bytes(_) => unreachable!(), // handled above
98            FzString::Null => None,
99        })
100    }
101
102    /// Convert this FzString, assuming it is not Null, into `&str`.
103    ///
104    /// This is a simple wrapper that will panic on the Null variant.  This is useful when
105    /// the C API prohibits NULL.
106    pub fn as_str_nonnull(&mut self) -> Result<&str, InvalidUTF8Error> {
107        self.as_str()
108            .map(|opt| opt.expect("unexpected NULL string"))
109    }
110
111    /// Convert this value to a CStr: a slice of bytes containing a valid, NUL-terminated C string.
112    ///
113    /// If required, the FzString is converted in-place to a CString variant. If this conversion
114    /// fails because the content contains embedded NUL characters, an error is returned.
115    ///
116    /// The Null FzString is represented as None.
117    pub fn as_cstr(&mut self) -> Result<Option<&CStr>, EmbeddedNulError> {
118        // first, convert in-place from String or Bytes (neither of which have a NUL terminator)
119        match self {
120            FzString::String(_) => self.string_to_cstring()?,
121            FzString::Bytes(_) => self.bytes_to_cstring()?,
122            _ => {}
123        }
124
125        Ok(match self {
126            FzString::CString(cstring) => Some(cstring.as_c_str()),
127            FzString::CStr(cstr) => Some(cstr),
128            FzString::String(_) => unreachable!(), // handled above
129            FzString::Bytes(_) => unreachable!(),  // handled above
130            FzString::Null => None,
131        })
132    }
133
134    /// Convert this FzString, assuming it is not Null, into a CStr.
135    ///
136    /// This is a simple wrapper that will panic on the Null variant.  This is useful when
137    /// the C API prohibits NULL.
138    pub fn as_cstr_nonnull(&mut self) -> Result<&CStr, EmbeddedNulError> {
139        self.as_cstr()
140            .map(|opt| opt.expect("unexpected NULL string"))
141    }
142
143    /// Consume this FzString and return an equivalent String.
144    ///
145    /// As with `as_str`, the FzString is converted in-place, and this conversion can fail.  In the
146    /// failure case, the original data is lost.
147    ///
148    /// The Null varaiant is represented as None.
149    pub fn into_string(mut self) -> Result<Option<String>, InvalidUTF8Error> {
150        // first, convert in-place from bytes
151        if let FzString::Bytes(_) = self {
152            self.bytes_to_string()?;
153        }
154
155        Ok(match self {
156            FzString::CString(cstring) => {
157                Some(cstring.into_string().map_err(|_| InvalidUTF8Error)?)
158            }
159            FzString::CStr(cstr) => Some(
160                cstr.to_str()
161                    .map(|s| s.to_string())
162                    .map_err(|_| InvalidUTF8Error)?,
163            ),
164            FzString::String(string) => Some(string),
165            FzString::Bytes(_) => unreachable!(), // handled above
166            FzString::Null => None,
167        })
168    }
169
170    /// Consume this FzString, assuming it is not Null, and return an equivalent String.
171    ///
172    /// This is a simple wrapper that will panic on the Null variant.  This is useful when
173    /// the C API prohibits NULL.
174    pub fn into_string_nonnull(self) -> Result<String, InvalidUTF8Error> {
175        self.into_string()
176            .map(|opt| opt.expect("unexpected NULL string"))
177    }
178
179    /// Consume this FzString and return an equivalent PathBuf.
180    ///
181    /// As with `as_str`, the FzString is converted in-place, and this conversion can fail.  In the
182    /// failure case, the original data is lost.
183    ///
184    /// The Null varaiant is represented as None.
185    pub fn into_path_buf(self) -> Result<Option<PathBuf>, std::str::Utf8Error> {
186        #[cfg(unix)]
187        let path: Option<OsString> = {
188            // on UNIX, we can use the bytes directly, without requiring that they
189            // be valid UTF-8.
190            use std::ffi::OsStr;
191            use std::os::unix::ffi::OsStrExt;
192            self.as_bytes()
193                .map(|bytes| OsStr::from_bytes(bytes).to_os_string())
194        };
195        #[cfg(windows)]
196        let path: Option<OsString> = {
197            // on Windows, we assume the filename is valid Unicode, so it can be
198            // represented as UTF-8.
199            self.into_string()?.map(|s| OsString::from(s))
200        };
201        Ok(path.map(|p| p.into()))
202    }
203
204    /// Consume this FzString, assuming it is not Null, and return an equivalent PathBuf.
205    ///
206    /// This is a simple wrapper that will panic on the Null variant.  This is useful when
207    /// the C API prohibits NULL.
208    pub fn into_path_buf_nonnull(self) -> Result<PathBuf, std::str::Utf8Error> {
209        self.into_path_buf()
210            .map(|opt| opt.expect("unexpected NULL string"))
211    }
212
213    /// Get the slice of bytes representing the content of this value, not including any NUL
214    /// terminator.
215    ///
216    /// Any variant can be represented as a byte slice, so this method does not mutate the
217    /// FzString and cannot fail.
218    ///
219    /// The Null variant is represented as None.
220    pub fn as_bytes(&self) -> Option<&[u8]> {
221        match self {
222            FzString::CString(cstring) => Some(cstring.as_bytes()),
223            FzString::CStr(cstr) => Some(cstr.to_bytes()),
224            FzString::String(string) => Some(string.as_bytes()),
225            FzString::Bytes(bytes) => Some(bytes.as_ref()),
226            FzString::Null => None,
227        }
228    }
229
230    /// Get the slice of bytes representing the content of this value, not including any NUL
231    /// terminator, panicing if this is the Null Variant.
232    ///
233    /// This is a simple wrapper that will panic on the Null variant.  This is useful when
234    /// the C API prohibits NULL.
235    pub fn as_bytes_nonnull(&self) -> &[u8] {
236        self.as_bytes().expect("unexpected NULL string")
237    }
238
239    /// Call the contained function with a shared reference to the FzString.
240    ///
241    /// This is a wrapper around `ffizz_passby::Unboxed::with_ref`.
242    ///
243    /// # Safety
244    ///
245    /// * fzstr must be NULL or point to a valid fz_string_t value
246    /// * no other thread may mutate the value pointed to by fzstr until with_ref returns.
247    #[inline]
248    pub unsafe fn with_ref<T, F: Fn(&FzString) -> T>(fzstr: *const fz_string_t, f: F) -> T {
249        unsafe { UnboxedString::with_ref(fzstr, f) }
250    }
251
252    /// Call the contained function with an exclusive reference to the FzString.
253    ///
254    /// This is a wrapper around `ffizz_passby::Unboxed::with_ref_mut`.
255    ///
256    /// # Safety
257    ///
258    /// * fzstr must be NULL or point to a valid `fz_string_t` value
259    /// * no other thread may access the value pointed to by `fzstr` until `with_ref_mut` returns.
260    #[inline]
261    pub unsafe fn with_ref_mut<T, F: Fn(&mut FzString) -> T>(fzstr: *mut fz_string_t, f: F) -> T {
262        unsafe { UnboxedString::with_ref_mut(fzstr, f) }
263    }
264
265    /// Initialize the value pointed to fzstr with, "moving" it into the pointer.
266    ///
267    /// This is a wrapper around `ffizz_passby::Unboxed::to_out_param`.
268    ///
269    /// If the pointer is NULL, the value is dropped.
270    ///
271    /// # Safety
272    ///
273    /// * if fzstr is not NULl, then it must be aligned for fz_string_t, and must have enough space
274    ///   for fz_string_t.
275    /// * ownership of the string is transfered to `*fzstr` or dropped.
276    #[inline]
277    pub unsafe fn to_out_param(self, fzstr: *mut fz_string_t) {
278        unsafe { UnboxedString::to_out_param(self, fzstr) }
279    }
280
281    /// Initialize the value pointed to fzstr with, "moving" it into the pointer.
282    ///
283    /// This is a wrapper around `ffizz_passby::Unboxed::to_out_param_nonnull`.
284    ///
285    /// If the pointer is NULL, this method will panic.  Use this when the C API requires that the
286    /// pointer be non-NULL.
287    ///
288    /// # Safety
289    ///
290    /// * fzstr must not be NULL, must be aligned for fz_string_t, and must have enough space for
291    ///   fz_string_t.
292    /// * ownership of the string is transfered to `*fzstr`.
293    #[inline]
294    pub unsafe fn to_out_param_nonnull(self, fzstr: *mut fz_string_t) {
295        unsafe { UnboxedString::to_out_param_nonnull(self, fzstr) }
296    }
297
298    /// Return a `fz_string_t` transferring ownership out of the function.
299    ///
300    /// This is a wrapper around `ffizz_passby::Unboxed::return_val`.
301    ///
302    /// # Safety
303    ///
304    /// * to avoid a leak, ownership of the value must eventually be returned to Rust.
305    #[inline]
306    pub unsafe fn return_val(self) -> fz_string_t {
307        unsafe { UnboxedString::return_val(self) }
308    }
309
310    /// Take a `fz_string_t` by value and return an owned `FzString`.
311    ///
312    /// This is a wrapper around `ffizz_passby::Unboxed::take`.
313    ///
314    /// This method is intended for C API functions that take a string by value and are
315    /// documented as taking ownership of the value.  However, this means that C retains
316    /// an expired "copy" of the value and could lead to use-after-free errors.
317    ///
318    /// Where compatible with the API design, prefer to use pointers in the C API and use
319    /// [`FzString::take_ptr`] to ensure the old value is invalidated.
320    ///
321    /// # Safety
322    ///
323    /// * fzstr must be a valid `fz_string_t` value
324    #[inline]
325    pub unsafe fn take(fzstr: fz_string_t) -> Self {
326        unsafe { UnboxedString::take(fzstr) }
327    }
328
329    /// Take a pointer to a CType and return an owned value.
330    ///
331    /// This is a wrapper around `ffizz_passby::Unboxed::take_ptr`.
332    ///
333    /// This is intended for C API functions that take a value by reference (pointer), but still
334    /// "take ownership" of the value.  It leaves behind an invalid value, where any non-padding
335    /// bytes of the Rust type are zeroed.  This makes use-after-free errors in the C code more
336    /// likely to crash instead of silently working.  Which is about as good as it gets in C.
337    ///
338    /// Do _not_ pass a pointer to a Rust value to this function:
339    ///
340    /// ```ignore
341    /// let rust_value = RustType::take_ptr(&mut c_value); // BAD!
342    /// ```
343    ///
344    /// This creates undefined behavior as Rust will assume `c_value` is still initialized. Use
345    /// `take` in this situation.
346    ///
347    /// # Safety
348    ///
349    /// * fzstr must be NULL or point to a valid fz_string_t value.
350    /// * the memory pointed to by fzstr is uninitialized when this function returns.
351    #[inline]
352    pub unsafe fn take_ptr(fzstr: *mut fz_string_t) -> Self {
353        unsafe { UnboxedString::take_ptr(fzstr) }
354    }
355
356    /// Convert the FzString, in place, from a Bytes to String variant, returning None if
357    /// the bytes do not contain valid UTF-8.
358    fn bytes_to_string(&mut self) -> Result<(), InvalidUTF8Error> {
359        if let FzString::Bytes(bytes) = self {
360            // first, check for invalid UTF-8
361            if std::str::from_utf8(bytes).is_err() {
362                return Err(InvalidUTF8Error);
363            }
364            // take ownership of the bytes Vec
365            let bytes = std::mem::take(bytes);
366            // SAFETY: we just checked this..
367            let string = unsafe { String::from_utf8_unchecked(bytes) };
368            *self = FzString::String(string);
369            Ok(())
370        } else {
371            unreachable!()
372        }
373    }
374
375    /// Convert the FxString, in place, from a Bytes to CString variant, returning None if the
376    /// string contains embedded NULs.
377    ///
378    /// Panics if self is not Bytes.
379    fn bytes_to_cstring(&mut self) -> Result<(), EmbeddedNulError> {
380        if let FzString::Bytes(bytes) = self {
381            // first, check for NUL bytes within the sequence
382            if has_nul_bytes(bytes) {
383                return Err(EmbeddedNulError);
384            }
385            // take ownership of the bytes Vec
386            let bytes = std::mem::take(bytes);
387            // SAFETY: we just checked for NUL bytes
388            let cstring = unsafe { CString::from_vec_unchecked(bytes) };
389            *self = FzString::CString(cstring);
390            Ok(())
391        } else {
392            unreachable!()
393        }
394    }
395
396    /// Convert the FzString, in place, from a String to CString variant, returning None if the
397    /// string contains embedded NULs.
398    ///
399    /// Panics if self is not String.
400    fn string_to_cstring(&mut self) -> Result<(), EmbeddedNulError> {
401        if let FzString::String(string) = self {
402            // first, check for NUL bytes within the sequence
403            if has_nul_bytes(string.as_bytes()) {
404                return Err(EmbeddedNulError);
405            }
406            // take ownership of the string
407            let string = std::mem::take(string);
408            // SAFETY: we just checked for NUL bytes
409            let cstring = unsafe { CString::from_vec_unchecked(string.into_bytes()) };
410            *self = FzString::CString(cstring);
411            Ok(())
412        } else {
413            unreachable!()
414        }
415    }
416}
417
418impl From<String> for FzString<'static> {
419    fn from(string: String) -> FzString<'static> {
420        FzString::String(string)
421    }
422}
423
424impl From<&str> for FzString<'static> {
425    fn from(string: &str) -> FzString<'static> {
426        FzString::String(string.to_string())
427    }
428}
429
430impl From<Vec<u8>> for FzString<'static> {
431    fn from(bytes: Vec<u8>) -> FzString<'static> {
432        FzString::Bytes(bytes)
433    }
434}
435
436impl From<&[u8]> for FzString<'static> {
437    fn from(bytes: &[u8]) -> FzString<'static> {
438        FzString::Bytes(bytes.to_vec())
439    }
440}
441
442impl From<Option<String>> for FzString<'static> {
443    fn from(string: Option<String>) -> FzString<'static> {
444        match string {
445            Some(string) => FzString::String(string),
446            None => FzString::Null,
447        }
448    }
449}
450
451impl From<Option<&str>> for FzString<'static> {
452    fn from(string: Option<&str>) -> FzString<'static> {
453        match string {
454            Some(string) => FzString::String(string.to_string()),
455            None => FzString::Null,
456        }
457    }
458}
459
460impl From<Option<Vec<u8>>> for FzString<'static> {
461    fn from(bytes: Option<Vec<u8>>) -> FzString<'static> {
462        match bytes {
463            Some(bytes) => FzString::Bytes(bytes),
464            None => FzString::Null,
465        }
466    }
467}
468
469impl From<Option<&[u8]>> for FzString<'static> {
470    fn from(bytes: Option<&[u8]>) -> FzString<'static> {
471        match bytes {
472            Some(bytes) => FzString::Bytes(bytes.to_vec()),
473            None => FzString::Null,
474        }
475    }
476}
477
478fn has_nul_bytes(bytes: &[u8]) -> bool {
479    bytes.iter().any(|c| *c == b'\x00')
480}
481
482#[cfg(test)]
483mod test {
484    use super::*;
485
486    const INVALID_UTF8: &[u8] = b"abc\xf0\x28\x8c\x28";
487
488    fn make_cstring() -> FzString<'static> {
489        FzString::CString(CString::new("a string").unwrap())
490    }
491
492    fn make_cstr() -> FzString<'static> {
493        let cstr = CStr::from_bytes_with_nul(b"a string\x00").unwrap();
494        FzString::CStr(cstr)
495    }
496
497    fn make_string() -> FzString<'static> {
498        "a string".into()
499    }
500
501    fn make_string_with_nul() -> FzString<'static> {
502        "a \x00 nul!".into()
503    }
504
505    fn make_invalid_bytes() -> FzString<'static> {
506        INVALID_UTF8.into()
507    }
508
509    fn make_nul_bytes() -> FzString<'static> {
510        (&b"abc\x00123"[..]).into()
511    }
512
513    fn make_bytes() -> FzString<'static> {
514        (&b"bytes"[..]).into()
515    }
516
517    fn make_null() -> FzString<'static> {
518        FzString::Null
519    }
520
521    fn cstr(s: &str) -> &CStr {
522        CStr::from_bytes_with_nul(s.as_bytes()).unwrap()
523    }
524
525    // as_str
526
527    #[test]
528    fn as_str_cstring() {
529        assert_eq!(make_cstring().as_str().unwrap(), Some("a string"));
530    }
531
532    #[test]
533    fn as_str_cstr() {
534        assert_eq!(make_cstr().as_str().unwrap(), Some("a string"));
535    }
536
537    #[test]
538    fn as_str_string() {
539        assert_eq!(make_string().as_str().unwrap(), Some("a string"));
540    }
541
542    #[test]
543    fn as_str_string_with_nul() {
544        assert_eq!(
545            make_string_with_nul().as_str().unwrap(),
546            Some("a \x00 nul!")
547        );
548    }
549
550    #[test]
551    fn as_str_invalid_bytes() {
552        assert_eq!(make_invalid_bytes().as_str().unwrap_err(), InvalidUTF8Error);
553    }
554
555    #[test]
556    fn as_str_nul_bytes() {
557        assert_eq!(make_nul_bytes().as_str().unwrap(), Some("abc\x00123"));
558    }
559
560    #[test]
561    fn as_str_valid_bytes() {
562        assert_eq!(make_bytes().as_str().unwrap(), Some("bytes"));
563    }
564
565    #[test]
566    fn as_str_null() {
567        assert!(make_null().as_str().unwrap().is_none());
568    }
569
570    #[test]
571    fn as_str_nonnull_string() {
572        assert_eq!(make_string().as_str_nonnull().unwrap(), "a string");
573    }
574
575    #[test]
576    #[should_panic]
577    fn as_str_nonnull_null() {
578        let _res = make_null().as_str_nonnull();
579    }
580
581    // as_cstr
582
583    #[test]
584    fn as_cstr_cstring() {
585        assert_eq!(
586            make_cstring().as_cstr().unwrap(),
587            Some(cstr("a string\x00"))
588        );
589    }
590
591    #[test]
592    fn as_cstr_cstr() {
593        assert_eq!(make_cstr().as_cstr().unwrap(), Some(cstr("a string\x00")));
594    }
595
596    #[test]
597    fn as_cstr_string() {
598        assert_eq!(make_string().as_cstr().unwrap(), Some(cstr("a string\x00")));
599    }
600
601    #[test]
602    fn as_cstr_string_with_nul() {
603        assert_eq!(
604            make_string_with_nul().as_cstr().unwrap_err(),
605            EmbeddedNulError
606        );
607    }
608
609    #[test]
610    fn as_cstr_invalid_bytes() {
611        let expected = CString::new(INVALID_UTF8).unwrap();
612        assert_eq!(
613            make_invalid_bytes().as_cstr().unwrap(),
614            Some(expected.as_c_str())
615        );
616    }
617
618    #[test]
619    fn as_cstr_nul_bytes() {
620        assert_eq!(make_nul_bytes().as_cstr().unwrap_err(), EmbeddedNulError);
621    }
622
623    #[test]
624    fn as_cstr_valid_bytes() {
625        assert_eq!(make_bytes().as_cstr().unwrap(), Some(cstr("bytes\x00")));
626    }
627
628    #[test]
629    fn as_cstr_null() {
630        assert_eq!(make_null().as_cstr().unwrap(), None);
631    }
632
633    #[test]
634    fn as_cstr_nonnull_string() {
635        assert_eq!(
636            make_string().as_cstr_nonnull().unwrap(),
637            cstr("a string\x00")
638        );
639    }
640
641    #[test]
642    #[should_panic]
643    fn as_cstr_nonnull_null() {
644        let _res = make_null().as_cstr_nonnull();
645    }
646
647    // into_string
648
649    #[test]
650    fn into_string_cstring() {
651        assert_eq!(
652            make_cstring().into_string().unwrap(),
653            Some(String::from("a string"))
654        );
655    }
656
657    #[test]
658    fn into_string_cstr() {
659        assert_eq!(
660            make_cstr().into_string().unwrap(),
661            Some(String::from("a string"))
662        );
663    }
664
665    #[test]
666    fn into_string_string() {
667        assert_eq!(
668            make_string().into_string().unwrap(),
669            Some(String::from("a string"))
670        );
671    }
672
673    #[test]
674    fn into_string_string_with_nul() {
675        assert_eq!(
676            make_string_with_nul().into_string().unwrap(),
677            Some(String::from("a \x00 nul!"))
678        )
679    }
680
681    #[test]
682    fn into_string_invalid_bytes() {
683        assert_eq!(
684            make_invalid_bytes().into_string().unwrap_err(),
685            InvalidUTF8Error
686        );
687    }
688
689    #[test]
690    fn into_string_nul_bytes() {
691        assert_eq!(
692            make_nul_bytes().into_string().unwrap(),
693            Some(String::from("abc\x00123"))
694        );
695    }
696
697    #[test]
698    fn into_string_valid_bytes() {
699        assert_eq!(
700            make_bytes().into_string().unwrap(),
701            Some(String::from("bytes"))
702        );
703    }
704
705    #[test]
706    fn into_string_null() {
707        assert_eq!(make_null().into_string().unwrap(), None);
708    }
709
710    #[test]
711    fn into_string_nonnull_string() {
712        assert_eq!(
713            make_string().into_string_nonnull().unwrap(),
714            String::from("a string")
715        );
716    }
717
718    #[test]
719    #[should_panic]
720    fn into_string_nonnull_null() {
721        let _res = make_null().into_string_nonnull();
722    }
723
724    // into_path_buf
725
726    #[test]
727    fn into_path_buf_cstring() {
728        assert_eq!(
729            make_cstring().into_path_buf().unwrap(),
730            Some(PathBuf::from("a string"))
731        );
732    }
733
734    #[test]
735    fn into_path_buf_cstr() {
736        assert_eq!(
737            make_cstr().into_path_buf().unwrap(),
738            Some(PathBuf::from("a string"))
739        );
740    }
741
742    #[test]
743    fn into_path_buf_string() {
744        assert_eq!(
745            make_string().into_path_buf().unwrap(),
746            Some(PathBuf::from("a string"))
747        );
748    }
749
750    #[test]
751    fn into_path_buf_string_with_nul() {
752        assert_eq!(
753            make_string_with_nul().into_path_buf().unwrap(),
754            Some(PathBuf::from("a \x00 nul!"))
755        )
756    }
757
758    #[test]
759    fn into_path_buf_invalid_bytes() {
760        #[cfg(windows)] // windows filenames are unicode
761        assert!(make_invalid_bytes().into_path_buf().is_err());
762        #[cfg(unix)] // UNIX doesn't care
763        assert!(make_invalid_bytes().into_path_buf().is_ok());
764    }
765
766    #[test]
767    fn into_path_buf_nul_bytes() {
768        assert_eq!(
769            make_nul_bytes().into_path_buf().unwrap(),
770            Some(PathBuf::from("abc\x00123"))
771        );
772    }
773
774    #[test]
775    fn into_path_buf_valid_bytes() {
776        assert_eq!(
777            make_bytes().into_path_buf().unwrap(),
778            Some(PathBuf::from("bytes"))
779        );
780    }
781
782    #[test]
783    fn into_path_buf_null() {
784        assert_eq!(make_null().into_path_buf().unwrap(), None);
785    }
786
787    #[test]
788    fn into_path_buf_nonnull_string() {
789        assert_eq!(
790            make_string().into_path_buf_nonnull().unwrap(),
791            PathBuf::from("a string")
792        );
793    }
794
795    #[test]
796    #[should_panic]
797    fn into_path_buf_nonnull_null() {
798        let _res = make_null().into_path_buf_nonnull();
799    }
800
801    // as_bytes
802
803    #[test]
804    fn as_bytes_cstring() {
805        assert_eq!(make_cstring().as_bytes().unwrap(), b"a string");
806    }
807
808    #[test]
809    fn as_bytes_cstr() {
810        assert_eq!(make_cstr().as_bytes().unwrap(), b"a string");
811    }
812
813    #[test]
814    fn as_bytes_string() {
815        assert_eq!(make_string().as_bytes().unwrap(), b"a string");
816    }
817
818    #[test]
819    fn as_bytes_string_with_nul() {
820        assert_eq!(make_string_with_nul().as_bytes().unwrap(), b"a \x00 nul!");
821    }
822
823    #[test]
824    fn as_bytes_invalid_bytes() {
825        assert_eq!(make_invalid_bytes().as_bytes().unwrap(), INVALID_UTF8);
826    }
827
828    #[test]
829    fn as_bytes_null_bytes() {
830        assert_eq!(make_nul_bytes().as_bytes().unwrap(), b"abc\x00123");
831    }
832
833    #[test]
834    fn as_bytes_null() {
835        assert_eq!(make_null().as_bytes(), None);
836    }
837
838    #[test]
839    fn as_bytes_nonnul_string() {
840        assert_eq!(make_string().as_bytes_nonnull(), b"a string");
841    }
842
843    #[test]
844    #[should_panic]
845    fn as_bytes_nonnull_null() {
846        let _res = make_null().as_bytes_nonnull();
847    }
848
849    // From<..>
850
851    #[test]
852    fn from_string() {
853        assert_eq!(
854            FzString::from(String::from("hello")),
855            FzString::String(String::from("hello"))
856        );
857    }
858
859    #[test]
860    fn from_str() {
861        assert_eq!(
862            FzString::from("hello"),
863            FzString::String(String::from("hello"))
864        );
865    }
866
867    #[test]
868    fn from_vec() {
869        assert_eq!(FzString::from(vec![1u8, 2u8]), FzString::Bytes(vec![1, 2]));
870    }
871
872    #[test]
873    fn from_bytes() {
874        assert_eq!(FzString::from(INVALID_UTF8), make_invalid_bytes());
875    }
876
877    #[test]
878    fn from_option_string() {
879        assert_eq!(FzString::from(None as Option<String>), FzString::Null);
880        assert_eq!(
881            FzString::from(Some(String::from("hello"))),
882            FzString::String(String::from("hello")),
883        );
884    }
885
886    #[test]
887    fn from_option_str() {
888        assert_eq!(FzString::from(None as Option<&str>), FzString::Null);
889        assert_eq!(
890            FzString::from(Some("hello")),
891            FzString::String(String::from("hello")),
892        );
893    }
894
895    #[test]
896    fn from_option_vec() {
897        assert_eq!(FzString::from(None as Option<Vec<u8>>), FzString::Null);
898        assert_eq!(
899            FzString::from(Some(vec![1u8, 2u8])),
900            FzString::Bytes(vec![1, 2])
901        );
902    }
903
904    #[test]
905    fn from_option_bytes() {
906        assert_eq!(FzString::from(None as Option<&[u8]>), FzString::Null);
907        assert_eq!(
908            FzString::from(Some(INVALID_UTF8)),
909            FzString::Bytes(INVALID_UTF8.into())
910        );
911    }
912}