// SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright The LanceDB Authors use std::str::Utf8Error; use arrow_schema::ArrowError; use jni::errors::Error as JniError; use serde_json::Error as JsonError; use snafu::{Location, Snafu}; type BoxedError = Box; /// Java Exception types pub enum JavaException { IllegalArgumentException, IOException, RuntimeException, } impl JavaException { pub fn as_str(&self) -> &str { match self { Self::IllegalArgumentException => "java/lang/IllegalArgumentException", Self::IOException => "java/io/IOException", Self::RuntimeException => "java/lang/RuntimeException", } } } /// TODO(lu) change to lancedb-jni #[derive(Debug, Snafu)] #[snafu(visibility(pub))] pub enum Error { #[snafu(display("JNI error: {message}, {location}"))] Jni { message: String, location: Location }, #[snafu(display("Invalid argument: {message}, {location}"))] InvalidArgument { message: String, location: Location }, #[snafu(display("IO error: {source}, {location}"))] IO { source: BoxedError, location: Location, }, #[snafu(display("Arrow error: {message}, {location}"))] Arrow { message: String, location: Location }, #[snafu(display("Index error: {message}, {location}"))] Index { message: String, location: Location }, #[snafu(display("JSON error: {message}, {location}"))] JSON { message: String, location: Location }, #[snafu(display("Dataset at path {path} was not found, {location}"))] DatasetNotFound { path: String, location: Location }, #[snafu(display("Dataset already exists: {uri}, {location}"))] DatasetAlreadyExists { uri: String, location: Location }, #[snafu(display("Table '{name}' already exists"))] TableAlreadyExists { name: String }, #[snafu(display("Table '{name}' was not found"))] TableNotFound { name: String }, #[snafu(display("Invalid table name '{name}': {reason}"))] InvalidTableName { name: String, reason: String }, #[snafu(display("Embedding function '{name}' was not found: {reason}, {location}"))] EmbeddingFunctionNotFound { name: String, reason: String, location: Location, }, #[snafu(display("Other Lance error: {message}, {location}"))] OtherLance { message: String, location: Location }, #[snafu(display("Other LanceDB error: {message}, {location}"))] OtherLanceDB { message: String, location: Location }, } impl Error { /// Throw as Java Exception pub fn throw(&self, env: &mut jni::JNIEnv) { match self { Self::InvalidArgument { .. } | Self::DatasetNotFound { .. } | Self::DatasetAlreadyExists { .. } | Self::TableAlreadyExists { .. } | Self::TableNotFound { .. } | Self::InvalidTableName { .. } | Self::EmbeddingFunctionNotFound { .. } => { self.throw_as(env, JavaException::IllegalArgumentException) } Self::IO { .. } | Self::Index { .. } => self.throw_as(env, JavaException::IOException), Self::Arrow { .. } | Self::JSON { .. } | Self::OtherLance { .. } | Self::OtherLanceDB { .. } | Self::Jni { .. } => self.throw_as(env, JavaException::RuntimeException), } } /// Throw as an concrete Java Exception pub fn throw_as(&self, env: &mut jni::JNIEnv, exception: JavaException) { let message = &format!( "Error when throwing Java exception: {}:{}", exception.as_str(), self ); env.throw_new(exception.as_str(), self.to_string()) .expect(message); } } pub type Result = std::result::Result; trait ToSnafuLocation { fn to_snafu_location(&'static self) -> snafu::Location; } impl ToSnafuLocation for std::panic::Location<'static> { fn to_snafu_location(&'static self) -> snafu::Location { snafu::Location::new(self.file(), self.line(), self.column()) } } impl From for Error { #[track_caller] fn from(source: JniError) -> Self { Self::Jni { message: source.to_string(), location: std::panic::Location::caller().to_snafu_location(), } } } impl From for Error { #[track_caller] fn from(source: Utf8Error) -> Self { Self::InvalidArgument { message: source.to_string(), location: std::panic::Location::caller().to_snafu_location(), } } } impl From for Error { #[track_caller] fn from(source: ArrowError) -> Self { Self::Arrow { message: source.to_string(), location: std::panic::Location::caller().to_snafu_location(), } } } impl From for Error { #[track_caller] fn from(source: JsonError) -> Self { Self::JSON { message: source.to_string(), location: std::panic::Location::caller().to_snafu_location(), } } } impl From for Error { #[track_caller] fn from(source: lance::Error) -> Self { match source { lance::Error::DatasetNotFound { path, source: _, location, } => Self::DatasetNotFound { path, location }, lance::Error::DatasetAlreadyExists { uri, location } => { Self::DatasetAlreadyExists { uri, location } } lance::Error::IO { source, location } => Self::IO { source, location }, lance::Error::Arrow { message, location } => Self::Arrow { message, location }, lance::Error::Index { message, location } => Self::Index { message, location }, lance::Error::InvalidInput { source, location } => Self::InvalidArgument { message: source.to_string(), location, }, _ => Self::OtherLance { message: source.to_string(), location: std::panic::Location::caller().to_snafu_location(), }, } } } impl From for Error { #[track_caller] fn from(source: lancedb::Error) -> Self { match source { lancedb::Error::InvalidTableName { name, reason } => { Self::InvalidTableName { name, reason } } lancedb::Error::InvalidInput { message } => Self::InvalidArgument { message, location: std::panic::Location::caller().to_snafu_location(), }, lancedb::Error::TableNotFound { name } => Self::TableNotFound { name }, lancedb::Error::TableAlreadyExists { name } => Self::TableAlreadyExists { name }, lancedb::Error::EmbeddingFunctionNotFound { name, reason } => { Self::EmbeddingFunctionNotFound { name, reason, location: std::panic::Location::caller().to_snafu_location(), } } lancedb::Error::Arrow { source } => Self::Arrow { message: source.to_string(), location: std::panic::Location::caller().to_snafu_location(), }, lancedb::Error::Lance { source } => Self::from(source), _ => Self::OtherLanceDB { message: source.to_string(), location: std::panic::Location::caller().to_snafu_location(), }, } } }