1use crate::{EmbeddedNulError, InvalidUTF8Error};
2use ffizz_passby::Unboxed;
3use std::ffi::{CStr, CString, OsString};
4use std::path::PathBuf;
5
6#[derive(PartialEq, Eq, Debug, Default)]
26pub enum FzString<'a> {
27 #[default]
29 Null,
30 String(String),
32 CString(CString),
34 CStr(&'a CStr),
36 Bytes(Vec<u8>),
38}
39
40#[repr(C)]
65pub struct fz_string_t {
66 __reserved: [usize; 4],
69}
70
71type UnboxedString<'a> = Unboxed<FzString<'a>, fz_string_t>;
72
73impl<'a> FzString<'a> {
74 pub fn is_null(&self) -> bool {
76 matches!(self, Self::Null)
77 }
78
79 pub fn as_str(&mut self) -> Result<Option<&str>, InvalidUTF8Error> {
86 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!(), FzString::Null => None,
99 })
100 }
101
102 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 pub fn as_cstr(&mut self) -> Result<Option<&CStr>, EmbeddedNulError> {
118 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!(), FzString::Bytes(_) => unreachable!(), FzString::Null => None,
131 })
132 }
133
134 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 pub fn into_string(mut self) -> Result<Option<String>, InvalidUTF8Error> {
150 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!(), FzString::Null => None,
167 })
168 }
169
170 pub fn into_string_nonnull(self) -> Result<String, InvalidUTF8Error> {
175 self.into_string()
176 .map(|opt| opt.expect("unexpected NULL string"))
177 }
178
179 pub fn into_path_buf(self) -> Result<Option<PathBuf>, std::str::Utf8Error> {
186 #[cfg(unix)]
187 let path: Option<OsString> = {
188 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 self.into_string()?.map(|s| OsString::from(s))
200 };
201 Ok(path.map(|p| p.into()))
202 }
203
204 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 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 pub fn as_bytes_nonnull(&self) -> &[u8] {
236 self.as_bytes().expect("unexpected NULL string")
237 }
238
239 #[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 #[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 #[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 #[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 #[inline]
306 pub unsafe fn return_val(self) -> fz_string_t {
307 unsafe { UnboxedString::return_val(self) }
308 }
309
310 #[inline]
325 pub unsafe fn take(fzstr: fz_string_t) -> Self {
326 unsafe { UnboxedString::take(fzstr) }
327 }
328
329 #[inline]
352 pub unsafe fn take_ptr(fzstr: *mut fz_string_t) -> Self {
353 unsafe { UnboxedString::take_ptr(fzstr) }
354 }
355
356 fn bytes_to_string(&mut self) -> Result<(), InvalidUTF8Error> {
359 if let FzString::Bytes(bytes) = self {
360 if std::str::from_utf8(bytes).is_err() {
362 return Err(InvalidUTF8Error);
363 }
364 let bytes = std::mem::take(bytes);
366 let string = unsafe { String::from_utf8_unchecked(bytes) };
368 *self = FzString::String(string);
369 Ok(())
370 } else {
371 unreachable!()
372 }
373 }
374
375 fn bytes_to_cstring(&mut self) -> Result<(), EmbeddedNulError> {
380 if let FzString::Bytes(bytes) = self {
381 if has_nul_bytes(bytes) {
383 return Err(EmbeddedNulError);
384 }
385 let bytes = std::mem::take(bytes);
387 let cstring = unsafe { CString::from_vec_unchecked(bytes) };
389 *self = FzString::CString(cstring);
390 Ok(())
391 } else {
392 unreachable!()
393 }
394 }
395
396 fn string_to_cstring(&mut self) -> Result<(), EmbeddedNulError> {
401 if let FzString::String(string) = self {
402 if has_nul_bytes(string.as_bytes()) {
404 return Err(EmbeddedNulError);
405 }
406 let string = std::mem::take(string);
408 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 #[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 #[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 #[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 #[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)] assert!(make_invalid_bytes().into_path_buf().is_err());
762 #[cfg(unix)] 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 #[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 #[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}