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!()
     }
 }