diff --git a/emgauwa-controller/src/main.rs b/emgauwa-controller/src/main.rs index 546822b..6ff6f65 100644 --- a/emgauwa-controller/src/main.rs +++ b/emgauwa-controller/src/main.rs @@ -1,6 +1,6 @@ use emgauwa_lib::constants::WEBSOCKET_RETRY_TIMEOUT; use emgauwa_lib::db::{DbController, DbJunctionRelaySchedule, DbRelay, DbSchedule}; -use emgauwa_lib::errors::DatabaseError; +use emgauwa_lib::errors::{DatabaseError, EmgauwaError}; use emgauwa_lib::models::{Controller, FromDbModel}; use emgauwa_lib::types::{ControllerUid, ControllerWsAction}; use emgauwa_lib::{db, utils}; @@ -23,7 +23,7 @@ mod settings; async fn create_this_controller( conn: &mut PoolConnection, settings: &Settings, -) -> DbController { +) -> Result { DbController::create( conn, &ControllerUid::default(), @@ -31,18 +31,20 @@ async fn create_this_controller( settings.relays.len() as i64, ) .await - .expect("Failed to create controller") + .map_err(EmgauwaError::from) } async fn create_this_relay( conn: &mut PoolConnection, this_controller: &DbController, settings_relay: &settings::Relay, -) -> Result { +) -> Result { let relay = DbRelay::create( conn, &settings_relay.name, - settings_relay.number.expect("Relay number is missing"), + settings_relay.number.ok_or(EmgauwaError::Internal( + "Relay number is missing".to_string(), + ))?, this_controller, ) .await?; @@ -55,7 +57,7 @@ async fn create_this_relay( Ok(relay) } -async fn run_websocket(this: Controller, url: &str) { +async fn run_websocket(this: Controller, url: &str) -> Result<(), EmgauwaError> { match connect_async(url).await { Ok(connection) => { let (ws_stream, _) = connection; @@ -64,11 +66,10 @@ async fn run_websocket(this: Controller, url: &str) { let ws_action = ControllerWsAction::Register(this.clone()); - let ws_action_json = - serde_json::to_string(&ws_action).expect("Failed to serialize action"); + let ws_action_json = serde_json::to_string(&ws_action)?; if let Err(err) = write.send(Message::text(ws_action_json)).await { log::error!("Failed to register at websocket: {}", err); - return; + return Ok(()); } let read_handler = read.for_each(handle_message); @@ -81,23 +82,23 @@ async fn run_websocket(this: Controller, url: &str) { log::warn!("Failed to connect to websocket: {}", err,); } } + Ok(()) } #[tokio::main] -async fn main() { - let settings = settings::init(); - init_logging(&settings.logging.level); +async fn main() -> Result<(), std::io::Error> { + let settings = settings::init()?; + init_logging(&settings.logging.level)?; - let pool = db::init(&settings.database).await; - - let mut conn = pool - .acquire() + let pool = db::init(&settings.database) .await - .expect("Failed to get database connection"); + .map_err(EmgauwaError::from)?; + + let mut conn = pool.acquire().await.map_err(EmgauwaError::from)?; let db_controller = match DbController::get_all(&mut conn) .await - .expect("Failed to get controller from database") + .map_err(EmgauwaError::from)? .pop() { None => futures::executor::block_on(create_this_controller(&mut conn, &settings)), @@ -108,25 +109,26 @@ async fn main() { if DbRelay::get_by_controller_and_num( &mut conn, &db_controller, - relay.number.expect("Relay number is missing"), + relay.number.ok_or(EmgauwaError::Internal( + "Relay number is missing".to_string(), + ))?, ) .await - .expect("Failed to get relay from database") + .map_err(EmgauwaError::from)? .is_none() { create_this_relay(&mut conn, &db_controller, relay) .await - .expect("Failed to create schedule."); + .map_err(EmgauwaError::from)?; } } let db_controller = db_controller .update(&mut conn, &db_controller.name, settings.relays.len() as i64) .await - .expect("Failed to update controller"); + .map_err(EmgauwaError::from)?; - let this = Controller::from_db_model(&mut conn, db_controller) - .expect("Failed to convert database models"); + let this = Controller::from_db_model(&mut conn, db_controller).map_err(EmgauwaError::from)?; let url = format!( "ws://{}:{}/api/v1/ws/controllers", @@ -136,7 +138,10 @@ async fn main() { tokio::spawn(run_relay_loop(settings)); loop { - run_websocket(this.clone(), &url).await; + let run_result = run_websocket(this.clone(), &url).await; + if let Err(err) = run_result { + log::error!("Error running websocket: {}", err); + } log::info!( "Retrying to connect in {} seconds...", diff --git a/emgauwa-controller/src/settings.rs b/emgauwa-controller/src/settings.rs index ca59096..d9b86e4 100644 --- a/emgauwa-controller/src/settings.rs +++ b/emgauwa-controller/src/settings.rs @@ -1,3 +1,4 @@ +use emgauwa_lib::errors::EmgauwaError; use emgauwa_lib::{constants, utils}; use serde_derive::Deserialize; @@ -83,8 +84,8 @@ impl Default for Logging { } } -pub fn init() -> Settings { - let mut settings: Settings = utils::load_settings("controller", "CONTROLLER"); +pub fn init() -> Result { + let mut settings: Settings = utils::load_settings("controller", "CONTROLLER")?; for (num, relay) in settings.relays.iter_mut().enumerate() { if relay.number.is_none() { @@ -92,5 +93,5 @@ pub fn init() -> Settings { } } - settings + Ok(settings) } diff --git a/emgauwa-core/src/handlers/v1/ws/controllers/mod.rs b/emgauwa-core/src/handlers/v1/ws/controllers/mod.rs index c4a6425..4a4e020 100644 --- a/emgauwa-core/src/handlers/v1/ws/controllers/mod.rs +++ b/emgauwa-core/src/handlers/v1/ws/controllers/mod.rs @@ -116,14 +116,17 @@ impl StreamHandler> for ControllerWs { let action_res = self.handle_action(&mut pool_conn, ctx, action); if let Err(e) = action_res { log::error!("Error handling action: {:?}", e); - ctx.text(serde_json::to_string(&e).expect("Failed to serialize error")); + ctx.text( + serde_json::to_string(&e) + .unwrap_or(format!("Error in handling action: {:?}", e)), + ); } } Err(e) => { log::error!("Error deserializing action: {:?}", e); ctx.text( serde_json::to_string(&EmgauwaError::Serialization(e)) - .expect("Failed to serialize error"), + .unwrap_or(String::from("Error in deserializing action")), ); } }, diff --git a/emgauwa-core/src/main.rs b/emgauwa-core/src/main.rs index 8e83d16..45c1214 100644 --- a/emgauwa-core/src/main.rs +++ b/emgauwa-core/src/main.rs @@ -5,6 +5,7 @@ use actix_cors::Cors; use actix_web::middleware::TrailingSlash; use actix_web::{middleware, web, App, HttpServer}; use emgauwa_lib::db::DbController; +use emgauwa_lib::errors::EmgauwaError; use emgauwa_lib::utils::init_logging; use crate::app_state::AppServer; @@ -16,27 +17,21 @@ mod settings; mod utils; #[actix_web::main] -async fn main() -> std::io::Result<()> { - let settings = settings::init(); - init_logging(&settings.logging.level); +async fn main() -> Result<(), std::io::Error> { + let settings = settings::init()?; + init_logging(&settings.logging.level)?; - let listener = TcpListener::bind(format!("{}:{}", settings.host, settings.port)) - .expect("Error creating listener"); + let listener = TcpListener::bind(format!("{}:{}", settings.host, settings.port))?; - drop_privileges(&settings).expect("Error dropping privileges"); + drop_privileges(&settings)?; - let pool = emgauwa_lib::db::init(&settings.database).await; + let pool = emgauwa_lib::db::init(&settings.database).await?; - // This block is to ensure that the connection is dropped after use. - { - let mut conn = pool - .acquire() - .await - .expect("Failed to get database connection"); - DbController::all_inactive(&mut conn) - .await - .expect("Error setting all controllers inactive"); - } + let mut conn = pool.acquire().await.map_err(EmgauwaError::from)?; + DbController::all_inactive(&mut conn) + .await + .map_err(EmgauwaError::from)?; + conn.close().await.map_err(EmgauwaError::from)?; let app_server = AppServer::new(pool.clone()).start(); diff --git a/emgauwa-core/src/settings.rs b/emgauwa-core/src/settings.rs index 5c4e353..115efa6 100644 --- a/emgauwa-core/src/settings.rs +++ b/emgauwa-core/src/settings.rs @@ -1,3 +1,4 @@ +use emgauwa_lib::errors::EmgauwaError; use emgauwa_lib::{constants, utils}; use serde_derive::Deserialize; @@ -51,6 +52,6 @@ impl Default for Logging { } } -pub fn init() -> Settings { +pub fn init() -> Result { utils::load_settings("core", "CORE") } diff --git a/emgauwa-lib/src/db/mod.rs b/emgauwa-lib/src/db/mod.rs index fd42606..0b0650d 100644 --- a/emgauwa-lib/src/db/mod.rs +++ b/emgauwa-lib/src/db/mod.rs @@ -19,38 +19,31 @@ pub use relays::DbRelay; pub use schedules::{DbPeriods, DbSchedule}; pub use tag::DbTag; +use crate::errors::{DatabaseError, EmgauwaError}; + static MIGRATOR: Migrator = sqlx::migrate!("../migrations"); // defaults to "./migrations" -pub async fn run_migrations(pool: &Pool) { +pub async fn run_migrations(pool: &Pool) -> Result<(), EmgauwaError> { log::info!("Running migrations"); - MIGRATOR.run(pool).await.expect("Failed to run migrations."); + MIGRATOR.run(pool).await.map_err(DatabaseError::from)?; + Ok(()) } -pub async fn init(db: &str) -> Pool { - let options = SqliteConnectOptions::from_str(db) - .expect("Error parsing database path") - .create_if_missing(true); +pub async fn init(db: &str) -> Result, EmgauwaError> { + let options = SqliteConnectOptions::from_str(db)?.create_if_missing(true); let pool: Pool = SqlitePoolOptions::new() .acquire_timeout(std::time::Duration::from_secs(1)) .max_connections(5) .connect_with(options) - .await - .expect("Error connecting to database"); + .await?; - run_migrations(&pool).await; + run_migrations(&pool).await?; - let mut pool_conn = pool - .acquire() - .await - .expect("Failed to acquire pool connection"); + let mut pool_conn = pool.acquire().await?; - DbSchedule::get_on(&mut pool_conn) - .await - .expect("Failed to init 'on' schedule"); - DbSchedule::get_off(&mut pool_conn) - .await - .expect("Failed to init 'off' schedule"); + DbSchedule::get_on(&mut pool_conn).await?; + DbSchedule::get_off(&mut pool_conn).await?; - pool + Ok(pool) } diff --git a/emgauwa-lib/src/errors/database_error.rs b/emgauwa-lib/src/errors/database_error.rs index c221f11..3f8c482 100644 --- a/emgauwa-lib/src/errors/database_error.rs +++ b/emgauwa-lib/src/errors/database_error.rs @@ -2,6 +2,7 @@ use actix_web::http::StatusCode; use actix_web::HttpResponse; use serde::ser::SerializeStruct; use serde::{Serialize, Serializer}; +use sqlx::migrate::MigrateError; use sqlx::Error; #[derive(Debug)] @@ -13,7 +14,8 @@ pub enum DatabaseError { Protected, UpdateError, UpdateGetError, - Unknown(String), + MigrationError(MigrateError), + Unknown(Error), } impl DatabaseError { @@ -53,6 +55,7 @@ impl From<&DatabaseError> for String { DatabaseError::UpdateGetError => { "error on retrieving updated model from database (your entry was saved)" } + DatabaseError::MigrationError(_) => "error on running migrations", DatabaseError::Unknown(_) => "unknown error", }) } @@ -68,7 +71,13 @@ impl From for DatabaseError { fn from(value: Error) -> Self { match value { Error::RowNotFound => DatabaseError::NotFound, - _ => DatabaseError::Unknown(value.to_string()), + _ => DatabaseError::Unknown(value), } } } + +impl From for DatabaseError { + fn from(value: MigrateError) -> Self { + Self::MigrationError(value) + } +} diff --git a/emgauwa-lib/src/errors/emgauwa_error.rs b/emgauwa-lib/src/errors/emgauwa_error.rs index ee33ed8..b947c16 100644 --- a/emgauwa-lib/src/errors/emgauwa_error.rs +++ b/emgauwa-lib/src/errors/emgauwa_error.rs @@ -1,8 +1,11 @@ +use std::error::Error; use std::fmt::{Debug, Display, Formatter}; +use std::io::ErrorKind; use actix::MailboxError; use actix_web::http::StatusCode; use actix_web::HttpResponse; +use config::ConfigError; use serde::ser::SerializeStruct; use serde::{Serialize, Serializer}; @@ -15,6 +18,7 @@ pub enum EmgauwaError { Uid(uuid::Error), Serialization(serde_json::Error), Database(DatabaseError), + Other(String), Internal(String), Connection(ControllerUid), } @@ -28,6 +32,7 @@ impl EmgauwaError { EmgauwaError::Uid(_) => StatusCode::BAD_REQUEST, EmgauwaError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, EmgauwaError::Connection(_) => StatusCode::GATEWAY_TIMEOUT, + EmgauwaError::Other(_) => StatusCode::INTERNAL_SERVER_ERROR, } } } @@ -39,8 +44,9 @@ impl From<&EmgauwaError> for String { EmgauwaError::Serialization(_) => String::from("error during (de-)serialization"), EmgauwaError::Database(err) => String::from(err), EmgauwaError::Uid(_) => String::from("the uid is in a bad format"), - EmgauwaError::Internal(_) => String::from("general error"), + EmgauwaError::Internal(_) => String::from("internal error"), EmgauwaError::Connection(_) => String::from("the target controller is not connected"), + EmgauwaError::Other(err) => format!("other error: {}", err), } } } @@ -81,12 +87,26 @@ impl From for EmgauwaError { } } +impl From for EmgauwaError { + fn from(value: ConfigError) -> Self { + Self::Other(value.to_string()) + } +} + impl From<&EmgauwaError> for HttpResponse { fn from(err: &EmgauwaError) -> Self { HttpResponse::build(err.get_code()).json(err) } } +impl Error for EmgauwaError {} + +impl From for std::io::Error { + fn from(value: EmgauwaError) -> Self { + std::io::Error::new(ErrorKind::Other, value) + } +} + impl Serialize for EmgauwaError { fn serialize(&self, serializer: S) -> Result where diff --git a/emgauwa-lib/src/types/controller_uid.rs b/emgauwa-lib/src/types/controller_uid.rs index 0d45973..bbaded8 100644 --- a/emgauwa-lib/src/types/controller_uid.rs +++ b/emgauwa-lib/src/types/controller_uid.rs @@ -62,15 +62,20 @@ impl Type for ControllerUid { impl<'q> Encode<'q, Sqlite> for ControllerUid { //noinspection DuplicatedCode fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { - let uuid_val = self.0.as_bytes().to_vec(); - <&Vec as Encode>::encode(&uuid_val, buf) + as Encode>::encode(Vec::from(self), buf) } } impl<'r> Decode<'r, Sqlite> for ControllerUid { //noinspection DuplicatedCode fn decode(value: SqliteValueRef<'r>) -> Result { - Ok(Self::from(<&[u8] as Decode>::decode(value)?)) + Self::try_from(<&[u8] as Decode>::decode(value)?).map_err(Into::into) + } +} + +impl From<&ControllerUid> for Vec { + fn from(uid: &ControllerUid) -> Vec { + uid.0.as_bytes().to_vec() } } @@ -83,14 +88,16 @@ impl TryFrom<&str> for ControllerUid { } } -impl From<&[u8]> for ControllerUid { - fn from(value: &[u8]) -> Self { - Self(Uuid::from_slice(value).expect("Failed to parse controller uid from database")) +impl TryFrom<&[u8]> for ControllerUid { + type Error = uuid::Error; + + fn try_from(value: &[u8]) -> Result { + Ok(Self(Uuid::from_slice(value)?)) } } impl From> for ControllerUid { fn from(value: Vec) -> Self { - Self::from(value.as_slice()) + Self::try_from(value.as_slice()).expect("Failed to parse controller uid from database") } } diff --git a/emgauwa-lib/src/types/schedule_uid.rs b/emgauwa-lib/src/types/schedule_uid.rs index ebd696d..2e903a9 100644 --- a/emgauwa-lib/src/types/schedule_uid.rs +++ b/emgauwa-lib/src/types/schedule_uid.rs @@ -55,14 +55,14 @@ impl Type for ScheduleUid { impl<'q> Encode<'q, Sqlite> for ScheduleUid { //noinspection DuplicatedCode fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { - <&Vec as Encode>::encode(&Vec::from(self), buf) + as Encode>::encode(Vec::from(self), buf) } } impl<'r> Decode<'r, Sqlite> for ScheduleUid { //noinspection DuplicatedCode fn decode(value: SqliteValueRef<'r>) -> Result { - Ok(Self::from(<&[u8] as Decode>::decode(value)?)) + Self::try_from(<&[u8] as Decode>::decode(value)?).map_err(Into::into) } } @@ -140,20 +140,21 @@ impl From<&ScheduleUid> for Vec { } } -impl From<&[u8]> for ScheduleUid { - fn from(value: &[u8]) -> Self { - match value { +impl TryFrom<&[u8]> for ScheduleUid { + type Error = uuid::Error; + + fn try_from(value: &[u8]) -> Result { + let result = match value { [Self::OFF_U8] => Self::Off, [Self::ON_U8] => Self::On, - value_bytes => Self::Any( - Uuid::from_slice(value_bytes).expect("Failed to parse schedule uid from database"), - ), - } + value_bytes => Self::Any(Uuid::from_slice(value_bytes)?), + }; + Ok(result) } } impl From> for ScheduleUid { fn from(value: Vec) -> Self { - Self::from(value.as_slice()) + Self::try_from(value.as_slice()).expect("Failed to parse schedule uid from database") } } diff --git a/emgauwa-lib/src/utils.rs b/emgauwa-lib/src/utils.rs index 27c253d..9f442b9 100644 --- a/emgauwa-lib/src/utils.rs +++ b/emgauwa-lib/src/utils.rs @@ -4,9 +4,10 @@ use chrono::Datelike; use log::LevelFilter; use simple_logger::SimpleLogger; +use crate::errors::EmgauwaError; use crate::types::Weekday; -pub fn load_settings(config_name: &str, env_prefix: &str) -> T +pub fn load_settings(config_name: &str, env_prefix: &str) -> Result where for<'de> T: serde::Deserialize<'de>, { @@ -19,20 +20,24 @@ where .prefix_separator("__") .separator("__"), ) - .build() - .expect("Error building settings") + .build()? .try_deserialize::() - .expect("Error reading settings") + .map_err(EmgauwaError::from) } -pub fn init_logging(level: &str) { - let log_level: LevelFilter = LevelFilter::from_str(level).expect("Error parsing log level."); +pub fn init_logging(level: &str) -> Result<(), EmgauwaError> { + let log_level: LevelFilter = LevelFilter::from_str(level) + .map_err(|_| EmgauwaError::Other(format!("Invalid log level: {}", level.to_string())))?; log::trace!("Log level set to {:?}", log_level); SimpleLogger::new() .with_level(log_level) .init() - .expect("Error initializing logger."); + .map_err(|err| { + EmgauwaError::Other(format!("Failed to initialize logger: {}", err.to_string())) + })?; + + Ok(()) } pub fn get_weekday() -> Weekday {