From d7fb9961836ff99ac57f21eb213ec4f340598d3f Mon Sep 17 00:00:00 2001 From: Elias Projahn Date: Thu, 4 Feb 2021 17:43:36 +0100 Subject: [PATCH] Add custom error type to database module --- src/backend/database/ensembles.rs | 11 ++- src/backend/database/error.rs | 26 +++++++ src/backend/database/instruments.rs | 11 ++- src/backend/database/medium.rs | 33 +++++---- src/backend/database/mod.rs | 8 ++- src/backend/database/persons.rs | 11 ++- src/backend/database/recordings.rs | 45 ++++++++---- src/backend/database/thread.rs | 107 ++++++++++++++-------------- src/backend/database/works.rs | 39 ++++++---- src/backend/error.rs | 55 ++++++++++++++ src/meson.build | 2 + 11 files changed, 232 insertions(+), 116 deletions(-) create mode 100644 src/backend/database/error.rs create mode 100644 src/backend/error.rs diff --git a/src/backend/database/ensembles.rs b/src/backend/database/ensembles.rs index 62475a3..13dd6d1 100644 --- a/src/backend/database/ensembles.rs +++ b/src/backend/database/ensembles.rs @@ -1,6 +1,5 @@ use super::schema::ensembles; -use super::Database; -use anyhow::Result; +use super::{Database, DatabaseResult}; use diesel::prelude::*; use serde::{Deserialize, Serialize}; @@ -14,7 +13,7 @@ pub struct Ensemble { impl Database { /// Update an existing ensemble or insert a new one. - pub fn update_ensemble(&self, ensemble: Ensemble) -> Result<()> { + pub fn update_ensemble(&self, ensemble: Ensemble) -> DatabaseResult<()> { self.defer_foreign_keys()?; self.connection.transaction(|| { @@ -27,7 +26,7 @@ impl Database { } /// Get an existing ensemble. - pub fn get_ensemble(&self, id: &str) -> Result> { + pub fn get_ensemble(&self, id: &str) -> DatabaseResult> { let ensemble = ensembles::table .filter(ensembles::id.eq(id)) .load::(&self.connection)? @@ -38,14 +37,14 @@ impl Database { } /// Delete an existing ensemble. - pub fn delete_ensemble(&self, id: &str) -> Result<()> { + pub fn delete_ensemble(&self, id: &str) -> DatabaseResult<()> { diesel::delete(ensembles::table.filter(ensembles::id.eq(id))).execute(&self.connection)?; Ok(()) } /// Get all existing ensembles. - pub fn get_ensembles(&self) -> Result> { + pub fn get_ensembles(&self) -> DatabaseResult> { let ensembles = ensembles::table.load::(&self.connection)?; Ok(ensembles) diff --git a/src/backend/database/error.rs b/src/backend/database/error.rs new file mode 100644 index 0000000..ac8a739 --- /dev/null +++ b/src/backend/database/error.rs @@ -0,0 +1,26 @@ +use thiserror::Error; + +/// Error that happens within the database module. +#[derive(Error, Debug)] +pub enum DatabaseError { + #[error(transparent)] + ConnectionError(#[from] diesel::result::ConnectionError), + + #[error(transparent)] + MigrationsError(#[from] diesel_migrations::RunMigrationsError), + + #[error(transparent)] + QueryError(#[from] diesel::result::Error), + + #[error(transparent)] + SendError(#[from] std::sync::mpsc::SendError), + + #[error(transparent)] + ReceiveError(#[from] futures_channel::oneshot::Canceled), + + #[error("Database error: {0}")] + Other(String), +} + +/// Return type for database methods. +pub type DatabaseResult = Result; diff --git a/src/backend/database/instruments.rs b/src/backend/database/instruments.rs index 1083a30..0848b00 100644 --- a/src/backend/database/instruments.rs +++ b/src/backend/database/instruments.rs @@ -1,6 +1,5 @@ use super::schema::instruments; -use super::Database; -use anyhow::Result; +use super::{Database, DatabaseResult}; use diesel::prelude::*; use serde::{Deserialize, Serialize}; @@ -14,7 +13,7 @@ pub struct Instrument { impl Database { /// Update an existing instrument or insert a new one. - pub fn update_instrument(&self, instrument: Instrument) -> Result<()> { + pub fn update_instrument(&self, instrument: Instrument) -> DatabaseResult<()> { self.defer_foreign_keys()?; self.connection.transaction(|| { @@ -27,7 +26,7 @@ impl Database { } /// Get an existing instrument. - pub fn get_instrument(&self, id: &str) -> Result> { + pub fn get_instrument(&self, id: &str) -> DatabaseResult> { let instrument = instruments::table .filter(instruments::id.eq(id)) .load::(&self.connection)? @@ -38,7 +37,7 @@ impl Database { } /// Delete an existing instrument. - pub fn delete_instrument(&self, id: &str) -> Result<()> { + pub fn delete_instrument(&self, id: &str) -> DatabaseResult<()> { diesel::delete(instruments::table.filter(instruments::id.eq(id))) .execute(&self.connection)?; @@ -46,7 +45,7 @@ impl Database { } /// Get all existing instruments. - pub fn get_instruments(&self) -> Result> { + pub fn get_instruments(&self) -> DatabaseResult> { let instruments = instruments::table.load::(&self.connection)?; Ok(instruments) diff --git a/src/backend/database/medium.rs b/src/backend/database/medium.rs index 0512c22..b0626c8 100644 --- a/src/backend/database/medium.rs +++ b/src/backend/database/medium.rs @@ -1,7 +1,6 @@ use super::generate_id; use super::schema::{mediums, recordings, track_sets, tracks}; -use super::{Database, Recording}; -use anyhow::{anyhow, Error, Result}; +use super::{Database, DatabaseError, Recording, DatabaseResult}; use diesel::prelude::*; use serde::{Deserialize, Serialize}; @@ -80,10 +79,10 @@ struct TrackRow { impl Database { /// Update an existing medium or insert a new one. - pub fn update_medium(&self, medium: Medium) -> Result<()> { + pub fn update_medium(&self, medium: Medium) -> DatabaseResult<()> { self.defer_foreign_keys()?; - self.connection.transaction::<(), Error, _>(|| { + self.connection.transaction::<(), DatabaseError, _>(|| { let medium_id = &medium.id; // This will also delete the track sets and tracks. @@ -153,7 +152,7 @@ impl Database { } /// Get an existing medium. - pub fn get_medium(&self, id: &str) -> Result> { + pub fn get_medium(&self, id: &str) -> DatabaseResult> { let row = mediums::table .filter(mediums::id.eq(id)) .load::(&self.connection)? @@ -170,13 +169,13 @@ impl Database { /// Delete a medium and all of its tracks. This will fail, if the music /// library contains audio files referencing any of those tracks. - pub fn delete_medium(&self, id: &str) -> Result<()> { + pub fn delete_medium(&self, id: &str) -> DatabaseResult<()> { diesel::delete(mediums::table.filter(mediums::id.eq(id))).execute(&self.connection)?; Ok(()) } /// Get all available track sets for a recording. - pub fn get_track_sets(&self, recording_id: &str) -> Result> { + pub fn get_track_sets(&self, recording_id: &str) -> DatabaseResult> { let mut track_sets: Vec = Vec::new(); let rows = track_sets::table @@ -194,7 +193,7 @@ impl Database { } /// Retrieve all available information on a medium from related tables. - fn get_medium_data(&self, row: MediumRow) -> Result { + fn get_medium_data(&self, row: MediumRow) -> DatabaseResult { let track_set_rows = track_sets::table .filter(track_sets::medium.eq(&row.id)) .order_by(track_sets::index) @@ -218,12 +217,16 @@ impl Database { } /// Convert a track set row from the database to an actual track set. - fn get_track_set_from_row(&self, row: TrackSetRow) -> Result { + fn get_track_set_from_row(&self, row: TrackSetRow) -> DatabaseResult { let recording_id = row.recording; let recording = self .get_recording(&recording_id)? - .ok_or_else(|| anyhow!("No recording with ID: {}", recording_id))?; + .ok_or(DatabaseError::Other(format!( + "Failed to get recording ({}) for track set ({}).", + recording_id, + row.id, + )))?; let track_rows = tracks::table .filter(tracks::track_set.eq(row.id)) @@ -236,8 +239,14 @@ impl Database { let work_parts = track_row .work_parts .split(',') - .map(|part_index| Ok(str::parse(part_index)?)) - .collect::>>()?; + .map(|part_index| { + str::parse(part_index) + .or(Err(DatabaseError::Other(format!( + "Failed to parse part index from '{}'.", + track_row.work_parts, + )))?) + }) + .collect::>>()?; let track = Track { work_parts, diff --git a/src/backend/database/mod.rs b/src/backend/database/mod.rs index ef87e5a..c0f8bbd 100644 --- a/src/backend/database/mod.rs +++ b/src/backend/database/mod.rs @@ -1,9 +1,11 @@ -use anyhow::Result; use diesel::prelude::*; pub mod ensembles; pub use ensembles::*; +pub mod error; +pub use error::*; + pub mod instruments; pub use instruments::*; @@ -42,7 +44,7 @@ pub struct Database { impl Database { /// Create a new database interface and run migrations if necessary. - pub fn new(file_name: &str) -> Result { + pub fn new(file_name: &str) -> DatabaseResult { let connection = SqliteConnection::establish(file_name)?; diesel::sql_query("PRAGMA foreign_keys = ON").execute(&connection)?; @@ -52,7 +54,7 @@ impl Database { } /// Defer all foreign keys for the next transaction. - fn defer_foreign_keys(&self) -> Result<()> { + fn defer_foreign_keys(&self) -> DatabaseResult<()> { diesel::sql_query("PRAGMA defer_foreign_keys = ON").execute(&self.connection)?; Ok(()) } diff --git a/src/backend/database/persons.rs b/src/backend/database/persons.rs index ab84271..64efe66 100644 --- a/src/backend/database/persons.rs +++ b/src/backend/database/persons.rs @@ -1,6 +1,5 @@ use super::schema::persons; -use super::Database; -use anyhow::Result; +use super::{Database, DatabaseResult}; use diesel::prelude::*; use serde::{Deserialize, Serialize}; @@ -27,7 +26,7 @@ impl Person { impl Database { /// Update an existing person or insert a new one. - pub fn update_person(&self, person: Person) -> Result<()> { + pub fn update_person(&self, person: Person) -> DatabaseResult<()> { self.defer_foreign_keys()?; self.connection.transaction(|| { @@ -40,7 +39,7 @@ impl Database { } /// Get an existing person. - pub fn get_person(&self, id: &str) -> Result> { + pub fn get_person(&self, id: &str) -> DatabaseResult> { let person = persons::table .filter(persons::id.eq(id)) .load::(&self.connection)? @@ -51,14 +50,14 @@ impl Database { } /// Delete an existing person. - pub fn delete_person(&self, id: &str) -> Result<()> { + pub fn delete_person(&self, id: &str) -> DatabaseResult<()> { diesel::delete(persons::table.filter(persons::id.eq(id))).execute(&self.connection)?; Ok(()) } /// Get all existing persons. - pub fn get_persons(&self) -> Result> { + pub fn get_persons(&self) -> DatabaseResult> { let persons = persons::table.load::(&self.connection)?; Ok(persons) diff --git a/src/backend/database/recordings.rs b/src/backend/database/recordings.rs index b2ccf00..ffe3dae 100644 --- a/src/backend/database/recordings.rs +++ b/src/backend/database/recordings.rs @@ -1,7 +1,6 @@ use super::generate_id; use super::schema::{ensembles, performances, persons, recordings}; -use super::{Database, Ensemble, Instrument, Person, Work}; -use anyhow::{anyhow, Error, Result}; +use super::{Database, Ensemble, DatabaseError, Instrument, Person, DatabaseResult, Work}; use diesel::prelude::*; use serde::{Deserialize, Serialize}; @@ -120,9 +119,9 @@ impl Recording { impl Database { /// Update an existing recording or insert a new one. // TODO: Think about whether to also insert the other items. - pub fn update_recording(&self, recording: Recording) -> Result<()> { + pub fn update_recording(&self, recording: Recording) -> DatabaseResult<()> { self.defer_foreign_keys()?; - self.connection.transaction::<(), Error, _>(|| { + self.connection.transaction::<(), DatabaseError, _>(|| { let recording_id = &recording.id; self.delete_recording(recording_id)?; @@ -180,7 +179,7 @@ impl Database { } /// Check whether the database contains a recording. - pub fn recording_exists(&self, id: &str) -> Result { + pub fn recording_exists(&self, id: &str) -> DatabaseResult { let exists = recordings::table .filter(recordings::id.eq(id)) .load::(&self.connection)? @@ -191,7 +190,7 @@ impl Database { } /// Get an existing recording. - pub fn get_recording(&self, id: &str) -> Result> { + pub fn get_recording(&self, id: &str) -> DatabaseResult> { let row = recordings::table .filter(recordings::id.eq(id)) .load::(&self.connection)? @@ -207,7 +206,7 @@ impl Database { } /// Retrieve all available information on a recording from related tables. - fn get_recording_data(&self, row: RecordingRow) -> Result { + fn get_recording_data(&self, row: RecordingRow) -> DatabaseResult { let mut performance_descriptions: Vec = Vec::new(); let performance_rows = performances::table @@ -219,21 +218,33 @@ impl Database { person: match row.person { Some(id) => Some( self.get_person(&id)? - .ok_or(anyhow!("No person with ID: {}", id))?, + .ok_or(DatabaseError::Other(format!( + "Failed to get person ({}) for recording ({}).", + id, + row.id, + )))? ), None => None, }, ensemble: match row.ensemble { Some(id) => Some( self.get_ensemble(&id)? - .ok_or(anyhow!("No ensemble with ID: {}", id))?, + .ok_or(DatabaseError::Other(format!( + "Failed to get ensemble ({}) for recording ({}).", + id, + row.id, + )))? ), None => None, }, role: match row.role { Some(id) => Some( self.get_instrument(&id)? - .ok_or(anyhow!("No instrument with ID: {}", id))?, + .ok_or(DatabaseError::Other(format!( + "Failed to get instrument ({}) for recording ({}).", + id, + row.id, + )))? ), None => None, }, @@ -243,7 +254,11 @@ impl Database { let work_id = &row.work; let work = self .get_work(work_id)? - .ok_or(anyhow!("Work doesn't exist: {}", work_id))?; + .ok_or(DatabaseError::Other(format!( + "Failed to get work ({}) for recording ({}).", + work_id, + row.id, + )))?; let recording_description = Recording { id: row.id, @@ -256,7 +271,7 @@ impl Database { } /// Get all available information on all recordings where a person is performing. - pub fn get_recordings_for_person(&self, person_id: &str) -> Result> { + pub fn get_recordings_for_person(&self, person_id: &str) -> DatabaseResult> { let mut recordings: Vec = Vec::new(); let rows = recordings::table @@ -274,7 +289,7 @@ impl Database { } /// Get all available information on all recordings where an ensemble is performing. - pub fn get_recordings_for_ensemble(&self, ensemble_id: &str) -> Result> { + pub fn get_recordings_for_ensemble(&self, ensemble_id: &str) -> DatabaseResult> { let mut recordings: Vec = Vec::new(); let rows = recordings::table @@ -292,7 +307,7 @@ impl Database { } /// Get allavailable information on all recordings of a work. - pub fn get_recordings_for_work(&self, work_id: &str) -> Result> { + pub fn get_recordings_for_work(&self, work_id: &str) -> DatabaseResult> { let mut recordings: Vec = Vec::new(); let rows = recordings::table @@ -308,7 +323,7 @@ impl Database { /// Delete an existing recording. This will fail if there are still references to this /// recording from other tables that are not directly part of the recording data. - pub fn delete_recording(&self, id: &str) -> Result<()> { + pub fn delete_recording(&self, id: &str) -> DatabaseResult<()> { diesel::delete(recordings::table.filter(recordings::id.eq(id))) .execute(&self.connection)?; Ok(()) diff --git a/src/backend/database/thread.rs b/src/backend/database/thread.rs index 2ce86fe..87f9772 100644 --- a/src/backend/database/thread.rs +++ b/src/backend/database/thread.rs @@ -1,37 +1,36 @@ use super::*; -use anyhow::Result; use futures_channel::oneshot; use futures_channel::oneshot::Sender; use std::sync::mpsc; use std::thread; /// An action the database thread can perform. -enum Action { - UpdatePerson(Person, Sender>), - GetPerson(String, Sender>>), - DeletePerson(String, Sender>), - GetPersons(Sender>>), - UpdateInstrument(Instrument, Sender>), - GetInstrument(String, Sender>>), - DeleteInstrument(String, Sender>), - GetInstruments(Sender>>), - UpdateWork(Work, Sender>), - DeleteWork(String, Sender>), - GetWorks(String, Sender>>), - UpdateEnsemble(Ensemble, Sender>), - GetEnsemble(String, Sender>>), - DeleteEnsemble(String, Sender>), - GetEnsembles(Sender>>), - UpdateRecording(Recording, Sender>), - DeleteRecording(String, Sender>), - GetRecordingsForPerson(String, Sender>>), - GetRecordingsForEnsemble(String, Sender>>), - GetRecordingsForWork(String, Sender>>), - RecordingExists(String, Sender>), - UpdateMedium(Medium, Sender>), - GetMedium(String, Sender>>), - DeleteMedium(String, Sender>), - GetTrackSets(String, Sender>>), +pub enum Action { + UpdatePerson(Person, Sender>), + GetPerson(String, Sender>>), + DeletePerson(String, Sender>), + GetPersons(Sender>>), + UpdateInstrument(Instrument, Sender>), + GetInstrument(String, Sender>>), + DeleteInstrument(String, Sender>), + GetInstruments(Sender>>), + UpdateWork(Work, Sender>), + DeleteWork(String, Sender>), + GetWorks(String, Sender>>), + UpdateEnsemble(Ensemble, Sender>), + GetEnsemble(String, Sender>>), + DeleteEnsemble(String, Sender>), + GetEnsembles(Sender>>), + UpdateRecording(Recording, Sender>), + DeleteRecording(String, Sender>), + GetRecordingsForPerson(String, Sender>>), + GetRecordingsForEnsemble(String, Sender>>), + GetRecordingsForWork(String, Sender>>), + RecordingExists(String, Sender>), + UpdateMedium(Medium, Sender>), + GetMedium(String, Sender>>), + DeleteMedium(String, Sender>), + GetTrackSets(String, Sender>>), Stop(Sender<()>), } @@ -44,7 +43,7 @@ pub struct DbThread { impl DbThread { /// Create a new database connection in a background thread. - pub async fn new(path: String) -> Result { + pub async fn new(path: String) -> DatabaseResult { let (action_sender, action_receiver) = mpsc::channel(); let (ready_sender, ready_receiver) = oneshot::channel(); @@ -150,14 +149,14 @@ impl DbThread { } /// Update an existing person or insert a new one. - pub async fn update_person(&self, person: Person) -> Result<()> { + pub async fn update_person(&self, person: Person) -> DatabaseResult<()> { let (sender, receiver) = oneshot::channel(); self.action_sender.send(UpdatePerson(person, sender))?; receiver.await? } /// Get an existing person. - pub async fn get_person(&self, id: &str) -> Result> { + pub async fn get_person(&self, id: &str) -> DatabaseResult> { let (sender, receiver) = oneshot::channel(); self.action_sender.send(GetPerson(id.to_string(), sender))?; receiver.await? @@ -165,7 +164,7 @@ impl DbThread { /// Delete an existing person. This will fail, if there are still other items referencing /// this person. - pub async fn delete_person(&self, id: &str) -> Result<()> { + pub async fn delete_person(&self, id: &str) -> DatabaseResult<()> { let (sender, receiver) = oneshot::channel(); self.action_sender .send(DeletePerson(id.to_string(), sender))?; @@ -173,14 +172,14 @@ impl DbThread { } /// Get all existing persons. - pub async fn get_persons(&self) -> Result> { + pub async fn get_persons(&self) -> DatabaseResult> { let (sender, receiver) = oneshot::channel(); self.action_sender.send(GetPersons(sender))?; receiver.await? } /// Update an existing instrument or insert a new one. - pub async fn update_instrument(&self, instrument: Instrument) -> Result<()> { + pub async fn update_instrument(&self, instrument: Instrument) -> DatabaseResult<()> { let (sender, receiver) = oneshot::channel(); self.action_sender .send(UpdateInstrument(instrument, sender))?; @@ -188,7 +187,7 @@ impl DbThread { } /// Get an existing instrument. - pub async fn get_instrument(&self, id: &str) -> Result> { + pub async fn get_instrument(&self, id: &str) -> DatabaseResult> { let (sender, receiver) = oneshot::channel(); self.action_sender .send(GetInstrument(id.to_string(), sender))?; @@ -197,7 +196,7 @@ impl DbThread { /// Delete an existing instrument. This will fail, if there are still other items referencing /// this instrument. - pub async fn delete_instrument(&self, id: &str) -> Result<()> { + pub async fn delete_instrument(&self, id: &str) -> DatabaseResult<()> { let (sender, receiver) = oneshot::channel(); self.action_sender .send(DeleteInstrument(id.to_string(), sender))?; @@ -205,14 +204,14 @@ impl DbThread { } /// Get all existing instruments. - pub async fn get_instruments(&self) -> Result> { + pub async fn get_instruments(&self) -> DatabaseResult> { let (sender, receiver) = oneshot::channel(); self.action_sender.send(GetInstruments(sender))?; receiver.await? } /// Update an existing work or insert a new one. - pub async fn update_work(&self, work: Work) -> Result<()> { + pub async fn update_work(&self, work: Work) -> DatabaseResult<()> { let (sender, receiver) = oneshot::channel(); self.action_sender.send(UpdateWork(work, sender))?; receiver.await? @@ -220,7 +219,7 @@ impl DbThread { /// Delete an existing work. This will fail, if there are still other items referencing /// this work. - pub async fn delete_work(&self, id: &str) -> Result<()> { + pub async fn delete_work(&self, id: &str) -> DatabaseResult<()> { let (sender, receiver) = oneshot::channel(); self.action_sender .send(DeleteWork(id.to_string(), sender))?; @@ -228,7 +227,7 @@ impl DbThread { } /// Get information on all existing works by a composer. - pub async fn get_works(&self, person_id: &str) -> Result> { + pub async fn get_works(&self, person_id: &str) -> DatabaseResult> { let (sender, receiver) = oneshot::channel(); self.action_sender .send(GetWorks(person_id.to_string(), sender))?; @@ -236,14 +235,14 @@ impl DbThread { } /// Update an existing ensemble or insert a new one. - pub async fn update_ensemble(&self, ensemble: Ensemble) -> Result<()> { + pub async fn update_ensemble(&self, ensemble: Ensemble) -> DatabaseResult<()> { let (sender, receiver) = oneshot::channel(); self.action_sender.send(UpdateEnsemble(ensemble, sender))?; receiver.await? } /// Get an existing ensemble. - pub async fn get_ensemble(&self, id: &str) -> Result> { + pub async fn get_ensemble(&self, id: &str) -> DatabaseResult> { let (sender, receiver) = oneshot::channel(); self.action_sender .send(GetEnsemble(id.to_string(), sender))?; @@ -252,7 +251,7 @@ impl DbThread { /// Delete an existing ensemble. This will fail, if there are still other items referencing /// this ensemble. - pub async fn delete_ensemble(&self, id: &str) -> Result<()> { + pub async fn delete_ensemble(&self, id: &str) -> DatabaseResult<()> { let (sender, receiver) = oneshot::channel(); self.action_sender .send(DeleteEnsemble(id.to_string(), sender))?; @@ -260,14 +259,14 @@ impl DbThread { } /// Get all existing ensembles. - pub async fn get_ensembles(&self) -> Result> { + pub async fn get_ensembles(&self) -> DatabaseResult> { let (sender, receiver) = oneshot::channel(); self.action_sender.send(GetEnsembles(sender))?; receiver.await? } /// Update an existing recording or insert a new one. - pub async fn update_recording(&self, recording: Recording) -> Result<()> { + pub async fn update_recording(&self, recording: Recording) -> DatabaseResult<()> { let (sender, receiver) = oneshot::channel(); self.action_sender .send(UpdateRecording(recording, sender))?; @@ -275,7 +274,7 @@ impl DbThread { } /// Delete an existing recording. - pub async fn delete_recording(&self, id: &str) -> Result<()> { + pub async fn delete_recording(&self, id: &str) -> DatabaseResult<()> { let (sender, receiver) = oneshot::channel(); self.action_sender .send(DeleteRecording(id.to_string(), sender))?; @@ -283,7 +282,7 @@ impl DbThread { } /// Get information on all recordings in which a person performs. - pub async fn get_recordings_for_person(&self, person_id: &str) -> Result> { + pub async fn get_recordings_for_person(&self, person_id: &str) -> DatabaseResult> { let (sender, receiver) = oneshot::channel(); self.action_sender .send(GetRecordingsForPerson(person_id.to_string(), sender))?; @@ -291,7 +290,7 @@ impl DbThread { } /// Get information on all recordings in which an ensemble performs. - pub async fn get_recordings_for_ensemble(&self, ensemble_id: &str) -> Result> { + pub async fn get_recordings_for_ensemble(&self, ensemble_id: &str) -> DatabaseResult> { let (sender, receiver) = oneshot::channel(); self.action_sender .send(GetRecordingsForEnsemble(ensemble_id.to_string(), sender))?; @@ -299,7 +298,7 @@ impl DbThread { } /// Get information on all recordings of a work. - pub async fn get_recordings_for_work(&self, work_id: &str) -> Result> { + pub async fn get_recordings_for_work(&self, work_id: &str) -> DatabaseResult> { let (sender, receiver) = oneshot::channel(); self.action_sender .send(GetRecordingsForWork(work_id.to_string(), sender))?; @@ -307,7 +306,7 @@ impl DbThread { } /// Check whether a recording exists within the database. - pub async fn recording_exists(&self, id: &str) -> Result { + pub async fn recording_exists(&self, id: &str) -> DatabaseResult { let (sender, receiver) = oneshot::channel(); self.action_sender .send(RecordingExists(id.to_string(), sender))?; @@ -315,7 +314,7 @@ impl DbThread { } /// Update an existing medium or insert a new one. - pub async fn update_medium(&self, medium: Medium) -> Result<()> { + pub async fn update_medium(&self, medium: Medium) -> DatabaseResult<()> { let (sender, receiver) = oneshot::channel(); self.action_sender.send(UpdateMedium(medium, sender))?; receiver.await? @@ -323,7 +322,7 @@ impl DbThread { /// Delete an existing medium. This will fail, if there are still other /// items referencing this medium. - pub async fn delete_medium(&self, id: &str) -> Result<()> { + pub async fn delete_medium(&self, id: &str) -> DatabaseResult<()> { let (sender, receiver) = oneshot::channel(); self.action_sender @@ -333,21 +332,21 @@ impl DbThread { } /// Get an existing medium. - pub async fn get_medium(&self, id: &str) -> Result> { + pub async fn get_medium(&self, id: &str) -> DatabaseResult> { let (sender, receiver) = oneshot::channel(); self.action_sender.send(GetMedium(id.to_owned(), sender))?; receiver.await? } /// Get all track sets for a recording. - pub async fn get_track_sets(&self, recording_id: &str) -> Result> { + pub async fn get_track_sets(&self, recording_id: &str) -> DatabaseResult> { let (sender, receiver) = oneshot::channel(); self.action_sender.send(GetTrackSets(recording_id.to_owned(), sender))?; receiver.await? } /// Stop the database thread. Any future access to the database will fail. - pub async fn stop(&self) -> Result<()> { + pub async fn stop(&self) -> DatabaseResult<()> { let (sender, receiver) = oneshot::channel(); self.action_sender.send(Stop(sender))?; Ok(receiver.await?) diff --git a/src/backend/database/works.rs b/src/backend/database/works.rs index a8ca80a..418d8f1 100644 --- a/src/backend/database/works.rs +++ b/src/backend/database/works.rs @@ -1,7 +1,6 @@ use super::generate_id; use super::schema::{instrumentations, work_parts, work_sections, works}; -use super::{Database, Instrument, Person}; -use anyhow::{anyhow, Error, Result}; +use super::{Database, DatabaseError, Instrument, Person, DatabaseResult}; use diesel::prelude::*; use diesel::{Insertable, Queryable}; use serde::{Deserialize, Serialize}; @@ -106,10 +105,10 @@ impl Work { impl Database { /// Update an existing work or insert a new one. // TODO: Think about also inserting related items. - pub fn update_work(&self, work: Work) -> Result<()> { + pub fn update_work(&self, work: Work) -> DatabaseResult<()> { self.defer_foreign_keys()?; - self.connection.transaction::<(), Error, _>(|| { + self.connection.transaction::<(), DatabaseError, _>(|| { let work_id = &work.id; self.delete_work(work_id)?; @@ -163,7 +162,7 @@ impl Database { let row = WorkPartRow { id: rand::random(), work: work_id.to_string(), - part_index: index.try_into()?, + part_index: index as i64, title: part.title, composer: part.composer.map(|person| person.id), }; @@ -178,7 +177,7 @@ impl Database { id: rand::random(), work: work_id.to_string(), title: section.title, - before_index: section.before_index.try_into()?, + before_index: section.before_index as i64, }; diesel::insert_into(work_sections::table) @@ -195,7 +194,7 @@ impl Database { } /// Get an existing work. - pub fn get_work(&self, id: &str) -> Result> { + pub fn get_work(&self, id: &str) -> DatabaseResult> { let row = works::table .filter(works::id.eq(id)) .load::(&self.connection)? @@ -211,7 +210,7 @@ impl Database { } /// Retrieve all available information on a work from related tables. - fn get_work_data(&self, row: WorkRow) -> Result { + fn get_work_data(&self, row: WorkRow) -> DatabaseResult { let mut instruments: Vec = Vec::new(); let instrumentations = instrumentations::table @@ -222,7 +221,11 @@ impl Database { let id = &instrumentation.instrument; instruments.push( self.get_instrument(id)? - .ok_or(anyhow!("No instrument with ID: {}", id))?, + .ok_or(DatabaseError::Other(format!( + "Failed to get instrument ({}) for work ({}).", + id, + row.id, + )))? ); } @@ -238,7 +241,11 @@ impl Database { composer: match part_row.composer { Some(composer) => Some( self.get_person(&composer)? - .ok_or(anyhow!("No person with ID: {}", composer))?, + .ok_or(DatabaseError::Other(format!( + "Failed to get person ({}) for work ({}).", + composer, + row.id, + )))? ), None => None, }, @@ -254,14 +261,18 @@ impl Database { for section_row in section_rows { sections.push(WorkSection { title: section_row.title, - before_index: section_row.before_index.try_into()?, + before_index: section_row.before_index as usize, }); } let person_id = &row.composer; let person = self .get_person(person_id)? - .ok_or(anyhow!("Person doesn't exist: {}", person_id))?; + .ok_or(DatabaseError::Other(format!( + "Failed to get person ({}) for work ({}).", + person_id, + row.id, + )))?; Ok(Work { id: row.id, @@ -275,13 +286,13 @@ impl Database { /// Delete an existing work. This will fail if there are still other tables that relate to /// this work except for the things that are part of the information on the work it - pub fn delete_work(&self, id: &str) -> Result<()> { + pub fn delete_work(&self, id: &str) -> DatabaseResult<()> { diesel::delete(works::table.filter(works::id.eq(id))).execute(&self.connection)?; Ok(()) } /// Get all existing works by a composer and related information from other tables. - pub fn get_works(&self, composer_id: &str) -> Result> { + pub fn get_works(&self, composer_id: &str) -> DatabaseResult> { let mut works: Vec = Vec::new(); let rows = works::table diff --git a/src/backend/error.rs b/src/backend/error.rs new file mode 100644 index 0000000..34e6aa0 --- /dev/null +++ b/src/backend/error.rs @@ -0,0 +1,55 @@ +use isahc::http::StatusCode; + +/// An error that can happen within the backend. +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("The users login credentials were wrong.")] + LoginFailed, + + #[error("The user has to be logged in to perform this action.")] + Unauthorized, + + #[error("The user is not allowed to perform this action.")] + Forbidden, + + #[error("The server returned an unexpected status code: {0}.")] + UnexpectedResponse(StatusCode), + + #[error("A networking error happened.")] + NetworkError(#[from] isahc::Error), + + #[error("A networking error happened.")] + HttpError(#[from] isahc::http::Error), + + #[error(transparent)] + DatabaseError(#[from] crate::backend::DatabaseError), + + #[error("An IO error happened.")] + IoError(#[from] std::io::Error), + + #[error("An error happened using the SecretService.")] + SecretServiceError(#[from] secret_service::Error), + + #[error("An error happened while serializing or deserializing.")] + SerdeError(#[from] serde_json::Error), + + #[error("An error happened in GLib.")] + GlibError(#[from] glib::BoolError), + + #[error("A channel was canceled.")] + ChannelError(#[from] futures_channel::oneshot::Canceled), + + #[error("Error decoding to UTF8.")] + Utf8Error(#[from] std::str::Utf8Error), + + #[error("An error happened: {0}")] + Other(&'static str), + + // TODO: Remove this once anyhow has been dropped as a dependency. + #[error("An unkown error happened.")] + Unknown(#[from] anyhow::Error), +} + + +pub type Result = std::result::Result; + diff --git a/src/meson.build b/src/meson.build index 9da7a7f..9e3d571 100644 --- a/src/meson.build +++ b/src/meson.build @@ -42,6 +42,7 @@ sources = files( 'backend/client/register.rs', 'backend/client/works.rs', 'backend/database/ensembles.rs', + 'backend/database/error.rs', 'backend/database/instruments.rs', 'backend/database/medium.rs', 'backend/database/mod.rs', @@ -50,6 +51,7 @@ sources = files( 'backend/database/schema.rs', 'backend/database/thread.rs', 'backend/database/works.rs', + 'backend/error.rs', 'backend/library.rs', 'backend/mod.rs', 'backend/player.rs',