diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 46c0f0b592b9f63d0ee00674515d041235cd2103..80ffe6fab0f335b1105a655c5de8c30de5effab5 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -25,8 +25,9 @@ license = "MIT" [workspace.dependencies] log = "0.4" -libc = "0.2.0" -lazy_static = "1" +libc = "0.2" +lazy_static = "^1" +thiserror = "^1" diesel_migrations = "2" diesel = { version = "2", default-features = false, features = ["sqlite"] } diff --git a/src/rust/lektor_db/Cargo.toml b/src/rust/lektor_db/Cargo.toml index 6016644038606c3d8415dd7536ab61b8c8d736ee..85e6ba8276b1643f15d29d8d9414a43f1963fc6a 100644 --- a/src/rust/lektor_db/Cargo.toml +++ b/src/rust/lektor_db/Cargo.toml @@ -8,6 +8,7 @@ license.workspace = true [dependencies] log.workspace = true serde.workspace = true +thiserror.workspace = true diesel_migrations = "2" diesel = { version = "2", default-features = false, features = ["sqlite"] } diff --git a/src/rust/lektor_db/src/connexion.rs b/src/rust/lektor_db/src/connexion.rs index ee7ec97738e9a85967ff32ad571ba7cf675b662a..26c30f7ba1beac6d1646d3ca9466846a9dddfc9e 100644 --- a/src/rust/lektor_db/src/connexion.rs +++ b/src/rust/lektor_db/src/connexion.rs @@ -21,15 +21,15 @@ pub struct LktDatabaseConnection { /// value. macro_rules! uri_val { ($uri: literal :/ $expr: expr) => { - $expr - .get_value() - .ok_or_else(|| format!("the value of the {}:// uri is invalid", $uri))? + $expr.get_value().ok_or_else(|| { + LktDatabaseError::String(format!("the value of the {}:// uri is invalid", $uri)) + })? }; ($expr: expr) => { $expr .get_value() - .ok_or_else(|| format!("the value of the uri is invalid"))? + .ok_or_else(|| LktDatabaseError::Str("the value of the uri is invalid"))? }; } @@ -38,7 +38,7 @@ macro_rules! uri_val { macro_rules! uri_str { ($what: literal :/ $uri: expr) => {{ let LktUriValue::String(str) = uri_val!($what :/ $uri) else { - return Err(format!("try to pass integer `{}` as {} in uri", String::from($uri), $what)) + return Err(LktDatabaseError::String(format!("try to pass integer `{}` as {} in uri", String::from($uri), $what))) }; str }}; @@ -53,9 +53,9 @@ macro_rules! uri_as_int { LktUriValue::Integer(int) => int, LktUriValue::String(str) => { let str = str.parse::<i64>().map_err(|err| { - format!("the {} `{str}` is not a correct integer: {err}", $what) + LktDatabaseError::String(format!("the {} `{str}` is not a correct integer: {err}", $what)) })?; - i64::try_from(str).map_err(|err| format!("the {} `{str}` {err}", $what))? + i64::try_from(str).map_err(|err| LktDatabaseError::String(format!("the {} `{str}` {err}", $what)))? } } }; @@ -223,34 +223,48 @@ impl LktDatabaseConnection { Ok((id, kara, kara_maker, vec![lang], tags)) } + /// Returns the queue history. + pub fn history(&mut self) -> LktDatabaseResult<Vec<i64>> { + todo!() + } + + /// Clears the history. + pub fn clear_history(&mut self) -> LktDatabaseResult<()> { + todo!() + } + + /// Search the history by URIs. We return the local ids. + pub fn search_history(&mut self, _uri: LktCUri) -> LktDatabaseResult<Vec<i64>> { + todo!() + } + /// Search the queue by URIs. We return the local ids. - pub fn queue_search(&mut self, _uri: LktCUri) -> Result<Vec<i64>, String> { + pub fn search_queue(&mut self, _uri: LktCUri) -> LktDatabaseResult<Vec<i64>> { todo!() } /// Search the given playlist by URIs. We return the local ids. - pub fn playlist_search<S: AsRef<str>>( + pub fn search_playlist<S: AsRef<str>>( &mut self, _playlist: S, _uri: LktCUri, - ) -> Result<Vec<i64>, String> { + ) -> LktDatabaseResult<Vec<i64>> { todo!() } /// Search karas by URIs. We return the local ids. - pub fn database_search(&mut self, uri: LktCUri) -> Result<Vec<i64>, String> { + pub fn search_database(&mut self, uri: LktCUri) -> LktDatabaseResult<Vec<i64>> { use lektor_c_compat::rs::{LktUriField, LktUriValue}; let uri_type = uri .get_type() - .ok_or_else(|| "passed an URI which has no valid type...".to_string())?; + .ok_or_else(|| LktDatabaseError::Str("passed an URI which has no valid type..."))?; match uri_type { LktUriField::Id => Ok(vec![with_dsl!(kara => kara .filter(id.is(uri_as_int!("id" :/ uri))) .filter(is_virtual.is(false)) .select(id) - .first::<i64>(&mut self.sqlite) - .map_err(|err| format!("{err}"))? + .first::<i64>(&mut self.sqlite)? )]), LktUriField::KaraMaker => Ok(with_dsl!(kara_maker => kara_maker @@ -258,22 +272,19 @@ impl LktDatabaseConnection { .inner_join(schema::kara::table) .filter(schema::kara::is_virtual.is(false)) .select(id) - .load::<i64>(&mut self.sqlite) - .map_err(|err| format!("{err}"))? + .load::<i64>(&mut self.sqlite)? )), LktUriField::Origin => Ok(with_dsl!(kara => kara .filter(song_origin.like(uri_str!("origin" :/ uri))) .filter(is_virtual.is(false)) - .select(id).load::<i64>(&mut self.sqlite) - .map_err(|err| format!("{err}"))? + .select(id).load::<i64>(&mut self.sqlite)? )), LktUriField::Type => Ok(with_dsl!(kara => kara .filter(song_type.is(uri_str!("type" :/ uri))) .filter(is_virtual.is(false)) - .select(id).load::<i64>(&mut self.sqlite) - .map_err(|err| format!("{err}"))? + .select(id).load::<i64>(&mut self.sqlite)? )), LktUriField::Language => Ok(with_dsl!(kara_lang => kara_lang @@ -281,8 +292,7 @@ impl LktDatabaseConnection { .inner_join(schema::kara::table) .filter(schema::kara::is_virtual.is(false)) .select(id) - .load::<i64>(&mut self.sqlite) - .map_err(|err| format!("{err}"))? + .load::<i64>(&mut self.sqlite)? )), LktUriField::Search | LktUriField::FuzzySearch => { @@ -291,30 +301,26 @@ impl LktDatabaseConnection { } LktUriField::Playlist => { - let playlist = uri_str!("playlist" :/ uri); - Err(format!( - "need to implement playlists, can't filter for {playlist}" - )) + let _ = uri_str!("playlist" :/ uri); + todo!() } } } /// Get all infos about a kara, its metadata, its tags, repo, repo id, /// languages, karamakers... - pub fn get_info(&mut self, local_id: i64) -> Result<(), String> { + pub fn get_kara_info(&mut self, local_id: i64) -> LktDatabaseResult<()> { let _repo: String = with_dsl!(repo_kara => repo_kara .filter(local_kara_id.is(local_id)) .inner_join(schema::repo::table) .select(schema::repo::name) - .first::<String>(&mut self.sqlite) - .map_err(|err| format!("failed to get parent repo of kara {local_id}: {err}"))? + .first::<String>(&mut self.sqlite)? ); let _kara_maker: Vec<String> = with_dsl!(kara => kara .filter(id.is(local_id)) .inner_join(schema::kara_maker::table) .select(schema::kara_maker::name) - .load::<String>(&mut self.sqlite) - .map_err(|err| format!("failed to get makers for kara {local_id}: {err}"))? + .load::<String>(&mut self.sqlite)? ); todo!() } diff --git a/src/rust/lektor_db/src/error.rs b/src/rust/lektor_db/src/error.rs index 2655c99d0bf1727e04ea63ef5f71175d450dcc23..05e562c974237ba747fdc9ee7ae6592dde399194 100644 --- a/src/rust/lektor_db/src/error.rs +++ b/src/rust/lektor_db/src/error.rs @@ -1,38 +1,41 @@ +#[derive(Debug, thiserror::Error)] pub enum LktDatabaseError { - DieselConnection(diesel::ConnectionError), - DieselResult(diesel::result::Error), - IntegerOverflow, + #[error("diesel connection error: {0}")] + DieselConnection(#[from] diesel::ConnectionError), + + #[error("diesel result error: {0}")] + DieselResult(#[from] diesel::result::Error), + + #[error("integer overflow: {0}")] + IntegerOverflow(#[from] std::num::TryFromIntError), + + #[error("database error: {0}")] String(String), + + #[error("database error: {0}")] + Str(&'static str), } -pub type LktDatabaseResult<T> = Result<T, LktDatabaseError>; +#[derive(Debug, thiserror::Error)] +pub enum LktQueueError { + #[error("the queue is empty")] + EmptyQueue, -impl From<diesel::ConnectionError> for LktDatabaseError { - fn from(err: diesel::ConnectionError) -> Self { - LktDatabaseError::DieselConnection(err) - } -} + #[error("the queue has no next kara to play after the current one")] + NoNextKara, -impl From<diesel::result::Error> for LktDatabaseError { - fn from(err: diesel::result::Error) -> Self { - LktDatabaseError::DieselResult(err) - } -} + #[error("index out of bound in queue: {0}")] + IndexOutOfBound(usize), -impl From<std::num::TryFromIntError> for LktDatabaseError { - fn from(_: std::num::TryFromIntError) -> Self { - LktDatabaseError::IntegerOverflow - } -} + #[error("kara id was not found in queue: {0}")] + IdNotFound(i64), + + #[error("queue error: {0}")] + String(String), -impl std::fmt::Display for LktDatabaseError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use LktDatabaseError::*; - match self { - DieselConnection(err) => write!(f, "diesel connection error: {err}"), - DieselResult(err) => write!(f, "diesel result error: {err}"), - IntegerOverflow => f.write_str("integer overflow when casting to i32"), - String(str) => f.write_str(str), - } - } + #[error("queue error: {0}")] + Str(&'static str), } + +pub type LktDatabaseResult<T> = Result<T, LktDatabaseError>; +pub type LktQueueResult<T> = Result<T, LktQueueError>; diff --git a/src/rust/lektor_db/src/queue.rs b/src/rust/lektor_db/src/queue.rs index 558dc3bdaddbf45a17debb0b76478217c11d2a17..a6426b95b5b2ae92c950234e24d1151c09f9ec34 100644 --- a/src/rust/lektor_db/src/queue.rs +++ b/src/rust/lektor_db/src/queue.rs @@ -50,8 +50,8 @@ impl_into_for_priority!(usize, isize); /// The iterator for the database queue. The iterator returns references to /// karas' id to protect modifications while a reference to some karas are /// present... -pub struct LktDatabaseQueueIter<'a> { - queue: &'a LktDatabaseQueue, +pub struct LktQueueIter<'a> { + queue: &'a LktQueue, priority: Option<usize>, index: usize, } @@ -59,12 +59,12 @@ pub struct LktDatabaseQueueIter<'a> { /// The iterator for a range of karas from the database queue. The iterator /// returns references to karas' id to protect modifications while a reference /// to some karas are present... -pub struct LktDatabaseQueueRangeIter<'a> { - iterator: LktDatabaseQueueIter<'a>, +pub struct LktQueueRangeIter<'a> { + iterator: LktQueueIter<'a>, remaining: usize, } -impl<'a> Iterator for LktDatabaseQueueIter<'a> { +impl<'a> Iterator for LktQueueIter<'a> { type Item = i64; fn next(&mut self) -> Option<Self::Item> { @@ -84,7 +84,7 @@ impl<'a> Iterator for LktDatabaseQueueIter<'a> { } } -impl<'a> Iterator for LktDatabaseQueueRangeIter<'a> { +impl<'a> Iterator for LktQueueRangeIter<'a> { type Item = i64; fn next(&mut self) -> Option<Self::Item> { @@ -100,15 +100,15 @@ impl<'a> Iterator for LktDatabaseQueueRangeIter<'a> { /// The queue datastructure used for storing karas in the queue. #[derive(Debug, Default)] -pub struct LktDatabaseQueue { +pub struct LktQueue { levels: [VecDeque<i64>; LKT_DATABASE_QUEUES_COUNT], current: Option<i64>, } -impl LktDatabaseQueue { +impl LktQueue { /// Get the current kara in the queue. - pub fn current_kara(&self) -> Option<i64> { - self.current + pub fn current_kara(&self) -> LktQueueResult<i64> { + self.current.ok_or(LktQueueError::EmptyQueue) } /// Add a kara in the queue with a given priority. @@ -128,14 +128,14 @@ impl LktDatabaseQueue { } /// Iterate over the queue content. - pub fn iter(&self) -> LktDatabaseQueueIter { + pub fn iter(&self) -> LktQueueIter { let priority = self .levels .iter() .enumerate() .rev() .find_map(|(priority, content)| (!content.is_empty()).then_some(priority)); - LktDatabaseQueueIter { + LktQueueIter { queue: self, priority, index: 0, @@ -143,25 +143,26 @@ impl LktDatabaseQueue { } /// Peek the next kara to play in the queue. - pub fn peek_next(&self) -> Option<i64> { - self.iter().next() + pub fn peek_next(&self) -> LktQueueResult<i64> { + self.iter().next().ok_or(LktQueueError::NoNextKara) } /// Pop the next kara from the queue and set the current kara to the /// returned value. - pub fn pop_next(&mut self) -> Option<i64> { + pub fn pop_next(&mut self) -> LktQueueResult<i64> { let level = self .levels .iter_mut() .rev() - .find(|content| !content.is_empty())?; + .find(|content| !content.is_empty()) + .ok_or(LktQueueError::EmptyQueue)?; let current = level.pop_front(); self.current = current; - current + current.ok_or(LktQueueError::EmptyQueue) } /// Get a range of karas in queue. - pub fn range(&self, range: Range<usize>) -> LktDatabaseQueueRangeIter { + pub fn range(&self, range: Range<usize>) -> LktQueueRangeIter { let mut iter = self.iter(); let remaining = range.end.saturating_sub(range.start); if remaining >= 1 { @@ -169,7 +170,7 @@ impl LktDatabaseQueue { iter.next(); } } - LktDatabaseQueueRangeIter { + LktQueueRangeIter { iterator: iter, remaining, } @@ -177,31 +178,31 @@ impl LktDatabaseQueue { /// Swap two karas with their positions. Returns the two karas that were /// swaped, the order is not defined. On error returns what went wrong. - pub fn swap(&mut self, _from: usize, _to: usize) -> Result<(i64, i64), String> { + pub fn swap(&mut self, _from: usize, _to: usize) -> LktQueueResult<(i64, i64)> { todo!() } /// Delete all occurencies of a given kara in the queue. Returns the number /// of deleted entries. - pub fn delete_all(&mut self, _kara_id: i64) -> usize { + pub fn delete_all(&mut self, _kara_id: i64) -> LktQueueResult<usize> { todo!() } /// Clear the queue, returns the number of delete entries. This also clears /// the current kara if the maximal level is passed. Only clears up to /// (including) the passed level. - pub fn clear(&mut self, _up_to: LktDatabasePriority) -> usize { + pub fn clear(&mut self, _up_to: LktDatabasePriority) -> LktQueueResult<usize> { todo!() } /// Crop the queue. Only the current kara remains. Returns the number of /// deleted entries. - pub fn crop(&mut self) -> usize { + pub fn crop(&mut self) -> LktQueueResult<usize> { todo!() } /// Shuffle the queue. Do the shuffle in a per-level way. - pub fn shuffle(&mut self) { + pub fn shuffle(&mut self) -> LktQueueResult<()> { todo!() } }