1use subversion_sys::svn_error_t;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum ErrorCategory {
10 BadInput,
12 Xml,
14 Io,
16 Stream,
18 Node,
20 Entry,
22 WorkingCopy,
24 Filesystem,
26 Repository,
28 RepositoryAccess,
30 RaDav,
32 RaLocal,
34 Svndiff,
36 ApacheMod,
38 Client,
40 Misc,
42 CommandLine,
44 RaSvn,
46 Authentication,
48 Authorization,
50 Diff,
52 RaSerf,
54 Malfunction,
56 X509,
58 Other,
60}
61
62pub struct Error<'a> {
100 ptr: *mut svn_error_t,
101 owns_ptr: bool,
102 _phantom: std::marker::PhantomData<&'a ()>,
103}
104
105unsafe impl Send for Error<'_> {}
106
107impl Error<'static> {
108 pub fn new(status: apr::Status, child: Option<Error<'static>>, msg: &str) -> Self {
110 let msg = std::ffi::CString::new(msg).unwrap();
111 let child = child
112 .map(|mut e| unsafe { e.detach() })
113 .unwrap_or(std::ptr::null_mut());
114 let err = unsafe { subversion_sys::svn_error_create(status as i32, child, msg.as_ptr()) };
115 Self {
116 ptr: err,
117 owns_ptr: true,
118 _phantom: std::marker::PhantomData,
119 }
120 }
121
122 pub fn with_raw_status(status: i32, child: Option<Error<'static>>, msg: &str) -> Self {
127 let msg = std::ffi::CString::new(msg).unwrap();
128 let child = child
129 .map(|mut e| unsafe { e.detach() })
130 .unwrap_or(std::ptr::null_mut());
131 let err = unsafe { subversion_sys::svn_error_create(status, child, msg.as_ptr()) };
132 Self {
133 ptr: err,
134 owns_ptr: true,
135 _phantom: std::marker::PhantomData,
136 }
137 }
138
139 pub fn from_message(msg: &str) -> Error<'static> {
141 Self::new(apr::Status::from(1), None, msg)
142 }
143
144 pub fn from_raw(err: *mut svn_error_t) -> Result<(), Error<'static>> {
146 if err.is_null() {
147 Ok(())
148 } else {
149 Err(Error {
150 ptr: err,
151 owns_ptr: true,
152 _phantom: std::marker::PhantomData,
153 })
154 }
155 }
156}
157
158impl<'a> Error<'a> {
159 pub(crate) unsafe fn from_ptr_borrowed(err: *mut svn_error_t) -> Error<'a> {
168 debug_assert!(!err.is_null());
169 Error {
170 ptr: err,
171 owns_ptr: false,
172 _phantom: std::marker::PhantomData,
173 }
174 }
175}
176
177impl<'a> Error<'a> {
178 pub fn apr_err(&self) -> apr::Status {
184 unsafe { (*self.ptr).apr_err }.into()
185 }
186
187 pub fn raw_apr_err(&self) -> i32 {
192 unsafe { (*self.ptr).apr_err }
193 }
194
195 pub fn as_mut_ptr(&mut self) -> *mut svn_error_t {
197 self.ptr
198 }
199
200 pub fn as_ptr(&self) -> *const svn_error_t {
202 self.ptr
203 }
204
205 pub fn line(&self) -> i64 {
207 unsafe { (*self.ptr).line.into() }
208 }
209
210 pub fn file(&self) -> Option<&str> {
212 unsafe {
213 let file = (*self.ptr).file;
214 if file.is_null() {
215 None
216 } else {
217 Some(std::ffi::CStr::from_ptr(file).to_str().unwrap())
218 }
219 }
220 }
221
222 pub fn location(&self) -> Option<(&str, i64)> {
224 self.file().map(|f| (f, self.line()))
225 }
226
227 pub fn child(&self) -> Option<Error<'a>> {
232 unsafe {
233 let child = (*self.ptr).child;
234 if child.is_null() {
235 None
236 } else {
237 Some(Error {
238 ptr: child,
239 owns_ptr: false,
240 _phantom: std::marker::PhantomData,
241 })
242 }
243 }
244 }
245
246 pub fn message(&self) -> Option<&str> {
248 unsafe {
249 let message = (*self.ptr).message;
250 if message.is_null() {
251 None
252 } else {
253 Some(std::ffi::CStr::from_ptr(message).to_str().unwrap())
254 }
255 }
256 }
257
258 pub fn find_cause(&self, status: apr::Status) -> Option<Error<'a>> {
262 unsafe {
263 let err = subversion_sys::svn_error_find_cause(self.ptr, status as i32);
264 if err.is_null() {
265 None
266 } else {
267 Some(Error {
268 ptr: err,
269 owns_ptr: false,
270 _phantom: std::marker::PhantomData,
271 })
272 }
273 }
274 }
275
276 pub fn purge_tracing(&self) -> Error<'_> {
280 unsafe {
281 Error {
282 ptr: subversion_sys::svn_error_purge_tracing(self.ptr),
283 owns_ptr: false,
284 _phantom: std::marker::PhantomData,
285 }
286 }
287 }
288
289 pub unsafe fn detach(&mut self) -> *mut svn_error_t {
296 let err = self.ptr;
297 self.ptr = std::ptr::null_mut();
298 err
299 }
300
301 pub unsafe fn into_raw(self) -> *mut svn_error_t {
308 let err = self.ptr;
309 std::mem::forget(self);
310 err
311 }
312
313 pub fn best_message(&self) -> String {
315 let mut buf = [0; 1024];
316 unsafe {
317 let ret = subversion_sys::svn_err_best_message(self.ptr, buf.as_mut_ptr(), buf.len());
318 std::ffi::CStr::from_ptr(ret).to_string_lossy().into_owned()
319 }
320 }
321
322 pub fn full_message(&self) -> String {
324 let mut messages = Vec::new();
325 let mut current = self.ptr;
326
327 unsafe {
328 while !current.is_null() {
329 let msg = (*current).message;
330 if !msg.is_null() {
331 let msg_str = std::ffi::CStr::from_ptr(msg).to_string_lossy();
332 if !msg_str.is_empty() {
333 messages.push(msg_str.into_owned());
334 }
335 }
336 current = (*current).child;
337 }
338 }
339
340 if messages.is_empty() {
341 self.best_message()
342 } else {
343 messages.join(": ")
344 }
345 }
346
347 pub fn category(&self) -> ErrorCategory {
371 use subversion_sys::*;
372 let code = unsafe { (*self.ptr).apr_err as u32 };
374 let category_size = SVN_ERR_CATEGORY_SIZE;
375
376 match code {
377 c if c >= SVN_ERR_BAD_CATEGORY_START
378 && c < SVN_ERR_BAD_CATEGORY_START + category_size =>
379 {
380 ErrorCategory::BadInput
381 }
382 c if c >= SVN_ERR_XML_CATEGORY_START
383 && c < SVN_ERR_XML_CATEGORY_START + category_size =>
384 {
385 ErrorCategory::Xml
386 }
387 c if c >= SVN_ERR_IO_CATEGORY_START
388 && c < SVN_ERR_IO_CATEGORY_START + category_size =>
389 {
390 ErrorCategory::Io
391 }
392 c if c >= SVN_ERR_STREAM_CATEGORY_START
393 && c < SVN_ERR_STREAM_CATEGORY_START + category_size =>
394 {
395 ErrorCategory::Stream
396 }
397 c if c >= SVN_ERR_NODE_CATEGORY_START
398 && c < SVN_ERR_NODE_CATEGORY_START + category_size =>
399 {
400 ErrorCategory::Node
401 }
402 c if c >= SVN_ERR_ENTRY_CATEGORY_START
403 && c < SVN_ERR_ENTRY_CATEGORY_START + category_size =>
404 {
405 ErrorCategory::Entry
406 }
407 c if c >= SVN_ERR_WC_CATEGORY_START
408 && c < SVN_ERR_WC_CATEGORY_START + category_size =>
409 {
410 ErrorCategory::WorkingCopy
411 }
412 c if c >= SVN_ERR_FS_CATEGORY_START
413 && c < SVN_ERR_FS_CATEGORY_START + category_size =>
414 {
415 ErrorCategory::Filesystem
416 }
417 c if c >= SVN_ERR_REPOS_CATEGORY_START
418 && c < SVN_ERR_REPOS_CATEGORY_START + category_size =>
419 {
420 ErrorCategory::Repository
421 }
422 c if c >= SVN_ERR_RA_CATEGORY_START
423 && c < SVN_ERR_RA_CATEGORY_START + category_size =>
424 {
425 ErrorCategory::RepositoryAccess
426 }
427 c if c >= SVN_ERR_RA_DAV_CATEGORY_START
428 && c < SVN_ERR_RA_DAV_CATEGORY_START + category_size =>
429 {
430 ErrorCategory::RaDav
431 }
432 c if c >= SVN_ERR_RA_LOCAL_CATEGORY_START
433 && c < SVN_ERR_RA_LOCAL_CATEGORY_START + category_size =>
434 {
435 ErrorCategory::RaLocal
436 }
437 c if c >= SVN_ERR_SVNDIFF_CATEGORY_START
438 && c < SVN_ERR_SVNDIFF_CATEGORY_START + category_size =>
439 {
440 ErrorCategory::Svndiff
441 }
442 c if c >= SVN_ERR_APMOD_CATEGORY_START
443 && c < SVN_ERR_APMOD_CATEGORY_START + category_size =>
444 {
445 ErrorCategory::ApacheMod
446 }
447 c if c >= SVN_ERR_CLIENT_CATEGORY_START
448 && c < SVN_ERR_CLIENT_CATEGORY_START + category_size =>
449 {
450 ErrorCategory::Client
451 }
452 c if c >= SVN_ERR_MISC_CATEGORY_START
453 && c < SVN_ERR_MISC_CATEGORY_START + category_size =>
454 {
455 ErrorCategory::Misc
456 }
457 c if c >= SVN_ERR_CL_CATEGORY_START
458 && c < SVN_ERR_CL_CATEGORY_START + category_size =>
459 {
460 ErrorCategory::CommandLine
461 }
462 c if c >= SVN_ERR_RA_SVN_CATEGORY_START
463 && c < SVN_ERR_RA_SVN_CATEGORY_START + category_size =>
464 {
465 ErrorCategory::RaSvn
466 }
467 c if c >= SVN_ERR_AUTHN_CATEGORY_START
468 && c < SVN_ERR_AUTHN_CATEGORY_START + category_size =>
469 {
470 ErrorCategory::Authentication
471 }
472 c if c >= SVN_ERR_AUTHZ_CATEGORY_START
473 && c < SVN_ERR_AUTHZ_CATEGORY_START + category_size =>
474 {
475 ErrorCategory::Authorization
476 }
477 c if c >= SVN_ERR_DIFF_CATEGORY_START
478 && c < SVN_ERR_DIFF_CATEGORY_START + category_size =>
479 {
480 ErrorCategory::Diff
481 }
482 c if c >= SVN_ERR_RA_SERF_CATEGORY_START
483 && c < SVN_ERR_RA_SERF_CATEGORY_START + category_size =>
484 {
485 ErrorCategory::RaSerf
486 }
487 c if c >= SVN_ERR_MALFUNC_CATEGORY_START
488 && c < SVN_ERR_MALFUNC_CATEGORY_START + category_size =>
489 {
490 ErrorCategory::Malfunction
491 }
492 c if c >= SVN_ERR_X509_CATEGORY_START
493 && c < SVN_ERR_X509_CATEGORY_START + category_size =>
494 {
495 ErrorCategory::X509
496 }
497 _ => ErrorCategory::Other,
498 }
499 }
500}
501
502pub fn symbolic_name(status: apr::Status) -> Option<&'static str> {
504 unsafe {
505 let name = subversion_sys::svn_error_symbolic_name(status as i32);
506 if name.is_null() {
507 None
508 } else {
509 Some(std::ffi::CStr::from_ptr(name).to_str().unwrap())
510 }
511 }
512}
513
514pub fn strerror(status: apr::Status) -> Option<&'static str> {
516 let mut buf = [0; 1024];
517 unsafe {
518 let name = subversion_sys::svn_strerror(status as i32, buf.as_mut_ptr(), buf.len());
519 if name.is_null() {
520 None
521 } else {
522 Some(std::ffi::CStr::from_ptr(name).to_str().unwrap())
523 }
524 }
525}
526
527impl Clone for Error<'static> {
528 fn clone(&self) -> Self {
529 unsafe {
530 Error {
531 ptr: subversion_sys::svn_error_dup(self.ptr),
532 owns_ptr: true,
533 _phantom: std::marker::PhantomData,
534 }
535 }
536 }
537}
538
539impl Drop for Error<'_> {
540 fn drop(&mut self) {
541 if self.owns_ptr && !self.ptr.is_null() {
543 unsafe { subversion_sys::svn_error_clear(self.ptr) }
544 }
545 }
546}
547
548impl std::fmt::Debug for Error<'_> {
549 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
550 writeln!(
551 f,
552 "{}:{}: {}",
553 self.file().unwrap_or("<unspecified>"),
554 self.line(),
555 self.message().unwrap_or("<no message>")
556 )?;
557 let mut n = self.child();
558 while let Some(err) = n {
559 writeln!(
560 f,
561 "{}:{}: {}",
562 err.file().unwrap_or("<unspecified>"),
563 err.line(),
564 err.message().unwrap_or("<no message>")
565 )?;
566 n = err.child();
567 }
568 Ok(())
569 }
570}
571
572impl std::fmt::Display for Error<'_> {
573 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
574 write!(f, "{}", self.full_message())
575 }
576}
577
578impl std::error::Error for Error<'_> {}
579
580impl From<std::io::Error> for Error<'static> {
581 fn from(err: std::io::Error) -> Self {
582 Error::new(apr::Status::from(err.kind()), None, &err.to_string())
583 }
584}
585
586impl From<Error<'_>> for std::io::Error {
587 fn from(err: Error) -> Self {
588 let errno = err.apr_err().raw_os_error();
589 errno.map_or(
590 std::io::Error::other(err.message().unwrap_or("Unknown error")),
591 std::io::Error::from_raw_os_error,
592 )
593 }
594}
595
596impl From<std::ffi::NulError> for Error<'static> {
597 fn from(err: std::ffi::NulError) -> Self {
598 Error::from_message(&format!("Null byte in string: {}", err))
599 }
600}
601
602impl From<std::str::Utf8Error> for Error<'static> {
603 fn from(err: std::str::Utf8Error) -> Self {
604 Error::from_message(&format!("UTF-8 encoding error: {}", err))
605 }
606}
607
608#[cfg(test)]
609mod tests {
610 use super::*;
611
612 #[test]
613 fn test_error_chain_formatting() {
614 let child_err = Error::from_message("Child error");
616 let parent_err = Error::new(apr::Status::from(1), Some(child_err), "Parent error");
617
618 let full_msg = parent_err.full_message();
619 assert!(full_msg.contains("Parent error"));
620 assert!(full_msg.contains("Child error"));
621 assert!(full_msg.contains(": ")); }
623
624 #[test]
625 fn test_single_error_message() {
626 let err = Error::from_message("Single error");
627 assert_eq!(err.message(), Some("Single error"));
628
629 let full_msg = err.full_message();
630 assert!(full_msg.contains("Single error"));
631 }
632
633 #[test]
634 fn test_error_display() {
635 let err = Error::from_message("Display test error");
636 let display_str = format!("{}", err);
637 assert!(display_str.contains("Display test error"));
638 }
639
640 #[test]
641 fn test_error_from_raw_null() {
642 Error::from_raw(std::ptr::null_mut()).unwrap();
643 }
644
645 #[test]
646 fn test_error_category() {
647 use subversion_sys::*;
648
649 let io_err_ptr = unsafe {
651 subversion_sys::svn_error_create(
652 SVN_ERR_IO_CATEGORY_START as i32,
653 std::ptr::null_mut(),
654 b"I/O error\0".as_ptr() as *const i8,
655 )
656 };
657 let io_err = Error {
658 ptr: io_err_ptr,
659 owns_ptr: true,
660 _phantom: std::marker::PhantomData,
661 };
662 assert_eq!(io_err.category(), ErrorCategory::Io);
663
664 let auth_err_ptr = unsafe {
665 subversion_sys::svn_error_create(
666 SVN_ERR_AUTHN_CATEGORY_START as i32,
667 std::ptr::null_mut(),
668 b"Auth error\0".as_ptr() as *const i8,
669 )
670 };
671 let auth_err = Error {
672 ptr: auth_err_ptr,
673 owns_ptr: true,
674 _phantom: std::marker::PhantomData,
675 };
676 assert_eq!(auth_err.category(), ErrorCategory::Authentication);
677
678 let authz_err_ptr = unsafe {
679 subversion_sys::svn_error_create(
680 SVN_ERR_AUTHZ_CATEGORY_START as i32,
681 std::ptr::null_mut(),
682 b"Authz error\0".as_ptr() as *const i8,
683 )
684 };
685 let authz_err = Error {
686 ptr: authz_err_ptr,
687 owns_ptr: true,
688 _phantom: std::marker::PhantomData,
689 };
690 assert_eq!(authz_err.category(), ErrorCategory::Authorization);
691
692 let wc_err_ptr = unsafe {
693 subversion_sys::svn_error_create(
694 SVN_ERR_WC_CATEGORY_START as i32,
695 std::ptr::null_mut(),
696 b"WC error\0".as_ptr() as *const i8,
697 )
698 };
699 let wc_err = Error {
700 ptr: wc_err_ptr,
701 owns_ptr: true,
702 _phantom: std::marker::PhantomData,
703 };
704 assert_eq!(wc_err.category(), ErrorCategory::WorkingCopy);
705
706 let repos_err_ptr = unsafe {
707 subversion_sys::svn_error_create(
708 SVN_ERR_REPOS_CATEGORY_START as i32,
709 std::ptr::null_mut(),
710 b"Repos error\0".as_ptr() as *const i8,
711 )
712 };
713 let repos_err = Error {
714 ptr: repos_err_ptr,
715 owns_ptr: true,
716 _phantom: std::marker::PhantomData,
717 };
718 assert_eq!(repos_err.category(), ErrorCategory::Repository);
719
720 let misc_err_ptr = unsafe {
721 subversion_sys::svn_error_create(
722 SVN_ERR_MISC_CATEGORY_START as i32,
723 std::ptr::null_mut(),
724 b"Misc error\0".as_ptr() as *const i8,
725 )
726 };
727 let misc_err = Error {
728 ptr: misc_err_ptr,
729 owns_ptr: true,
730 _phantom: std::marker::PhantomData,
731 };
732 assert_eq!(misc_err.category(), ErrorCategory::Misc);
733 }
734
735 #[test]
736 fn test_error_location_returns_value() {
737 use subversion_sys::*;
739
740 static TEST_FILE: &[u8] = b"test_file.c\0";
743
744 let err_ptr = unsafe {
745 let err = svn_error_create(
746 SVN_ERR_IO_CATEGORY_START as i32,
747 std::ptr::null_mut(),
748 b"Test error\0".as_ptr() as *const i8,
749 );
750 (*err).file = TEST_FILE.as_ptr() as *const i8;
753 (*err).line = 42;
754 err
755 };
756
757 let err = Error {
758 ptr: err_ptr,
759 owns_ptr: true,
760 _phantom: std::marker::PhantomData,
761 };
762
763 let location = err.location();
765 assert!(
766 location.is_some(),
767 "Error with file/line should have location"
768 );
769 let (file, line) = location.unwrap();
770 assert_eq!(file, "test_file.c", "File name should match");
771 assert_eq!(line, 42, "Line number should match");
772 }
773
774 #[test]
775 fn test_error_category_boundary_conditions() {
776 use subversion_sys::*;
779
780 let make_error = |code: u32| -> Error<'static> {
782 let err_ptr = unsafe {
783 svn_error_create(
784 code as i32,
785 std::ptr::null_mut(),
786 b"Test\0".as_ptr() as *const i8,
787 )
788 };
789 Error {
790 ptr: err_ptr,
791 owns_ptr: true,
792 _phantom: std::marker::PhantomData,
793 }
794 };
795
796 let category_size = SVN_ERR_CATEGORY_SIZE;
797
798 assert_eq!(
800 make_error(SVN_ERR_BAD_CATEGORY_START).category(),
801 ErrorCategory::BadInput,
802 "Start of BadInput range"
803 );
804 assert_eq!(
805 make_error(SVN_ERR_BAD_CATEGORY_START + category_size - 1).category(),
806 ErrorCategory::BadInput,
807 "End of BadInput range"
808 );
809 assert_eq!(
810 make_error(SVN_ERR_BAD_CATEGORY_START - 1).category(),
811 ErrorCategory::Other,
812 "Just before BadInput range"
813 );
814
815 assert_eq!(
817 make_error(SVN_ERR_XML_CATEGORY_START).category(),
818 ErrorCategory::Xml,
819 "Start of Xml range"
820 );
821 assert_eq!(
822 make_error(SVN_ERR_XML_CATEGORY_START + category_size - 1).category(),
823 ErrorCategory::Xml,
824 "End of Xml range"
825 );
826 assert_eq!(
827 make_error(SVN_ERR_XML_CATEGORY_START + category_size).category(),
828 ErrorCategory::Io,
829 "Just after Xml range should be Io"
830 );
831
832 assert_eq!(
834 make_error(SVN_ERR_IO_CATEGORY_START).category(),
835 ErrorCategory::Io,
836 "Start of Io range"
837 );
838 assert_eq!(
839 make_error(SVN_ERR_IO_CATEGORY_START + category_size - 1).category(),
840 ErrorCategory::Io,
841 "End of Io range"
842 );
843
844 assert_eq!(
846 make_error(SVN_ERR_STREAM_CATEGORY_START).category(),
847 ErrorCategory::Stream,
848 "Start of Stream range"
849 );
850 assert_eq!(
851 make_error(SVN_ERR_STREAM_CATEGORY_START + category_size - 1).category(),
852 ErrorCategory::Stream,
853 "End of Stream range"
854 );
855
856 assert_eq!(
858 make_error(SVN_ERR_NODE_CATEGORY_START).category(),
859 ErrorCategory::Node,
860 "Start of Node range"
861 );
862 assert_eq!(
863 make_error(SVN_ERR_NODE_CATEGORY_START + category_size - 1).category(),
864 ErrorCategory::Node,
865 "End of Node range"
866 );
867
868 assert_eq!(
870 make_error(SVN_ERR_ENTRY_CATEGORY_START).category(),
871 ErrorCategory::Entry,
872 "Start of Entry range"
873 );
874 assert_eq!(
875 make_error(SVN_ERR_ENTRY_CATEGORY_START + category_size - 1).category(),
876 ErrorCategory::Entry,
877 "End of Entry range"
878 );
879
880 assert_eq!(
882 make_error(SVN_ERR_WC_CATEGORY_START).category(),
883 ErrorCategory::WorkingCopy,
884 "Start of WorkingCopy range"
885 );
886 assert_eq!(
887 make_error(SVN_ERR_WC_CATEGORY_START + category_size - 1).category(),
888 ErrorCategory::WorkingCopy,
889 "End of WorkingCopy range"
890 );
891
892 assert_eq!(
894 make_error(SVN_ERR_FS_CATEGORY_START).category(),
895 ErrorCategory::Filesystem,
896 "Start of Filesystem range"
897 );
898 assert_eq!(
899 make_error(SVN_ERR_FS_CATEGORY_START + category_size - 1).category(),
900 ErrorCategory::Filesystem,
901 "End of Filesystem range"
902 );
903
904 assert_eq!(
906 make_error(SVN_ERR_REPOS_CATEGORY_START).category(),
907 ErrorCategory::Repository,
908 "Start of Repository range"
909 );
910 assert_eq!(
911 make_error(SVN_ERR_REPOS_CATEGORY_START + category_size - 1).category(),
912 ErrorCategory::Repository,
913 "End of Repository range"
914 );
915
916 assert_eq!(
918 make_error(SVN_ERR_RA_CATEGORY_START).category(),
919 ErrorCategory::RepositoryAccess,
920 "Start of RepositoryAccess range"
921 );
922 assert_eq!(
923 make_error(SVN_ERR_RA_CATEGORY_START + category_size - 1).category(),
924 ErrorCategory::RepositoryAccess,
925 "End of RepositoryAccess range"
926 );
927
928 assert_eq!(
930 make_error(SVN_ERR_RA_DAV_CATEGORY_START).category(),
931 ErrorCategory::RaDav,
932 "Start of RaDav range"
933 );
934 assert_eq!(
935 make_error(SVN_ERR_RA_DAV_CATEGORY_START + category_size - 1).category(),
936 ErrorCategory::RaDav,
937 "End of RaDav range"
938 );
939
940 assert_eq!(
942 make_error(SVN_ERR_RA_LOCAL_CATEGORY_START).category(),
943 ErrorCategory::RaLocal,
944 "Start of RaLocal range"
945 );
946 assert_eq!(
947 make_error(SVN_ERR_RA_LOCAL_CATEGORY_START + category_size - 1).category(),
948 ErrorCategory::RaLocal,
949 "End of RaLocal range"
950 );
951
952 assert_eq!(
954 make_error(SVN_ERR_SVNDIFF_CATEGORY_START).category(),
955 ErrorCategory::Svndiff,
956 "Start of Svndiff range"
957 );
958 assert_eq!(
959 make_error(SVN_ERR_SVNDIFF_CATEGORY_START + category_size - 1).category(),
960 ErrorCategory::Svndiff,
961 "End of Svndiff range"
962 );
963
964 assert_eq!(
966 make_error(SVN_ERR_APMOD_CATEGORY_START).category(),
967 ErrorCategory::ApacheMod,
968 "Start of ApacheMod range"
969 );
970 assert_eq!(
971 make_error(SVN_ERR_APMOD_CATEGORY_START + category_size - 1).category(),
972 ErrorCategory::ApacheMod,
973 "End of ApacheMod range"
974 );
975
976 assert_eq!(
978 make_error(SVN_ERR_CLIENT_CATEGORY_START).category(),
979 ErrorCategory::Client,
980 "Start of Client range"
981 );
982 assert_eq!(
983 make_error(SVN_ERR_CLIENT_CATEGORY_START + category_size - 1).category(),
984 ErrorCategory::Client,
985 "End of Client range"
986 );
987
988 assert_eq!(
990 make_error(SVN_ERR_MISC_CATEGORY_START).category(),
991 ErrorCategory::Misc,
992 "Start of Misc range"
993 );
994 assert_eq!(
995 make_error(SVN_ERR_MISC_CATEGORY_START + category_size - 1).category(),
996 ErrorCategory::Misc,
997 "End of Misc range"
998 );
999
1000 assert_eq!(
1002 make_error(SVN_ERR_CL_CATEGORY_START).category(),
1003 ErrorCategory::CommandLine,
1004 "Start of CommandLine range"
1005 );
1006 assert_eq!(
1007 make_error(SVN_ERR_CL_CATEGORY_START + category_size - 1).category(),
1008 ErrorCategory::CommandLine,
1009 "End of CommandLine range"
1010 );
1011
1012 assert_eq!(
1014 make_error(SVN_ERR_RA_SVN_CATEGORY_START).category(),
1015 ErrorCategory::RaSvn,
1016 "Start of RaSvn range"
1017 );
1018 assert_eq!(
1019 make_error(SVN_ERR_RA_SVN_CATEGORY_START + category_size - 1).category(),
1020 ErrorCategory::RaSvn,
1021 "End of RaSvn range"
1022 );
1023
1024 assert_eq!(
1026 make_error(SVN_ERR_AUTHN_CATEGORY_START).category(),
1027 ErrorCategory::Authentication,
1028 "Start of Authentication range"
1029 );
1030 assert_eq!(
1031 make_error(SVN_ERR_AUTHN_CATEGORY_START + category_size - 1).category(),
1032 ErrorCategory::Authentication,
1033 "End of Authentication range"
1034 );
1035
1036 assert_eq!(
1038 make_error(SVN_ERR_AUTHZ_CATEGORY_START).category(),
1039 ErrorCategory::Authorization,
1040 "Start of Authorization range"
1041 );
1042 assert_eq!(
1043 make_error(SVN_ERR_AUTHZ_CATEGORY_START + category_size - 1).category(),
1044 ErrorCategory::Authorization,
1045 "End of Authorization range"
1046 );
1047
1048 assert_eq!(
1050 make_error(SVN_ERR_DIFF_CATEGORY_START).category(),
1051 ErrorCategory::Diff,
1052 "Start of Diff range"
1053 );
1054 assert_eq!(
1055 make_error(SVN_ERR_DIFF_CATEGORY_START + category_size - 1).category(),
1056 ErrorCategory::Diff,
1057 "End of Diff range"
1058 );
1059
1060 assert_eq!(
1062 make_error(SVN_ERR_RA_SERF_CATEGORY_START).category(),
1063 ErrorCategory::RaSerf,
1064 "Start of RaSerf range"
1065 );
1066 assert_eq!(
1067 make_error(SVN_ERR_RA_SERF_CATEGORY_START + category_size - 1).category(),
1068 ErrorCategory::RaSerf,
1069 "End of RaSerf range"
1070 );
1071
1072 assert_eq!(
1074 make_error(SVN_ERR_MALFUNC_CATEGORY_START).category(),
1075 ErrorCategory::Malfunction,
1076 "Start of Malfunction range"
1077 );
1078 assert_eq!(
1079 make_error(SVN_ERR_MALFUNC_CATEGORY_START + category_size - 1).category(),
1080 ErrorCategory::Malfunction,
1081 "End of Malfunction range"
1082 );
1083
1084 assert_eq!(
1086 make_error(SVN_ERR_X509_CATEGORY_START).category(),
1087 ErrorCategory::X509,
1088 "Start of X509 range"
1089 );
1090 assert_eq!(
1091 make_error(SVN_ERR_X509_CATEGORY_START + category_size - 1).category(),
1092 ErrorCategory::X509,
1093 "End of X509 range"
1094 );
1095 assert_eq!(
1096 make_error(SVN_ERR_X509_CATEGORY_START + category_size).category(),
1097 ErrorCategory::Other,
1098 "Just after X509 range"
1099 );
1100
1101 assert_eq!(
1103 make_error(0).category(),
1104 ErrorCategory::Other,
1105 "Zero should be Other"
1106 );
1107 assert_eq!(
1108 make_error(1000).category(),
1109 ErrorCategory::Other,
1110 "Small values should be Other"
1111 );
1112 assert_eq!(
1113 make_error(300000).category(),
1114 ErrorCategory::Other,
1115 "Values beyond all categories should be Other"
1116 );
1117 }
1118
1119 #[test]
1120 fn test_error_best_message_returns_actual_message() {
1121 use subversion_sys::*;
1123
1124 let err_ptr = unsafe {
1125 svn_error_create(
1126 SVN_ERR_IO_CATEGORY_START as i32,
1127 std::ptr::null_mut(),
1128 b"Specific error message\0".as_ptr() as *const i8,
1129 )
1130 };
1131 let err = Error {
1132 ptr: err_ptr,
1133 owns_ptr: true,
1134 _phantom: std::marker::PhantomData,
1135 };
1136
1137 let msg = err.best_message();
1138 assert!(!msg.is_empty(), "best_message should not be empty");
1139 assert_ne!(msg, "xyzzy", "best_message should not be 'xyzzy'");
1140 assert_eq!(
1141 msg, "Specific error message",
1142 "best_message should return exact message, got '{}'",
1143 msg
1144 );
1145 }
1146
1147 #[test]
1148 fn test_error_child_returns_none_when_no_child() {
1149 use subversion_sys::*;
1151
1152 let err_ptr = unsafe {
1153 svn_error_create(
1154 SVN_ERR_IO_CATEGORY_START as i32,
1155 std::ptr::null_mut(),
1156 b"Error without child\0".as_ptr() as *const i8,
1157 )
1158 };
1159
1160 let err = Error {
1161 ptr: err_ptr,
1162 owns_ptr: true,
1163 _phantom: std::marker::PhantomData,
1164 };
1165
1166 let child = err.child();
1168 assert!(
1169 child.is_none(),
1170 "child() should return None when no child exists"
1171 );
1172 }
1173
1174 #[test]
1175 fn test_error_find_cause_returns_none_for_non_matching_status() {
1176 use subversion_sys::*;
1178
1179 let err_ptr = unsafe {
1181 svn_error_create(
1182 SVN_ERR_IO_CATEGORY_START as i32,
1183 std::ptr::null_mut(),
1184 b"Test error\0".as_ptr() as *const i8,
1185 )
1186 };
1187 let err = Error {
1188 ptr: err_ptr,
1189 owns_ptr: true,
1190 _phantom: std::marker::PhantomData,
1191 };
1192
1193 let different_err_ptr = unsafe {
1195 svn_error_create(
1196 (SVN_ERR_CLIENT_CATEGORY_START + 100) as i32,
1197 std::ptr::null_mut(),
1198 b"Different error\0".as_ptr() as *const i8,
1199 )
1200 };
1201 let different_err = Error {
1202 ptr: different_err_ptr,
1203 owns_ptr: true,
1204 _phantom: std::marker::PhantomData,
1205 };
1206 let different_status = different_err.apr_err();
1207 std::mem::forget(different_err);
1209
1210 let found = err.find_cause(different_status);
1212
1213 assert!(
1214 found.is_none(),
1215 "find_cause() should return None when status doesn't match any error in chain"
1216 );
1217
1218 unsafe {
1220 subversion_sys::svn_error_clear(different_err_ptr);
1221 }
1222 }
1223
1224 #[test]
1225 fn test_error_child_returns_actual_child() {
1226 use subversion_sys::*;
1228
1229 let child_err_ptr = unsafe {
1230 svn_error_create(
1231 SVN_ERR_IO_CATEGORY_START as i32,
1232 std::ptr::null_mut(),
1233 b"Child error\0".as_ptr() as *const i8,
1234 )
1235 };
1236
1237 let parent_err_ptr = unsafe {
1238 svn_error_create(
1239 SVN_ERR_CLIENT_CATEGORY_START as i32,
1240 child_err_ptr,
1241 b"Parent error\0".as_ptr() as *const i8,
1242 )
1243 };
1244
1245 let parent_err = Error {
1246 ptr: parent_err_ptr,
1247 owns_ptr: true,
1248 _phantom: std::marker::PhantomData,
1249 };
1250
1251 let child = parent_err.child();
1253 assert!(
1254 child.is_some(),
1255 "child() should return Some when child exists"
1256 );
1257
1258 let child_err = child.unwrap();
1259 assert_eq!(
1260 child_err.category(),
1261 ErrorCategory::Io,
1262 "Child error should have Io category"
1263 );
1264 assert!(
1265 child_err.message().unwrap().contains("Child error"),
1266 "Child error should have correct message"
1267 );
1268 }
1269
1270 #[test]
1271 fn test_error_find_cause_returns_matching_error() {
1272 let child_err = Error::from_message("Child error");
1275 let parent_status = apr::Status::from(12345);
1276 let parent_err = Error::new(parent_status, Some(child_err), "Parent error");
1277
1278 let found = parent_err.find_cause(parent_status);
1280 assert!(
1281 found.is_some(),
1282 "find_cause() should find error with matching status"
1283 );
1284
1285 let found_err = found.unwrap();
1286 assert_eq!(
1287 found_err.apr_err(),
1288 parent_status,
1289 "Found error should have correct status"
1290 );
1291 }
1292
1293 #[test]
1294 fn test_error_as_ptr_returns_actual_pointer() {
1295 use subversion_sys::*;
1297
1298 let err_ptr = unsafe {
1299 svn_error_create(
1300 SVN_ERR_IO_CATEGORY_START as i32,
1301 std::ptr::null_mut(),
1302 b"Test\0".as_ptr() as *const i8,
1303 )
1304 };
1305
1306 let err = Error {
1307 ptr: err_ptr,
1308 owns_ptr: true,
1309 _phantom: std::marker::PhantomData,
1310 };
1311
1312 let ptr = err.as_ptr();
1313 assert!(!ptr.is_null(), "as_ptr() should return non-null pointer");
1314 assert_eq!(
1315 ptr, err_ptr,
1316 "as_ptr() should return the actual error pointer"
1317 );
1318 }
1319
1320 #[test]
1321 fn test_error_as_mut_ptr_returns_actual_pointer() {
1322 use subversion_sys::*;
1324
1325 let err_ptr = unsafe {
1326 svn_error_create(
1327 SVN_ERR_IO_CATEGORY_START as i32,
1328 std::ptr::null_mut(),
1329 b"Test\0".as_ptr() as *const i8,
1330 )
1331 };
1332
1333 let mut err = Error {
1334 ptr: err_ptr,
1335 owns_ptr: true,
1336 _phantom: std::marker::PhantomData,
1337 };
1338
1339 let ptr = err.as_mut_ptr();
1340 assert!(
1341 !ptr.is_null(),
1342 "as_mut_ptr() should return non-null pointer"
1343 );
1344 assert_eq!(
1345 ptr, err_ptr,
1346 "as_mut_ptr() should return the actual error pointer"
1347 );
1348 }
1349
1350 #[test]
1351 fn test_symbolic_name_returns_actual_names() {
1352 let err = Error::from_message("Test error");
1357 let status = err.apr_err();
1358
1359 let name = symbolic_name(status);
1361
1362 if let Some(name_str) = name {
1366 assert!(
1367 !name_str.is_empty(),
1368 "Symbolic name should not be empty if returned"
1369 );
1370 assert_ne!(name_str, "xyzzy", "Symbolic name should not be 'xyzzy'");
1371 assert!(
1372 name_str.starts_with("SVN_"),
1373 "Symbolic name should start with SVN_, got: {}",
1374 name_str
1375 );
1376 }
1377
1378 let _ = symbolic_name(0.into());
1380 let _ = symbolic_name(999999.into());
1381 }
1382
1383 #[test]
1384 fn test_strerror_returns_actual_error_strings() {
1385 let err = Error::from_message("Test error");
1390 let status = err.apr_err();
1391
1392 let err_str = strerror(status);
1394
1395 assert!(
1398 err_str.is_some(),
1399 "strerror() must return Some for a valid SVN error code, got None"
1400 );
1401
1402 let err_msg = err_str.unwrap();
1403 assert!(
1404 !err_msg.is_empty(),
1405 "Error string should not be empty if returned"
1406 );
1407 assert_ne!(err_msg, "xyzzy", "Error string should not be 'xyzzy'");
1408 assert!(
1409 err_msg.len() > 2,
1410 "Error string should be substantive, got: {}",
1411 err_msg
1412 );
1413
1414 let _ = strerror(0.into());
1417 let _ = strerror(999999.into());
1418 }
1419
1420 #[test]
1421 fn test_error_category_off_by_one_and_midrange() {
1422 use subversion_sys::*;
1425
1426 let make_error = |code: u32| -> Error<'static> {
1427 let err_ptr = unsafe {
1428 svn_error_create(
1429 code as i32,
1430 std::ptr::null_mut(),
1431 b"Test\0".as_ptr() as *const i8,
1432 )
1433 };
1434 Error {
1435 ptr: err_ptr,
1436 owns_ptr: true,
1437 _phantom: std::marker::PhantomData,
1438 }
1439 };
1440
1441 let category_size = SVN_ERR_CATEGORY_SIZE;
1442
1443 let categories = vec![
1445 (
1446 SVN_ERR_BAD_CATEGORY_START,
1447 ErrorCategory::BadInput,
1448 "BadInput",
1449 ),
1450 (SVN_ERR_XML_CATEGORY_START, ErrorCategory::Xml, "Xml"),
1451 (SVN_ERR_IO_CATEGORY_START, ErrorCategory::Io, "Io"),
1452 (
1453 SVN_ERR_STREAM_CATEGORY_START,
1454 ErrorCategory::Stream,
1455 "Stream",
1456 ),
1457 (SVN_ERR_NODE_CATEGORY_START, ErrorCategory::Node, "Node"),
1458 (SVN_ERR_ENTRY_CATEGORY_START, ErrorCategory::Entry, "Entry"),
1459 (
1460 SVN_ERR_WC_CATEGORY_START,
1461 ErrorCategory::WorkingCopy,
1462 "WorkingCopy",
1463 ),
1464 (
1465 SVN_ERR_FS_CATEGORY_START,
1466 ErrorCategory::Filesystem,
1467 "Filesystem",
1468 ),
1469 (
1470 SVN_ERR_REPOS_CATEGORY_START,
1471 ErrorCategory::Repository,
1472 "Repository",
1473 ),
1474 (
1475 SVN_ERR_RA_CATEGORY_START,
1476 ErrorCategory::RepositoryAccess,
1477 "RepositoryAccess",
1478 ),
1479 (SVN_ERR_RA_DAV_CATEGORY_START, ErrorCategory::RaDav, "RaDav"),
1480 (
1481 SVN_ERR_RA_LOCAL_CATEGORY_START,
1482 ErrorCategory::RaLocal,
1483 "RaLocal",
1484 ),
1485 (
1486 SVN_ERR_SVNDIFF_CATEGORY_START,
1487 ErrorCategory::Svndiff,
1488 "Svndiff",
1489 ),
1490 (
1491 SVN_ERR_APMOD_CATEGORY_START,
1492 ErrorCategory::ApacheMod,
1493 "ApacheMod",
1494 ),
1495 (
1496 SVN_ERR_CLIENT_CATEGORY_START,
1497 ErrorCategory::Client,
1498 "Client",
1499 ),
1500 (SVN_ERR_MISC_CATEGORY_START, ErrorCategory::Misc, "Misc"),
1501 (
1502 SVN_ERR_CL_CATEGORY_START,
1503 ErrorCategory::CommandLine,
1504 "CommandLine",
1505 ),
1506 (SVN_ERR_RA_SVN_CATEGORY_START, ErrorCategory::RaSvn, "RaSvn"),
1507 (
1508 SVN_ERR_AUTHN_CATEGORY_START,
1509 ErrorCategory::Authentication,
1510 "Authentication",
1511 ),
1512 (
1513 SVN_ERR_AUTHZ_CATEGORY_START,
1514 ErrorCategory::Authorization,
1515 "Authorization",
1516 ),
1517 (SVN_ERR_DIFF_CATEGORY_START, ErrorCategory::Diff, "Diff"),
1518 (
1519 SVN_ERR_RA_SERF_CATEGORY_START,
1520 ErrorCategory::RaSerf,
1521 "RaSerf",
1522 ),
1523 (
1524 SVN_ERR_MALFUNC_CATEGORY_START,
1525 ErrorCategory::Malfunction,
1526 "Malfunction",
1527 ),
1528 (SVN_ERR_X509_CATEGORY_START, ErrorCategory::X509, "X509"),
1529 ];
1530
1531 for (start, expected_cat, name) in categories {
1532 assert_eq!(
1534 make_error(start).category(),
1535 expected_cat,
1536 "{}: START should be in category",
1537 name
1538 );
1539
1540 assert_eq!(
1542 make_error(start + 1).category(),
1543 expected_cat,
1544 "{}: START+1 should be in category",
1545 name
1546 );
1547
1548 let mid = start + category_size / 2;
1550 assert_eq!(
1551 make_error(mid).category(),
1552 expected_cat,
1553 "{}: MID should be in category",
1554 name
1555 );
1556
1557 assert_eq!(
1559 make_error(start + category_size - 2).category(),
1560 expected_cat,
1561 "{}: END-2 should be in category",
1562 name
1563 );
1564
1565 assert_eq!(
1567 make_error(start + category_size - 1).category(),
1568 expected_cat,
1569 "{}: END-1 (last valid) should be in category",
1570 name
1571 );
1572
1573 assert_ne!(
1575 make_error(start + category_size).category(),
1576 expected_cat,
1577 "{}: END should NOT be in category",
1578 name
1579 );
1580
1581 if start > 1000 {
1584 assert_ne!(
1585 make_error(start - 1).category(),
1586 expected_cat,
1587 "{}: START-1 should NOT be in category",
1588 name
1589 );
1590 }
1591 }
1592
1593 assert_eq!(
1595 make_error(100).category(),
1596 ErrorCategory::Other,
1597 "Value below all categories should be Other"
1598 );
1599
1600 assert_eq!(
1602 make_error(500000).category(),
1603 ErrorCategory::Other,
1604 "Value above all categories should be Other"
1605 );
1606
1607 let between = SVN_ERR_BAD_CATEGORY_START + category_size;
1609 let between_cat = make_error(between).category();
1610 assert_ne!(
1611 between_cat,
1612 ErrorCategory::BadInput,
1613 "Value just after BadInput should not be BadInput"
1614 );
1615 }
1616
1617 #[test]
1618 fn test_raw_apr_err_preserves_svn_error_codes() {
1619 let cancelled_code = subversion_sys::svn_errno_t_SVN_ERR_CANCELLED as i32;
1623 let err = Error::with_raw_status(cancelled_code, None, "cancelled");
1624
1625 assert_eq!(err.raw_apr_err(), cancelled_code);
1626 assert_eq!(err.apr_err(), apr::Status::General);
1628 }
1629
1630 #[test]
1631 fn test_with_raw_status_creates_distinguishable_errors() {
1632 let cancelled_code = subversion_sys::svn_errno_t_SVN_ERR_CANCELLED as i32;
1633 let fs_not_found_code = subversion_sys::svn_errno_t_SVN_ERR_FS_NOT_FOUND as i32;
1634
1635 let err1 = Error::with_raw_status(cancelled_code, None, "cancelled");
1636 let err2 = Error::with_raw_status(fs_not_found_code, None, "not found");
1637
1638 assert_eq!(err1.apr_err(), err2.apr_err());
1640 assert_ne!(err1.raw_apr_err(), err2.raw_apr_err());
1642 assert_eq!(err1.raw_apr_err(), cancelled_code);
1643 assert_eq!(err2.raw_apr_err(), fs_not_found_code);
1644 }
1645
1646 #[test]
1647 fn test_with_raw_status_message_and_child() {
1648 let child = Error::from_message("child error");
1649 let parent = Error::with_raw_status(200015, Some(child), "parent error");
1650
1651 assert_eq!(parent.message(), Some("parent error"));
1652 let full = parent.full_message();
1653 assert!(full.contains("parent error"));
1654 assert!(full.contains("child error"));
1655 }
1656}