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 into_static(self) -> Error<'static> {
321 let ptr = self.ptr;
322 let owns = self.owns_ptr;
323 std::mem::forget(self);
324 Error {
325 ptr,
326 owns_ptr: owns,
327 _phantom: std::marker::PhantomData,
328 }
329 }
330
331 pub fn best_message(&self) -> String {
333 let mut buf = [0; 1024];
334 unsafe {
335 let ret = subversion_sys::svn_err_best_message(self.ptr, buf.as_mut_ptr(), buf.len());
336 std::ffi::CStr::from_ptr(ret).to_string_lossy().into_owned()
337 }
338 }
339
340 pub fn full_message(&self) -> String {
342 let mut messages = Vec::new();
343 let mut current = self.ptr;
344
345 unsafe {
346 while !current.is_null() {
347 let msg = (*current).message;
348 if !msg.is_null() {
349 let msg_str = std::ffi::CStr::from_ptr(msg).to_string_lossy();
350 if !msg_str.is_empty() {
351 messages.push(msg_str.into_owned());
352 }
353 }
354 current = (*current).child;
355 }
356 }
357
358 if messages.is_empty() {
359 self.best_message()
360 } else {
361 messages.join(": ")
362 }
363 }
364
365 pub fn category(&self) -> ErrorCategory {
389 use subversion_sys::*;
390 let code = unsafe { (*self.ptr).apr_err as u32 };
392 let category_size = SVN_ERR_CATEGORY_SIZE;
393
394 match code {
395 c if c >= SVN_ERR_BAD_CATEGORY_START
396 && c < SVN_ERR_BAD_CATEGORY_START + category_size =>
397 {
398 ErrorCategory::BadInput
399 }
400 c if c >= SVN_ERR_XML_CATEGORY_START
401 && c < SVN_ERR_XML_CATEGORY_START + category_size =>
402 {
403 ErrorCategory::Xml
404 }
405 c if c >= SVN_ERR_IO_CATEGORY_START
406 && c < SVN_ERR_IO_CATEGORY_START + category_size =>
407 {
408 ErrorCategory::Io
409 }
410 c if c >= SVN_ERR_STREAM_CATEGORY_START
411 && c < SVN_ERR_STREAM_CATEGORY_START + category_size =>
412 {
413 ErrorCategory::Stream
414 }
415 c if c >= SVN_ERR_NODE_CATEGORY_START
416 && c < SVN_ERR_NODE_CATEGORY_START + category_size =>
417 {
418 ErrorCategory::Node
419 }
420 c if c >= SVN_ERR_ENTRY_CATEGORY_START
421 && c < SVN_ERR_ENTRY_CATEGORY_START + category_size =>
422 {
423 ErrorCategory::Entry
424 }
425 c if c >= SVN_ERR_WC_CATEGORY_START
426 && c < SVN_ERR_WC_CATEGORY_START + category_size =>
427 {
428 ErrorCategory::WorkingCopy
429 }
430 c if c >= SVN_ERR_FS_CATEGORY_START
431 && c < SVN_ERR_FS_CATEGORY_START + category_size =>
432 {
433 ErrorCategory::Filesystem
434 }
435 c if c >= SVN_ERR_REPOS_CATEGORY_START
436 && c < SVN_ERR_REPOS_CATEGORY_START + category_size =>
437 {
438 ErrorCategory::Repository
439 }
440 c if c >= SVN_ERR_RA_CATEGORY_START
441 && c < SVN_ERR_RA_CATEGORY_START + category_size =>
442 {
443 ErrorCategory::RepositoryAccess
444 }
445 c if c >= SVN_ERR_RA_DAV_CATEGORY_START
446 && c < SVN_ERR_RA_DAV_CATEGORY_START + category_size =>
447 {
448 ErrorCategory::RaDav
449 }
450 c if c >= SVN_ERR_RA_LOCAL_CATEGORY_START
451 && c < SVN_ERR_RA_LOCAL_CATEGORY_START + category_size =>
452 {
453 ErrorCategory::RaLocal
454 }
455 c if c >= SVN_ERR_SVNDIFF_CATEGORY_START
456 && c < SVN_ERR_SVNDIFF_CATEGORY_START + category_size =>
457 {
458 ErrorCategory::Svndiff
459 }
460 c if c >= SVN_ERR_APMOD_CATEGORY_START
461 && c < SVN_ERR_APMOD_CATEGORY_START + category_size =>
462 {
463 ErrorCategory::ApacheMod
464 }
465 c if c >= SVN_ERR_CLIENT_CATEGORY_START
466 && c < SVN_ERR_CLIENT_CATEGORY_START + category_size =>
467 {
468 ErrorCategory::Client
469 }
470 c if c >= SVN_ERR_MISC_CATEGORY_START
471 && c < SVN_ERR_MISC_CATEGORY_START + category_size =>
472 {
473 ErrorCategory::Misc
474 }
475 c if c >= SVN_ERR_CL_CATEGORY_START
476 && c < SVN_ERR_CL_CATEGORY_START + category_size =>
477 {
478 ErrorCategory::CommandLine
479 }
480 c if c >= SVN_ERR_RA_SVN_CATEGORY_START
481 && c < SVN_ERR_RA_SVN_CATEGORY_START + category_size =>
482 {
483 ErrorCategory::RaSvn
484 }
485 c if c >= SVN_ERR_AUTHN_CATEGORY_START
486 && c < SVN_ERR_AUTHN_CATEGORY_START + category_size =>
487 {
488 ErrorCategory::Authentication
489 }
490 c if c >= SVN_ERR_AUTHZ_CATEGORY_START
491 && c < SVN_ERR_AUTHZ_CATEGORY_START + category_size =>
492 {
493 ErrorCategory::Authorization
494 }
495 c if c >= SVN_ERR_DIFF_CATEGORY_START
496 && c < SVN_ERR_DIFF_CATEGORY_START + category_size =>
497 {
498 ErrorCategory::Diff
499 }
500 c if c >= SVN_ERR_RA_SERF_CATEGORY_START
501 && c < SVN_ERR_RA_SERF_CATEGORY_START + category_size =>
502 {
503 ErrorCategory::RaSerf
504 }
505 c if c >= SVN_ERR_MALFUNC_CATEGORY_START
506 && c < SVN_ERR_MALFUNC_CATEGORY_START + category_size =>
507 {
508 ErrorCategory::Malfunction
509 }
510 c if c >= SVN_ERR_X509_CATEGORY_START
511 && c < SVN_ERR_X509_CATEGORY_START + category_size =>
512 {
513 ErrorCategory::X509
514 }
515 _ => ErrorCategory::Other,
516 }
517 }
518}
519
520pub fn symbolic_name(status: apr::Status) -> Option<&'static str> {
522 unsafe {
523 let name = subversion_sys::svn_error_symbolic_name(status as i32);
524 if name.is_null() {
525 None
526 } else {
527 Some(std::ffi::CStr::from_ptr(name).to_str().unwrap())
528 }
529 }
530}
531
532pub fn strerror(status: apr::Status) -> Option<&'static str> {
534 let mut buf = [0; 1024];
535 unsafe {
536 let name = subversion_sys::svn_strerror(status as i32, buf.as_mut_ptr(), buf.len());
537 if name.is_null() {
538 None
539 } else {
540 Some(std::ffi::CStr::from_ptr(name).to_str().unwrap())
541 }
542 }
543}
544
545impl Clone for Error<'static> {
546 fn clone(&self) -> Self {
547 unsafe {
548 Error {
549 ptr: subversion_sys::svn_error_dup(self.ptr),
550 owns_ptr: true,
551 _phantom: std::marker::PhantomData,
552 }
553 }
554 }
555}
556
557impl Drop for Error<'_> {
558 fn drop(&mut self) {
559 if self.owns_ptr && !self.ptr.is_null() {
561 unsafe { subversion_sys::svn_error_clear(self.ptr) }
562 }
563 }
564}
565
566impl std::fmt::Debug for Error<'_> {
567 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
568 writeln!(
569 f,
570 "{}:{}: {}",
571 self.file().unwrap_or("<unspecified>"),
572 self.line(),
573 self.message().unwrap_or("<no message>")
574 )?;
575 let mut n = self.child();
576 while let Some(err) = n {
577 writeln!(
578 f,
579 "{}:{}: {}",
580 err.file().unwrap_or("<unspecified>"),
581 err.line(),
582 err.message().unwrap_or("<no message>")
583 )?;
584 n = err.child();
585 }
586 Ok(())
587 }
588}
589
590impl std::fmt::Display for Error<'_> {
591 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
592 write!(f, "{}", self.full_message())
593 }
594}
595
596impl std::error::Error for Error<'_> {}
597
598impl From<std::io::Error> for Error<'static> {
599 fn from(err: std::io::Error) -> Self {
600 Error::new(apr::Status::from(err.kind()), None, &err.to_string())
601 }
602}
603
604impl From<Error<'_>> for std::io::Error {
605 fn from(err: Error) -> Self {
606 let errno = err.apr_err().raw_os_error();
607 errno.map_or(
608 std::io::Error::other(err.message().unwrap_or("Unknown error")),
609 std::io::Error::from_raw_os_error,
610 )
611 }
612}
613
614impl From<std::ffi::NulError> for Error<'static> {
615 fn from(err: std::ffi::NulError) -> Self {
616 Error::from_message(&format!("Null byte in string: {}", err))
617 }
618}
619
620impl From<std::str::Utf8Error> for Error<'static> {
621 fn from(err: std::str::Utf8Error) -> Self {
622 Error::from_message(&format!("UTF-8 encoding error: {}", err))
623 }
624}
625
626#[cfg(test)]
627mod tests {
628 use super::*;
629
630 #[test]
631 fn test_error_chain_formatting() {
632 let child_err = Error::from_message("Child error");
634 let parent_err = Error::new(apr::Status::from(1), Some(child_err), "Parent error");
635
636 let full_msg = parent_err.full_message();
637 assert!(full_msg.contains("Parent error"));
638 assert!(full_msg.contains("Child error"));
639 assert!(full_msg.contains(": ")); }
641
642 #[test]
643 fn test_single_error_message() {
644 let err = Error::from_message("Single error");
645 assert_eq!(err.message(), Some("Single error"));
646
647 let full_msg = err.full_message();
648 assert!(full_msg.contains("Single error"));
649 }
650
651 #[test]
652 fn test_error_display() {
653 let err = Error::from_message("Display test error");
654 let display_str = format!("{}", err);
655 assert!(display_str.contains("Display test error"));
656 }
657
658 #[test]
659 fn test_error_from_raw_null() {
660 Error::from_raw(std::ptr::null_mut()).unwrap();
661 }
662
663 #[test]
664 fn test_error_category() {
665 use subversion_sys::*;
666
667 let io_err_ptr = unsafe {
669 subversion_sys::svn_error_create(
670 SVN_ERR_IO_CATEGORY_START as i32,
671 std::ptr::null_mut(),
672 b"I/O error\0".as_ptr() as *const i8,
673 )
674 };
675 let io_err = Error {
676 ptr: io_err_ptr,
677 owns_ptr: true,
678 _phantom: std::marker::PhantomData,
679 };
680 assert_eq!(io_err.category(), ErrorCategory::Io);
681
682 let auth_err_ptr = unsafe {
683 subversion_sys::svn_error_create(
684 SVN_ERR_AUTHN_CATEGORY_START as i32,
685 std::ptr::null_mut(),
686 b"Auth error\0".as_ptr() as *const i8,
687 )
688 };
689 let auth_err = Error {
690 ptr: auth_err_ptr,
691 owns_ptr: true,
692 _phantom: std::marker::PhantomData,
693 };
694 assert_eq!(auth_err.category(), ErrorCategory::Authentication);
695
696 let authz_err_ptr = unsafe {
697 subversion_sys::svn_error_create(
698 SVN_ERR_AUTHZ_CATEGORY_START as i32,
699 std::ptr::null_mut(),
700 b"Authz error\0".as_ptr() as *const i8,
701 )
702 };
703 let authz_err = Error {
704 ptr: authz_err_ptr,
705 owns_ptr: true,
706 _phantom: std::marker::PhantomData,
707 };
708 assert_eq!(authz_err.category(), ErrorCategory::Authorization);
709
710 let wc_err_ptr = unsafe {
711 subversion_sys::svn_error_create(
712 SVN_ERR_WC_CATEGORY_START as i32,
713 std::ptr::null_mut(),
714 b"WC error\0".as_ptr() as *const i8,
715 )
716 };
717 let wc_err = Error {
718 ptr: wc_err_ptr,
719 owns_ptr: true,
720 _phantom: std::marker::PhantomData,
721 };
722 assert_eq!(wc_err.category(), ErrorCategory::WorkingCopy);
723
724 let repos_err_ptr = unsafe {
725 subversion_sys::svn_error_create(
726 SVN_ERR_REPOS_CATEGORY_START as i32,
727 std::ptr::null_mut(),
728 b"Repos error\0".as_ptr() as *const i8,
729 )
730 };
731 let repos_err = Error {
732 ptr: repos_err_ptr,
733 owns_ptr: true,
734 _phantom: std::marker::PhantomData,
735 };
736 assert_eq!(repos_err.category(), ErrorCategory::Repository);
737
738 let misc_err_ptr = unsafe {
739 subversion_sys::svn_error_create(
740 SVN_ERR_MISC_CATEGORY_START as i32,
741 std::ptr::null_mut(),
742 b"Misc error\0".as_ptr() as *const i8,
743 )
744 };
745 let misc_err = Error {
746 ptr: misc_err_ptr,
747 owns_ptr: true,
748 _phantom: std::marker::PhantomData,
749 };
750 assert_eq!(misc_err.category(), ErrorCategory::Misc);
751 }
752
753 #[test]
754 fn test_error_location_returns_value() {
755 use subversion_sys::*;
757
758 static TEST_FILE: &[u8] = b"test_file.c\0";
761
762 let err_ptr = unsafe {
763 let err = svn_error_create(
764 SVN_ERR_IO_CATEGORY_START as i32,
765 std::ptr::null_mut(),
766 b"Test error\0".as_ptr() as *const i8,
767 );
768 (*err).file = TEST_FILE.as_ptr() as *const i8;
771 (*err).line = 42;
772 err
773 };
774
775 let err = Error {
776 ptr: err_ptr,
777 owns_ptr: true,
778 _phantom: std::marker::PhantomData,
779 };
780
781 let location = err.location();
783 assert!(
784 location.is_some(),
785 "Error with file/line should have location"
786 );
787 let (file, line) = location.unwrap();
788 assert_eq!(file, "test_file.c", "File name should match");
789 assert_eq!(line, 42, "Line number should match");
790 }
791
792 #[test]
793 fn test_error_category_boundary_conditions() {
794 use subversion_sys::*;
797
798 let make_error = |code: u32| -> Error<'static> {
800 let err_ptr = unsafe {
801 svn_error_create(
802 code as i32,
803 std::ptr::null_mut(),
804 b"Test\0".as_ptr() as *const i8,
805 )
806 };
807 Error {
808 ptr: err_ptr,
809 owns_ptr: true,
810 _phantom: std::marker::PhantomData,
811 }
812 };
813
814 let category_size = SVN_ERR_CATEGORY_SIZE;
815
816 assert_eq!(
818 make_error(SVN_ERR_BAD_CATEGORY_START).category(),
819 ErrorCategory::BadInput,
820 "Start of BadInput range"
821 );
822 assert_eq!(
823 make_error(SVN_ERR_BAD_CATEGORY_START + category_size - 1).category(),
824 ErrorCategory::BadInput,
825 "End of BadInput range"
826 );
827 assert_eq!(
828 make_error(SVN_ERR_BAD_CATEGORY_START - 1).category(),
829 ErrorCategory::Other,
830 "Just before BadInput range"
831 );
832
833 assert_eq!(
835 make_error(SVN_ERR_XML_CATEGORY_START).category(),
836 ErrorCategory::Xml,
837 "Start of Xml range"
838 );
839 assert_eq!(
840 make_error(SVN_ERR_XML_CATEGORY_START + category_size - 1).category(),
841 ErrorCategory::Xml,
842 "End of Xml range"
843 );
844 assert_eq!(
845 make_error(SVN_ERR_XML_CATEGORY_START + category_size).category(),
846 ErrorCategory::Io,
847 "Just after Xml range should be Io"
848 );
849
850 assert_eq!(
852 make_error(SVN_ERR_IO_CATEGORY_START).category(),
853 ErrorCategory::Io,
854 "Start of Io range"
855 );
856 assert_eq!(
857 make_error(SVN_ERR_IO_CATEGORY_START + category_size - 1).category(),
858 ErrorCategory::Io,
859 "End of Io range"
860 );
861
862 assert_eq!(
864 make_error(SVN_ERR_STREAM_CATEGORY_START).category(),
865 ErrorCategory::Stream,
866 "Start of Stream range"
867 );
868 assert_eq!(
869 make_error(SVN_ERR_STREAM_CATEGORY_START + category_size - 1).category(),
870 ErrorCategory::Stream,
871 "End of Stream range"
872 );
873
874 assert_eq!(
876 make_error(SVN_ERR_NODE_CATEGORY_START).category(),
877 ErrorCategory::Node,
878 "Start of Node range"
879 );
880 assert_eq!(
881 make_error(SVN_ERR_NODE_CATEGORY_START + category_size - 1).category(),
882 ErrorCategory::Node,
883 "End of Node range"
884 );
885
886 assert_eq!(
888 make_error(SVN_ERR_ENTRY_CATEGORY_START).category(),
889 ErrorCategory::Entry,
890 "Start of Entry range"
891 );
892 assert_eq!(
893 make_error(SVN_ERR_ENTRY_CATEGORY_START + category_size - 1).category(),
894 ErrorCategory::Entry,
895 "End of Entry range"
896 );
897
898 assert_eq!(
900 make_error(SVN_ERR_WC_CATEGORY_START).category(),
901 ErrorCategory::WorkingCopy,
902 "Start of WorkingCopy range"
903 );
904 assert_eq!(
905 make_error(SVN_ERR_WC_CATEGORY_START + category_size - 1).category(),
906 ErrorCategory::WorkingCopy,
907 "End of WorkingCopy range"
908 );
909
910 assert_eq!(
912 make_error(SVN_ERR_FS_CATEGORY_START).category(),
913 ErrorCategory::Filesystem,
914 "Start of Filesystem range"
915 );
916 assert_eq!(
917 make_error(SVN_ERR_FS_CATEGORY_START + category_size - 1).category(),
918 ErrorCategory::Filesystem,
919 "End of Filesystem range"
920 );
921
922 assert_eq!(
924 make_error(SVN_ERR_REPOS_CATEGORY_START).category(),
925 ErrorCategory::Repository,
926 "Start of Repository range"
927 );
928 assert_eq!(
929 make_error(SVN_ERR_REPOS_CATEGORY_START + category_size - 1).category(),
930 ErrorCategory::Repository,
931 "End of Repository range"
932 );
933
934 assert_eq!(
936 make_error(SVN_ERR_RA_CATEGORY_START).category(),
937 ErrorCategory::RepositoryAccess,
938 "Start of RepositoryAccess range"
939 );
940 assert_eq!(
941 make_error(SVN_ERR_RA_CATEGORY_START + category_size - 1).category(),
942 ErrorCategory::RepositoryAccess,
943 "End of RepositoryAccess range"
944 );
945
946 assert_eq!(
948 make_error(SVN_ERR_RA_DAV_CATEGORY_START).category(),
949 ErrorCategory::RaDav,
950 "Start of RaDav range"
951 );
952 assert_eq!(
953 make_error(SVN_ERR_RA_DAV_CATEGORY_START + category_size - 1).category(),
954 ErrorCategory::RaDav,
955 "End of RaDav range"
956 );
957
958 assert_eq!(
960 make_error(SVN_ERR_RA_LOCAL_CATEGORY_START).category(),
961 ErrorCategory::RaLocal,
962 "Start of RaLocal range"
963 );
964 assert_eq!(
965 make_error(SVN_ERR_RA_LOCAL_CATEGORY_START + category_size - 1).category(),
966 ErrorCategory::RaLocal,
967 "End of RaLocal range"
968 );
969
970 assert_eq!(
972 make_error(SVN_ERR_SVNDIFF_CATEGORY_START).category(),
973 ErrorCategory::Svndiff,
974 "Start of Svndiff range"
975 );
976 assert_eq!(
977 make_error(SVN_ERR_SVNDIFF_CATEGORY_START + category_size - 1).category(),
978 ErrorCategory::Svndiff,
979 "End of Svndiff range"
980 );
981
982 assert_eq!(
984 make_error(SVN_ERR_APMOD_CATEGORY_START).category(),
985 ErrorCategory::ApacheMod,
986 "Start of ApacheMod range"
987 );
988 assert_eq!(
989 make_error(SVN_ERR_APMOD_CATEGORY_START + category_size - 1).category(),
990 ErrorCategory::ApacheMod,
991 "End of ApacheMod range"
992 );
993
994 assert_eq!(
996 make_error(SVN_ERR_CLIENT_CATEGORY_START).category(),
997 ErrorCategory::Client,
998 "Start of Client range"
999 );
1000 assert_eq!(
1001 make_error(SVN_ERR_CLIENT_CATEGORY_START + category_size - 1).category(),
1002 ErrorCategory::Client,
1003 "End of Client range"
1004 );
1005
1006 assert_eq!(
1008 make_error(SVN_ERR_MISC_CATEGORY_START).category(),
1009 ErrorCategory::Misc,
1010 "Start of Misc range"
1011 );
1012 assert_eq!(
1013 make_error(SVN_ERR_MISC_CATEGORY_START + category_size - 1).category(),
1014 ErrorCategory::Misc,
1015 "End of Misc range"
1016 );
1017
1018 assert_eq!(
1020 make_error(SVN_ERR_CL_CATEGORY_START).category(),
1021 ErrorCategory::CommandLine,
1022 "Start of CommandLine range"
1023 );
1024 assert_eq!(
1025 make_error(SVN_ERR_CL_CATEGORY_START + category_size - 1).category(),
1026 ErrorCategory::CommandLine,
1027 "End of CommandLine range"
1028 );
1029
1030 assert_eq!(
1032 make_error(SVN_ERR_RA_SVN_CATEGORY_START).category(),
1033 ErrorCategory::RaSvn,
1034 "Start of RaSvn range"
1035 );
1036 assert_eq!(
1037 make_error(SVN_ERR_RA_SVN_CATEGORY_START + category_size - 1).category(),
1038 ErrorCategory::RaSvn,
1039 "End of RaSvn range"
1040 );
1041
1042 assert_eq!(
1044 make_error(SVN_ERR_AUTHN_CATEGORY_START).category(),
1045 ErrorCategory::Authentication,
1046 "Start of Authentication range"
1047 );
1048 assert_eq!(
1049 make_error(SVN_ERR_AUTHN_CATEGORY_START + category_size - 1).category(),
1050 ErrorCategory::Authentication,
1051 "End of Authentication range"
1052 );
1053
1054 assert_eq!(
1056 make_error(SVN_ERR_AUTHZ_CATEGORY_START).category(),
1057 ErrorCategory::Authorization,
1058 "Start of Authorization range"
1059 );
1060 assert_eq!(
1061 make_error(SVN_ERR_AUTHZ_CATEGORY_START + category_size - 1).category(),
1062 ErrorCategory::Authorization,
1063 "End of Authorization range"
1064 );
1065
1066 assert_eq!(
1068 make_error(SVN_ERR_DIFF_CATEGORY_START).category(),
1069 ErrorCategory::Diff,
1070 "Start of Diff range"
1071 );
1072 assert_eq!(
1073 make_error(SVN_ERR_DIFF_CATEGORY_START + category_size - 1).category(),
1074 ErrorCategory::Diff,
1075 "End of Diff range"
1076 );
1077
1078 assert_eq!(
1080 make_error(SVN_ERR_RA_SERF_CATEGORY_START).category(),
1081 ErrorCategory::RaSerf,
1082 "Start of RaSerf range"
1083 );
1084 assert_eq!(
1085 make_error(SVN_ERR_RA_SERF_CATEGORY_START + category_size - 1).category(),
1086 ErrorCategory::RaSerf,
1087 "End of RaSerf range"
1088 );
1089
1090 assert_eq!(
1092 make_error(SVN_ERR_MALFUNC_CATEGORY_START).category(),
1093 ErrorCategory::Malfunction,
1094 "Start of Malfunction range"
1095 );
1096 assert_eq!(
1097 make_error(SVN_ERR_MALFUNC_CATEGORY_START + category_size - 1).category(),
1098 ErrorCategory::Malfunction,
1099 "End of Malfunction range"
1100 );
1101
1102 assert_eq!(
1104 make_error(SVN_ERR_X509_CATEGORY_START).category(),
1105 ErrorCategory::X509,
1106 "Start of X509 range"
1107 );
1108 assert_eq!(
1109 make_error(SVN_ERR_X509_CATEGORY_START + category_size - 1).category(),
1110 ErrorCategory::X509,
1111 "End of X509 range"
1112 );
1113 assert_eq!(
1114 make_error(SVN_ERR_X509_CATEGORY_START + category_size).category(),
1115 ErrorCategory::Other,
1116 "Just after X509 range"
1117 );
1118
1119 assert_eq!(
1121 make_error(0).category(),
1122 ErrorCategory::Other,
1123 "Zero should be Other"
1124 );
1125 assert_eq!(
1126 make_error(1000).category(),
1127 ErrorCategory::Other,
1128 "Small values should be Other"
1129 );
1130 assert_eq!(
1131 make_error(300000).category(),
1132 ErrorCategory::Other,
1133 "Values beyond all categories should be Other"
1134 );
1135 }
1136
1137 #[test]
1138 fn test_error_best_message_returns_actual_message() {
1139 use subversion_sys::*;
1141
1142 let err_ptr = unsafe {
1143 svn_error_create(
1144 SVN_ERR_IO_CATEGORY_START as i32,
1145 std::ptr::null_mut(),
1146 b"Specific error message\0".as_ptr() as *const i8,
1147 )
1148 };
1149 let err = Error {
1150 ptr: err_ptr,
1151 owns_ptr: true,
1152 _phantom: std::marker::PhantomData,
1153 };
1154
1155 let msg = err.best_message();
1156 assert!(!msg.is_empty(), "best_message should not be empty");
1157 assert_ne!(msg, "xyzzy", "best_message should not be 'xyzzy'");
1158 assert_eq!(
1159 msg, "Specific error message",
1160 "best_message should return exact message, got '{}'",
1161 msg
1162 );
1163 }
1164
1165 #[test]
1166 fn test_error_child_returns_none_when_no_child() {
1167 use subversion_sys::*;
1169
1170 let err_ptr = unsafe {
1171 svn_error_create(
1172 SVN_ERR_IO_CATEGORY_START as i32,
1173 std::ptr::null_mut(),
1174 b"Error without child\0".as_ptr() as *const i8,
1175 )
1176 };
1177
1178 let err = Error {
1179 ptr: err_ptr,
1180 owns_ptr: true,
1181 _phantom: std::marker::PhantomData,
1182 };
1183
1184 let child = err.child();
1186 assert!(
1187 child.is_none(),
1188 "child() should return None when no child exists"
1189 );
1190 }
1191
1192 #[test]
1193 fn test_error_find_cause_returns_none_for_non_matching_status() {
1194 use subversion_sys::*;
1196
1197 let err_ptr = unsafe {
1199 svn_error_create(
1200 SVN_ERR_IO_CATEGORY_START as i32,
1201 std::ptr::null_mut(),
1202 b"Test error\0".as_ptr() as *const i8,
1203 )
1204 };
1205 let err = Error {
1206 ptr: err_ptr,
1207 owns_ptr: true,
1208 _phantom: std::marker::PhantomData,
1209 };
1210
1211 let different_err_ptr = unsafe {
1213 svn_error_create(
1214 (SVN_ERR_CLIENT_CATEGORY_START + 100) as i32,
1215 std::ptr::null_mut(),
1216 b"Different error\0".as_ptr() as *const i8,
1217 )
1218 };
1219 let different_err = Error {
1220 ptr: different_err_ptr,
1221 owns_ptr: true,
1222 _phantom: std::marker::PhantomData,
1223 };
1224 let different_status = different_err.apr_err();
1225 std::mem::forget(different_err);
1227
1228 let found = err.find_cause(different_status);
1230
1231 assert!(
1232 found.is_none(),
1233 "find_cause() should return None when status doesn't match any error in chain"
1234 );
1235
1236 unsafe {
1238 subversion_sys::svn_error_clear(different_err_ptr);
1239 }
1240 }
1241
1242 #[test]
1243 fn test_error_child_returns_actual_child() {
1244 use subversion_sys::*;
1246
1247 let child_err_ptr = unsafe {
1248 svn_error_create(
1249 SVN_ERR_IO_CATEGORY_START as i32,
1250 std::ptr::null_mut(),
1251 b"Child error\0".as_ptr() as *const i8,
1252 )
1253 };
1254
1255 let parent_err_ptr = unsafe {
1256 svn_error_create(
1257 SVN_ERR_CLIENT_CATEGORY_START as i32,
1258 child_err_ptr,
1259 b"Parent error\0".as_ptr() as *const i8,
1260 )
1261 };
1262
1263 let parent_err = Error {
1264 ptr: parent_err_ptr,
1265 owns_ptr: true,
1266 _phantom: std::marker::PhantomData,
1267 };
1268
1269 let child = parent_err.child();
1271 assert!(
1272 child.is_some(),
1273 "child() should return Some when child exists"
1274 );
1275
1276 let child_err = child.unwrap();
1277 assert_eq!(
1278 child_err.category(),
1279 ErrorCategory::Io,
1280 "Child error should have Io category"
1281 );
1282 assert!(
1283 child_err.message().unwrap().contains("Child error"),
1284 "Child error should have correct message"
1285 );
1286 }
1287
1288 #[test]
1289 fn test_error_find_cause_returns_matching_error() {
1290 let child_err = Error::from_message("Child error");
1293 let parent_status = apr::Status::from(12345);
1294 let parent_err = Error::new(parent_status, Some(child_err), "Parent error");
1295
1296 let found = parent_err.find_cause(parent_status);
1298 assert!(
1299 found.is_some(),
1300 "find_cause() should find error with matching status"
1301 );
1302
1303 let found_err = found.unwrap();
1304 assert_eq!(
1305 found_err.apr_err(),
1306 parent_status,
1307 "Found error should have correct status"
1308 );
1309 }
1310
1311 #[test]
1312 fn test_error_as_ptr_returns_actual_pointer() {
1313 use subversion_sys::*;
1315
1316 let err_ptr = unsafe {
1317 svn_error_create(
1318 SVN_ERR_IO_CATEGORY_START as i32,
1319 std::ptr::null_mut(),
1320 b"Test\0".as_ptr() as *const i8,
1321 )
1322 };
1323
1324 let err = Error {
1325 ptr: err_ptr,
1326 owns_ptr: true,
1327 _phantom: std::marker::PhantomData,
1328 };
1329
1330 let ptr = err.as_ptr();
1331 assert!(!ptr.is_null(), "as_ptr() should return non-null pointer");
1332 assert_eq!(
1333 ptr, err_ptr,
1334 "as_ptr() should return the actual error pointer"
1335 );
1336 }
1337
1338 #[test]
1339 fn test_error_as_mut_ptr_returns_actual_pointer() {
1340 use subversion_sys::*;
1342
1343 let err_ptr = unsafe {
1344 svn_error_create(
1345 SVN_ERR_IO_CATEGORY_START as i32,
1346 std::ptr::null_mut(),
1347 b"Test\0".as_ptr() as *const i8,
1348 )
1349 };
1350
1351 let mut err = Error {
1352 ptr: err_ptr,
1353 owns_ptr: true,
1354 _phantom: std::marker::PhantomData,
1355 };
1356
1357 let ptr = err.as_mut_ptr();
1358 assert!(
1359 !ptr.is_null(),
1360 "as_mut_ptr() should return non-null pointer"
1361 );
1362 assert_eq!(
1363 ptr, err_ptr,
1364 "as_mut_ptr() should return the actual error pointer"
1365 );
1366 }
1367
1368 #[test]
1369 fn test_symbolic_name_returns_actual_names() {
1370 let err = Error::from_message("Test error");
1375 let status = err.apr_err();
1376
1377 let name = symbolic_name(status);
1379
1380 if let Some(name_str) = name {
1384 assert!(
1385 !name_str.is_empty(),
1386 "Symbolic name should not be empty if returned"
1387 );
1388 assert_ne!(name_str, "xyzzy", "Symbolic name should not be 'xyzzy'");
1389 assert!(
1390 name_str.starts_with("SVN_"),
1391 "Symbolic name should start with SVN_, got: {}",
1392 name_str
1393 );
1394 }
1395
1396 assert_eq!(symbolic_name(0.into()), Some("SVN_NO_ERROR"));
1399 let _ = symbolic_name(999999.into());
1400 }
1401
1402 #[test]
1403 fn test_strerror_returns_actual_error_strings() {
1404 let err = Error::from_message("Test error");
1409 let status = err.apr_err();
1410
1411 let err_str = strerror(status);
1413
1414 assert!(
1417 err_str.is_some(),
1418 "strerror() must return Some for a valid SVN error code, got None"
1419 );
1420
1421 let err_msg = err_str.unwrap();
1422 assert!(
1423 !err_msg.is_empty(),
1424 "Error string should not be empty if returned"
1425 );
1426 assert_ne!(err_msg, "xyzzy", "Error string should not be 'xyzzy'");
1427 assert!(
1428 err_msg.len() > 2,
1429 "Error string should be substantive, got: {}",
1430 err_msg
1431 );
1432
1433 let _ = strerror(0.into());
1436 let _ = strerror(999999.into());
1437 }
1438
1439 #[test]
1440 fn test_error_category_off_by_one_and_midrange() {
1441 use subversion_sys::*;
1444
1445 let make_error = |code: u32| -> Error<'static> {
1446 let err_ptr = unsafe {
1447 svn_error_create(
1448 code as i32,
1449 std::ptr::null_mut(),
1450 b"Test\0".as_ptr() as *const i8,
1451 )
1452 };
1453 Error {
1454 ptr: err_ptr,
1455 owns_ptr: true,
1456 _phantom: std::marker::PhantomData,
1457 }
1458 };
1459
1460 let category_size = SVN_ERR_CATEGORY_SIZE;
1461
1462 let categories = vec![
1464 (
1465 SVN_ERR_BAD_CATEGORY_START,
1466 ErrorCategory::BadInput,
1467 "BadInput",
1468 ),
1469 (SVN_ERR_XML_CATEGORY_START, ErrorCategory::Xml, "Xml"),
1470 (SVN_ERR_IO_CATEGORY_START, ErrorCategory::Io, "Io"),
1471 (
1472 SVN_ERR_STREAM_CATEGORY_START,
1473 ErrorCategory::Stream,
1474 "Stream",
1475 ),
1476 (SVN_ERR_NODE_CATEGORY_START, ErrorCategory::Node, "Node"),
1477 (SVN_ERR_ENTRY_CATEGORY_START, ErrorCategory::Entry, "Entry"),
1478 (
1479 SVN_ERR_WC_CATEGORY_START,
1480 ErrorCategory::WorkingCopy,
1481 "WorkingCopy",
1482 ),
1483 (
1484 SVN_ERR_FS_CATEGORY_START,
1485 ErrorCategory::Filesystem,
1486 "Filesystem",
1487 ),
1488 (
1489 SVN_ERR_REPOS_CATEGORY_START,
1490 ErrorCategory::Repository,
1491 "Repository",
1492 ),
1493 (
1494 SVN_ERR_RA_CATEGORY_START,
1495 ErrorCategory::RepositoryAccess,
1496 "RepositoryAccess",
1497 ),
1498 (SVN_ERR_RA_DAV_CATEGORY_START, ErrorCategory::RaDav, "RaDav"),
1499 (
1500 SVN_ERR_RA_LOCAL_CATEGORY_START,
1501 ErrorCategory::RaLocal,
1502 "RaLocal",
1503 ),
1504 (
1505 SVN_ERR_SVNDIFF_CATEGORY_START,
1506 ErrorCategory::Svndiff,
1507 "Svndiff",
1508 ),
1509 (
1510 SVN_ERR_APMOD_CATEGORY_START,
1511 ErrorCategory::ApacheMod,
1512 "ApacheMod",
1513 ),
1514 (
1515 SVN_ERR_CLIENT_CATEGORY_START,
1516 ErrorCategory::Client,
1517 "Client",
1518 ),
1519 (SVN_ERR_MISC_CATEGORY_START, ErrorCategory::Misc, "Misc"),
1520 (
1521 SVN_ERR_CL_CATEGORY_START,
1522 ErrorCategory::CommandLine,
1523 "CommandLine",
1524 ),
1525 (SVN_ERR_RA_SVN_CATEGORY_START, ErrorCategory::RaSvn, "RaSvn"),
1526 (
1527 SVN_ERR_AUTHN_CATEGORY_START,
1528 ErrorCategory::Authentication,
1529 "Authentication",
1530 ),
1531 (
1532 SVN_ERR_AUTHZ_CATEGORY_START,
1533 ErrorCategory::Authorization,
1534 "Authorization",
1535 ),
1536 (SVN_ERR_DIFF_CATEGORY_START, ErrorCategory::Diff, "Diff"),
1537 (
1538 SVN_ERR_RA_SERF_CATEGORY_START,
1539 ErrorCategory::RaSerf,
1540 "RaSerf",
1541 ),
1542 (
1543 SVN_ERR_MALFUNC_CATEGORY_START,
1544 ErrorCategory::Malfunction,
1545 "Malfunction",
1546 ),
1547 (SVN_ERR_X509_CATEGORY_START, ErrorCategory::X509, "X509"),
1548 ];
1549
1550 for (start, expected_cat, name) in categories {
1551 assert_eq!(
1553 make_error(start).category(),
1554 expected_cat,
1555 "{}: START should be in category",
1556 name
1557 );
1558
1559 assert_eq!(
1561 make_error(start + 1).category(),
1562 expected_cat,
1563 "{}: START+1 should be in category",
1564 name
1565 );
1566
1567 let mid = start + category_size / 2;
1569 assert_eq!(
1570 make_error(mid).category(),
1571 expected_cat,
1572 "{}: MID should be in category",
1573 name
1574 );
1575
1576 assert_eq!(
1578 make_error(start + category_size - 2).category(),
1579 expected_cat,
1580 "{}: END-2 should be in category",
1581 name
1582 );
1583
1584 assert_eq!(
1586 make_error(start + category_size - 1).category(),
1587 expected_cat,
1588 "{}: END-1 (last valid) should be in category",
1589 name
1590 );
1591
1592 assert_ne!(
1594 make_error(start + category_size).category(),
1595 expected_cat,
1596 "{}: END should NOT be in category",
1597 name
1598 );
1599
1600 if start > 1000 {
1603 assert_ne!(
1604 make_error(start - 1).category(),
1605 expected_cat,
1606 "{}: START-1 should NOT be in category",
1607 name
1608 );
1609 }
1610 }
1611
1612 assert_eq!(
1614 make_error(100).category(),
1615 ErrorCategory::Other,
1616 "Value below all categories should be Other"
1617 );
1618
1619 assert_eq!(
1621 make_error(500000).category(),
1622 ErrorCategory::Other,
1623 "Value above all categories should be Other"
1624 );
1625
1626 let between = SVN_ERR_BAD_CATEGORY_START + category_size;
1628 let between_cat = make_error(between).category();
1629 assert_ne!(
1630 between_cat,
1631 ErrorCategory::BadInput,
1632 "Value just after BadInput should not be BadInput"
1633 );
1634 }
1635
1636 #[test]
1637 fn test_raw_apr_err_preserves_svn_error_codes() {
1638 let cancelled_code = subversion_sys::svn_errno_t_SVN_ERR_CANCELLED as i32;
1642 let err = Error::with_raw_status(cancelled_code, None, "cancelled");
1643
1644 assert_eq!(err.raw_apr_err(), cancelled_code);
1645 assert_eq!(err.apr_err(), apr::Status::General);
1647 }
1648
1649 #[test]
1650 fn test_with_raw_status_creates_distinguishable_errors() {
1651 let cancelled_code = subversion_sys::svn_errno_t_SVN_ERR_CANCELLED as i32;
1652 let fs_not_found_code = subversion_sys::svn_errno_t_SVN_ERR_FS_NOT_FOUND as i32;
1653
1654 let err1 = Error::with_raw_status(cancelled_code, None, "cancelled");
1655 let err2 = Error::with_raw_status(fs_not_found_code, None, "not found");
1656
1657 assert_eq!(err1.apr_err(), err2.apr_err());
1659 assert_ne!(err1.raw_apr_err(), err2.raw_apr_err());
1661 assert_eq!(err1.raw_apr_err(), cancelled_code);
1662 assert_eq!(err2.raw_apr_err(), fs_not_found_code);
1663 }
1664
1665 #[test]
1666 fn test_with_raw_status_message_and_child() {
1667 let child = Error::from_message("child error");
1668 let parent = Error::with_raw_status(200015, Some(child), "parent error");
1669
1670 assert_eq!(parent.message(), Some("parent error"));
1671 let full = parent.full_message();
1672 assert!(full.contains("parent error"));
1673 assert!(full.contains("child error"));
1674 }
1675}