unobtanium 3.0.0

Opinioated Web search engine library with crawler and viewer companion.
Documentation
use rusqlite::Error as RusqliteError;
use thiserror::Error;

use std::path::PathBuf;
use std::path::Path;

use super::id::CrawlLogEntryId;

#[derive(Error, Debug)]
pub enum DatabaseOpeningError {
	#[error("Unable to open Database at {path:?}: {source}")]
	UnableToOpenDatabase {
		source: RusqliteError,
		path: PathBuf,
	},

	#[error("Database file not found at {path:?}")]
	DatabaseFileNotFound {
		path: PathBuf,
	},

	#[error("Database at {path:?} required to be writable, but it isn't. Are the (file) permissions correct?")]
	DatabaseIsNotWritable {
		path: PathBuf,
	},

	#[error("Error while initalizing Database at {path:?}: {source}")]
	ErrorInitalizingDatabase {
		source: DatabaseError,
		path: PathBuf,
	},

	#[error("Database at {path:?} either isn't an unobtanium database or an unversioned one (before 2025-08-09).")]
	DatabaseUnversioned {
		path: PathBuf,
	},

	#[error("Database at {path:?} has {schema} schema version of {got_version:?}, expected {expected_version:?}")]
	WrongSchemaVersion {
		path: PathBuf,
		schema: String,
		got_version: String,
		expected_version: String,
	},

	#[error("Database at {path:?} isof he wrong kind. Found {got_kind:?}, Expected {expected_kind:?}")]
	WrongDatabaseKind {
		path: PathBuf,
		got_kind: String,
		expected_kind: String
	}
}

#[derive(Error, Debug)]
pub enum DatabaseError {

	#[error("Problem wile interacting with database: {source}")]
	RusqliteError {
		source: RusqliteError,
	},

	/// Returned when the possiblity of a write to a read only database is detected.
	#[error("Attempted to write to read only database using {function}")]
	AttemptToWriteToReadOnlyDatabse {
		function: String,
	},

	#[error("Out of range crawl exit code {code} on entry {crawl_log_id}")]
	OutOfRangeCrawlerExitCode {
		crawl_log_id: CrawlLogEntryId,
		code: i64,
	},

}

impl DatabaseError {
	/// For smuggling errors through rusqlite Errors.
	///
	/// Errors will be smuggled through the `InvalidColumnName` error message.
	///
	/// This will loose error metadata, use only when neccessary!
	pub(super) fn smuggle_through_rusqlite(self) -> RusqliteError {
		match self {
			Self::RusqliteError { source } => source,
			Self::AttemptToWriteToReadOnlyDatabse { function } =>
				RusqliteError::InvalidColumnName(
					format!("SmuggledUnobtaniumDatabaseError: Attempt to write to readonly database in {function}")),
			Self::OutOfRangeCrawlerExitCode { crawl_log_id, code } =>
				RusqliteError::InvalidColumnName(
					format!("SmuggledUnobtaniumDatabaseError: Out of range crawl exit code {code} on entry {crawl_log_id}")),
		}
	}

	/// Wrap as an error while initializing into an [DatabaseOpeningError].
	pub(super) fn while_initlizing(self, path: impl AsRef<Path>) -> DatabaseOpeningError {
		DatabaseOpeningError::ErrorInitalizingDatabase {
			source: self,
			path: path.as_ref().into()
		}
	}

	/// Wheter the error is about something not being found.
	pub fn is_not_found(&self) -> bool {
		matches!(self,Self::RusqliteError { source: RusqliteError::QueryReturnedNoRows })
	}
}

impl From<RusqliteError> for DatabaseError {
	fn from(value: RusqliteError) -> Self {
		Self::RusqliteError { source: value }
	}
}

pub trait SmuggleDatabaseErrorExtension<T> {
    fn smuggle_through_rusqlite(self) -> Result<T, RusqliteError>;
}

impl<T> SmuggleDatabaseErrorExtension<T> for Result<T, DatabaseError> {
	fn smuggle_through_rusqlite(self) -> Result<T, RusqliteError> {
		match self {
			Ok(v) => Ok(v),
			Err(e) => Err(e.smuggle_through_rusqlite()),
		}
	}
}

pub trait IntoDatabaseErrorExtension<T> {
	/// Turns a database specific error into an error while opening database.
	fn error_while_opening(self, path: impl AsRef<Path>) -> Result<T,DatabaseOpeningError>;
}

impl<T> IntoDatabaseErrorExtension<T> for Result<T,RusqliteError> {
	fn error_while_opening(self, path: impl AsRef<Path>) -> Result<T,DatabaseOpeningError> {
	    self.map_err(|e| DatabaseOpeningError::UnableToOpenDatabase {
		    source: e,
		    path: path.as_ref().into()
	    }) 
	}
}