@@ -175,28 +175,35 @@ impl std::fmt::Display for Error {
175175
176176impl From < LanceError > for Error {
177177 fn from ( err : LanceError ) -> Self {
178+ let backtrace_suffix = err
179+ . backtrace ( )
180+ . map ( |bt| format ! ( "\n \n Rust backtrace:\n {}" , bt) )
181+ . unwrap_or_default ( ) ;
182+ let message = format ! ( "{}{}" , err, backtrace_suffix) ;
183+
178184 match & err {
179185 LanceError :: DatasetNotFound { .. }
180186 | LanceError :: DatasetAlreadyExists { .. }
181187 | LanceError :: CommitConflict { .. }
182- | LanceError :: InvalidInput { .. } => Self :: input_error ( err . to_string ( ) ) ,
183- LanceError :: IO { .. } => Self :: io_error ( err . to_string ( ) ) ,
184- LanceError :: NotSupported { .. } => Self :: unsupported_error ( err . to_string ( ) ) ,
185- LanceError :: NotFound { .. } => Self :: io_error ( err . to_string ( ) ) ,
188+ | LanceError :: InvalidInput { .. } => Self :: input_error ( message ) ,
189+ LanceError :: IO { .. } => Self :: io_error ( message ) ,
190+ LanceError :: NotSupported { .. } => Self :: unsupported_error ( message ) ,
191+ LanceError :: NotFound { .. } => Self :: io_error ( message ) ,
186192 LanceError :: Namespace { source, .. } => {
187193 // Try to downcast to NamespaceError and get the error code
188194 if let Some ( ns_err) = source. downcast_ref :: < NamespaceError > ( ) {
189- Self :: namespace_error ( ns_err. code ( ) . as_u32 ( ) , ns_err. to_string ( ) )
195+ let ns_message = format ! ( "{}{}" , ns_err, backtrace_suffix) ;
196+ Self :: namespace_error ( ns_err. code ( ) . as_u32 ( ) , ns_message)
190197 } else {
191198 log:: warn!(
192199 "Failed to downcast NamespaceError source, falling back to runtime error. \
193200 This may indicate a version mismatch. Source type: {:?}",
194201 source
195202 ) ;
196- Self :: runtime_error ( err . to_string ( ) )
203+ Self :: runtime_error ( message )
197204 }
198205 }
199- _ => Self :: runtime_error ( err . to_string ( ) ) ,
206+ _ => Self :: runtime_error ( message ) ,
200207 }
201208 }
202209}
@@ -234,3 +241,104 @@ impl From<Utf8Error> for Error {
234241 Self :: input_error ( err. to_string ( ) )
235242 }
236243}
244+
245+ #[ cfg( test) ]
246+ mod tests {
247+ use super :: * ;
248+
249+ // Helper: extract the java_class from an Error via Display output
250+ fn java_class ( err : & Error ) -> & JavaExceptionClass {
251+ & err. java_class
252+ }
253+
254+ #[ test]
255+ fn test_invalid_input_maps_to_illegal_argument ( ) {
256+ let lance_err = LanceError :: invalid_input ( "bad input" ) ;
257+ let jni_err: Error = lance_err. into ( ) ;
258+ assert_eq ! (
259+ * java_class( & jni_err) ,
260+ JavaExceptionClass :: IllegalArgumentException
261+ ) ;
262+ assert ! ( jni_err. message. contains( "bad input" ) ) ;
263+ }
264+
265+ #[ test]
266+ fn test_dataset_not_found_maps_to_illegal_argument ( ) {
267+ let lance_err = LanceError :: dataset_not_found ( "my_dataset" , "not found" . to_string ( ) . into ( ) ) ;
268+ let jni_err: Error = lance_err. into ( ) ;
269+ assert_eq ! (
270+ * java_class( & jni_err) ,
271+ JavaExceptionClass :: IllegalArgumentException
272+ ) ;
273+ assert ! ( jni_err. message. contains( "my_dataset" ) ) ;
274+ }
275+
276+ #[ test]
277+ fn test_dataset_already_exists_maps_to_illegal_argument ( ) {
278+ let lance_err = LanceError :: dataset_already_exists ( "my_dataset" ) ;
279+ let jni_err: Error = lance_err. into ( ) ;
280+ assert_eq ! (
281+ * java_class( & jni_err) ,
282+ JavaExceptionClass :: IllegalArgumentException
283+ ) ;
284+ assert ! ( jni_err. message. contains( "my_dataset" ) ) ;
285+ }
286+
287+ #[ test]
288+ fn test_commit_conflict_maps_to_illegal_argument ( ) {
289+ let lance_err = LanceError :: commit_conflict_source ( 42 , "conflict" . to_string ( ) . into ( ) ) ;
290+ let jni_err: Error = lance_err. into ( ) ;
291+ assert_eq ! (
292+ * java_class( & jni_err) ,
293+ JavaExceptionClass :: IllegalArgumentException
294+ ) ;
295+ }
296+
297+ #[ test]
298+ fn test_io_maps_to_ioexception ( ) {
299+ let lance_err = LanceError :: io ( "disk failure" ) ;
300+ let jni_err: Error = lance_err. into ( ) ;
301+ assert_eq ! ( * java_class( & jni_err) , JavaExceptionClass :: IOException ) ;
302+ assert ! ( jni_err. message. contains( "disk failure" ) ) ;
303+ }
304+
305+ #[ test]
306+ fn test_not_supported_maps_to_unsupported ( ) {
307+ let lance_err = LanceError :: not_supported ( "nope" ) ;
308+ let jni_err: Error = lance_err. into ( ) ;
309+ assert_eq ! (
310+ * java_class( & jni_err) ,
311+ JavaExceptionClass :: UnsupportedOperationException
312+ ) ;
313+ assert ! ( jni_err. message. contains( "nope" ) ) ;
314+ }
315+
316+ #[ test]
317+ fn test_not_found_maps_to_ioexception ( ) {
318+ let lance_err = LanceError :: not_found ( "missing_uri" ) ;
319+ let jni_err: Error = lance_err. into ( ) ;
320+ assert_eq ! ( * java_class( & jni_err) , JavaExceptionClass :: IOException ) ;
321+ assert ! ( jni_err. message. contains( "missing_uri" ) ) ;
322+ }
323+
324+ #[ test]
325+ fn test_fallthrough_maps_to_runtime ( ) {
326+ let lance_err = LanceError :: internal ( "internal oops" ) ;
327+ let jni_err: Error = lance_err. into ( ) ;
328+ assert_eq ! ( * java_class( & jni_err) , JavaExceptionClass :: RuntimeException ) ;
329+ assert ! ( jni_err. message. contains( "internal oops" ) ) ;
330+ }
331+
332+ #[ test]
333+ fn test_no_backtrace_suffix_when_backtrace_is_none ( ) {
334+ // Without the backtrace feature enabled in lance-core default tests,
335+ // backtrace() returns None, so no suffix should be appended.
336+ let lance_err = LanceError :: io ( "clean message" ) ;
337+ let jni_err: Error = lance_err. into ( ) ;
338+ assert ! (
339+ !jni_err. message. contains( "Rust backtrace:" ) ,
340+ "Expected no backtrace suffix, got: {}" ,
341+ jni_err. message
342+ ) ;
343+ }
344+ }
0 commit comments