rb_sys_test_helpers/
ruby_exception.rs1use crate::{rb_funcall_typed, rstring_to_string};
2use rb_sys::{
3 rb_ary_join, rb_class2name, rb_obj_class, rb_str_new,
4 ruby_value_type::{RUBY_T_ARRAY, RUBY_T_STRING},
5 RB_TYPE_P, VALUE,
6};
7use std::ffi::CStr;
8
9#[derive(Clone, Eq, PartialEq)]
12pub struct RubyException {
13 value: VALUE,
14}
15
16impl RubyException {
17 pub fn new(value: VALUE) -> Self {
19 Self { value }
20 }
21
22 pub fn message(&self) -> Option<String> {
24 unsafe {
25 rb_funcall_typed!(self.value, "message", [], RUBY_T_STRING)
26 .map(|mut message| rstring_to_string!(message))
27 }
28 }
29
30 pub fn full_message(&self) -> Option<String> {
32 unsafe {
33 if let Some(mut message) =
34 rb_funcall_typed!(self.value, "full_message", [], RUBY_T_STRING)
35 {
36 let message = rstring_to_string!(message);
37 Some(message.trim_start_matches("-e: ").to_string())
38 } else {
39 None
40 }
41 }
42 }
43
44 pub fn backtrace(&self) -> Option<String> {
46 unsafe {
47 if let Some(backtrace) = rb_funcall_typed!(self.value, "backtrace", [], RUBY_T_ARRAY) {
48 let mut backtrace = rb_ary_join(backtrace, rb_str_new("\n".as_ptr() as _, 1));
49 let backtrace = rstring_to_string!(backtrace);
50
51 if backtrace.is_empty() {
52 return None;
53 }
54
55 Some(backtrace)
56 } else {
57 None
58 }
59 }
60 }
61
62 pub fn inspect(&self) -> String {
64 unsafe {
65 if let Some(mut inspect) = rb_funcall_typed!(self.value, "inspect", [], RUBY_T_STRING) {
66 rstring_to_string!(inspect)
67 } else {
68 format!("<no inspect: {:?}>", self.value)
69 }
70 }
71 }
72
73 pub fn classname(&self) -> String {
75 unsafe {
76 let classname = rb_class2name(rb_obj_class(self.value));
77 CStr::from_ptr(classname).to_string_lossy().into_owned()
78 }
79 }
80}
81
82impl std::fmt::Debug for RubyException {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 let message = self.message();
91 let klass = self.classname();
92 let bt = self.backtrace();
93
94 if let Some(full_message) = self.full_message() {
95 return f.write_str(&full_message);
96 }
97
98 if let Some(message) = message {
99 f.write_str(&message)?;
100 } else {
101 f.write_str("<no message>")?;
102 }
103
104 f.write_fmt(format_args!(" ({}):\n", klass))?;
105
106 if let Some(bt) = bt {
107 f.write_str(&bt)?;
108 } else {
109 f.write_str("<no backtrace>")?;
110 }
111
112 Ok(())
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use crate::{protect, with_ruby_vm};
119 use rb_sys::rb_eval_string;
120
121 #[test]
122 fn test_exception() -> Result<(), Box<dyn std::error::Error>> {
123 with_ruby_vm(|| {
124 let exception = protect(|| unsafe {
125 rb_eval_string("raise 'oh no'\0".as_ptr() as _);
126 })
127 .unwrap_err();
128
129 assert_eq!("RuntimeError", exception.classname());
130 assert_eq!("oh no", exception.message().unwrap());
131 #[cfg(ruby_gt_2_4)]
132 {
133 let message = exception.full_message().unwrap();
134 assert!(message.contains("eval:1:in "), "message: {}", message);
135 }
136 })
137 }
138}