Replace expect usage with Result

This commit is contained in:
Tobias Reisinger 2023-12-05 01:42:19 +01:00
parent 9394a1ae52
commit b3228ea6b5
Signed by: serguzim
GPG key ID: 13AD60C237A28DFE
11 changed files with 135 additions and 95 deletions

View file

@ -1,6 +1,6 @@
use emgauwa_lib::constants::WEBSOCKET_RETRY_TIMEOUT; use emgauwa_lib::constants::WEBSOCKET_RETRY_TIMEOUT;
use emgauwa_lib::db::{DbController, DbJunctionRelaySchedule, DbRelay, DbSchedule}; 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::models::{Controller, FromDbModel};
use emgauwa_lib::types::{ControllerUid, ControllerWsAction}; use emgauwa_lib::types::{ControllerUid, ControllerWsAction};
use emgauwa_lib::{db, utils}; use emgauwa_lib::{db, utils};
@ -23,7 +23,7 @@ mod settings;
async fn create_this_controller( async fn create_this_controller(
conn: &mut PoolConnection<Sqlite>, conn: &mut PoolConnection<Sqlite>,
settings: &Settings, settings: &Settings,
) -> DbController { ) -> Result<DbController, EmgauwaError> {
DbController::create( DbController::create(
conn, conn,
&ControllerUid::default(), &ControllerUid::default(),
@ -31,18 +31,20 @@ async fn create_this_controller(
settings.relays.len() as i64, settings.relays.len() as i64,
) )
.await .await
.expect("Failed to create controller") .map_err(EmgauwaError::from)
} }
async fn create_this_relay( async fn create_this_relay(
conn: &mut PoolConnection<Sqlite>, conn: &mut PoolConnection<Sqlite>,
this_controller: &DbController, this_controller: &DbController,
settings_relay: &settings::Relay, settings_relay: &settings::Relay,
) -> Result<DbRelay, DatabaseError> { ) -> Result<DbRelay, EmgauwaError> {
let relay = DbRelay::create( let relay = DbRelay::create(
conn, conn,
&settings_relay.name, &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, this_controller,
) )
.await?; .await?;
@ -55,7 +57,7 @@ async fn create_this_relay(
Ok(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 { match connect_async(url).await {
Ok(connection) => { Ok(connection) => {
let (ws_stream, _) = 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 = ControllerWsAction::Register(this.clone());
let ws_action_json = let ws_action_json = serde_json::to_string(&ws_action)?;
serde_json::to_string(&ws_action).expect("Failed to serialize action");
if let Err(err) = write.send(Message::text(ws_action_json)).await { if let Err(err) = write.send(Message::text(ws_action_json)).await {
log::error!("Failed to register at websocket: {}", err); log::error!("Failed to register at websocket: {}", err);
return; return Ok(());
} }
let read_handler = read.for_each(handle_message); 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,); log::warn!("Failed to connect to websocket: {}", err,);
} }
} }
Ok(())
} }
#[tokio::main] #[tokio::main]
async fn main() { async fn main() -> Result<(), std::io::Error> {
let settings = settings::init(); let settings = settings::init()?;
init_logging(&settings.logging.level); init_logging(&settings.logging.level)?;
let pool = db::init(&settings.database).await; let pool = db::init(&settings.database)
let mut conn = pool
.acquire()
.await .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) let db_controller = match DbController::get_all(&mut conn)
.await .await
.expect("Failed to get controller from database") .map_err(EmgauwaError::from)?
.pop() .pop()
{ {
None => futures::executor::block_on(create_this_controller(&mut conn, &settings)), 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( if DbRelay::get_by_controller_and_num(
&mut conn, &mut conn,
&db_controller, &db_controller,
relay.number.expect("Relay number is missing"), relay.number.ok_or(EmgauwaError::Internal(
"Relay number is missing".to_string(),
))?,
) )
.await .await
.expect("Failed to get relay from database") .map_err(EmgauwaError::from)?
.is_none() .is_none()
{ {
create_this_relay(&mut conn, &db_controller, relay) create_this_relay(&mut conn, &db_controller, relay)
.await .await
.expect("Failed to create schedule."); .map_err(EmgauwaError::from)?;
} }
} }
let db_controller = db_controller let db_controller = db_controller
.update(&mut conn, &db_controller.name, settings.relays.len() as i64) .update(&mut conn, &db_controller.name, settings.relays.len() as i64)
.await .await
.expect("Failed to update controller"); .map_err(EmgauwaError::from)?;
let this = Controller::from_db_model(&mut conn, db_controller) let this = Controller::from_db_model(&mut conn, db_controller).map_err(EmgauwaError::from)?;
.expect("Failed to convert database models");
let url = format!( let url = format!(
"ws://{}:{}/api/v1/ws/controllers", "ws://{}:{}/api/v1/ws/controllers",
@ -136,7 +138,10 @@ async fn main() {
tokio::spawn(run_relay_loop(settings)); tokio::spawn(run_relay_loop(settings));
loop { 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!( log::info!(
"Retrying to connect in {} seconds...", "Retrying to connect in {} seconds...",

View file

@ -1,3 +1,4 @@
use emgauwa_lib::errors::EmgauwaError;
use emgauwa_lib::{constants, utils}; use emgauwa_lib::{constants, utils};
use serde_derive::Deserialize; use serde_derive::Deserialize;
@ -83,8 +84,8 @@ impl Default for Logging {
} }
} }
pub fn init() -> Settings { pub fn init() -> Result<Settings, EmgauwaError> {
let mut settings: Settings = utils::load_settings("controller", "CONTROLLER"); let mut settings: Settings = utils::load_settings("controller", "CONTROLLER")?;
for (num, relay) in settings.relays.iter_mut().enumerate() { for (num, relay) in settings.relays.iter_mut().enumerate() {
if relay.number.is_none() { if relay.number.is_none() {
@ -92,5 +93,5 @@ pub fn init() -> Settings {
} }
} }
settings Ok(settings)
} }

View file

@ -116,14 +116,17 @@ impl StreamHandler<Result<Message, ProtocolError>> for ControllerWs {
let action_res = self.handle_action(&mut pool_conn, ctx, action); let action_res = self.handle_action(&mut pool_conn, ctx, action);
if let Err(e) = action_res { if let Err(e) = action_res {
log::error!("Error handling action: {:?}", e); 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) => { Err(e) => {
log::error!("Error deserializing action: {:?}", e); log::error!("Error deserializing action: {:?}", e);
ctx.text( ctx.text(
serde_json::to_string(&EmgauwaError::Serialization(e)) serde_json::to_string(&EmgauwaError::Serialization(e))
.expect("Failed to serialize error"), .unwrap_or(String::from("Error in deserializing action")),
); );
} }
}, },

View file

@ -5,6 +5,7 @@ use actix_cors::Cors;
use actix_web::middleware::TrailingSlash; use actix_web::middleware::TrailingSlash;
use actix_web::{middleware, web, App, HttpServer}; use actix_web::{middleware, web, App, HttpServer};
use emgauwa_lib::db::DbController; use emgauwa_lib::db::DbController;
use emgauwa_lib::errors::EmgauwaError;
use emgauwa_lib::utils::init_logging; use emgauwa_lib::utils::init_logging;
use crate::app_state::AppServer; use crate::app_state::AppServer;
@ -16,27 +17,21 @@ mod settings;
mod utils; mod utils;
#[actix_web::main] #[actix_web::main]
async fn main() -> std::io::Result<()> { async fn main() -> Result<(), std::io::Error> {
let settings = settings::init(); let settings = settings::init()?;
init_logging(&settings.logging.level); init_logging(&settings.logging.level)?;
let listener = TcpListener::bind(format!("{}:{}", settings.host, settings.port)) let listener = TcpListener::bind(format!("{}:{}", settings.host, settings.port))?;
.expect("Error creating listener");
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.map_err(EmgauwaError::from)?;
{
let mut conn = pool
.acquire()
.await
.expect("Failed to get database connection");
DbController::all_inactive(&mut conn) DbController::all_inactive(&mut conn)
.await .await
.expect("Error setting all controllers inactive"); .map_err(EmgauwaError::from)?;
} conn.close().await.map_err(EmgauwaError::from)?;
let app_server = AppServer::new(pool.clone()).start(); let app_server = AppServer::new(pool.clone()).start();

View file

@ -1,3 +1,4 @@
use emgauwa_lib::errors::EmgauwaError;
use emgauwa_lib::{constants, utils}; use emgauwa_lib::{constants, utils};
use serde_derive::Deserialize; use serde_derive::Deserialize;
@ -51,6 +52,6 @@ impl Default for Logging {
} }
} }
pub fn init() -> Settings { pub fn init() -> Result<Settings, EmgauwaError> {
utils::load_settings("core", "CORE") utils::load_settings("core", "CORE")
} }

View file

@ -19,38 +19,31 @@ pub use relays::DbRelay;
pub use schedules::{DbPeriods, DbSchedule}; pub use schedules::{DbPeriods, DbSchedule};
pub use tag::DbTag; pub use tag::DbTag;
use crate::errors::{DatabaseError, EmgauwaError};
static MIGRATOR: Migrator = sqlx::migrate!("../migrations"); // defaults to "./migrations" static MIGRATOR: Migrator = sqlx::migrate!("../migrations"); // defaults to "./migrations"
pub async fn run_migrations(pool: &Pool<Sqlite>) { pub async fn run_migrations(pool: &Pool<Sqlite>) -> Result<(), EmgauwaError> {
log::info!("Running migrations"); 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<Sqlite> { pub async fn init(db: &str) -> Result<Pool<Sqlite>, EmgauwaError> {
let options = SqliteConnectOptions::from_str(db) let options = SqliteConnectOptions::from_str(db)?.create_if_missing(true);
.expect("Error parsing database path")
.create_if_missing(true);
let pool: Pool<Sqlite> = SqlitePoolOptions::new() let pool: Pool<Sqlite> = SqlitePoolOptions::new()
.acquire_timeout(std::time::Duration::from_secs(1)) .acquire_timeout(std::time::Duration::from_secs(1))
.max_connections(5) .max_connections(5)
.connect_with(options) .connect_with(options)
.await .await?;
.expect("Error connecting to database");
run_migrations(&pool).await; run_migrations(&pool).await?;
let mut pool_conn = pool let mut pool_conn = pool.acquire().await?;
.acquire()
.await
.expect("Failed to acquire pool connection");
DbSchedule::get_on(&mut pool_conn) DbSchedule::get_on(&mut pool_conn).await?;
.await DbSchedule::get_off(&mut pool_conn).await?;
.expect("Failed to init 'on' schedule");
DbSchedule::get_off(&mut pool_conn)
.await
.expect("Failed to init 'off' schedule");
pool Ok(pool)
} }

View file

@ -2,6 +2,7 @@ use actix_web::http::StatusCode;
use actix_web::HttpResponse; use actix_web::HttpResponse;
use serde::ser::SerializeStruct; use serde::ser::SerializeStruct;
use serde::{Serialize, Serializer}; use serde::{Serialize, Serializer};
use sqlx::migrate::MigrateError;
use sqlx::Error; use sqlx::Error;
#[derive(Debug)] #[derive(Debug)]
@ -13,7 +14,8 @@ pub enum DatabaseError {
Protected, Protected,
UpdateError, UpdateError,
UpdateGetError, UpdateGetError,
Unknown(String), MigrationError(MigrateError),
Unknown(Error),
} }
impl DatabaseError { impl DatabaseError {
@ -53,6 +55,7 @@ impl From<&DatabaseError> for String {
DatabaseError::UpdateGetError => { DatabaseError::UpdateGetError => {
"error on retrieving updated model from database (your entry was saved)" "error on retrieving updated model from database (your entry was saved)"
} }
DatabaseError::MigrationError(_) => "error on running migrations",
DatabaseError::Unknown(_) => "unknown error", DatabaseError::Unknown(_) => "unknown error",
}) })
} }
@ -68,7 +71,13 @@ impl From<Error> for DatabaseError {
fn from(value: Error) -> Self { fn from(value: Error) -> Self {
match value { match value {
Error::RowNotFound => DatabaseError::NotFound, Error::RowNotFound => DatabaseError::NotFound,
_ => DatabaseError::Unknown(value.to_string()), _ => DatabaseError::Unknown(value),
} }
} }
} }
impl From<MigrateError> for DatabaseError {
fn from(value: MigrateError) -> Self {
Self::MigrationError(value)
}
}

View file

@ -1,8 +1,11 @@
use std::error::Error;
use std::fmt::{Debug, Display, Formatter}; use std::fmt::{Debug, Display, Formatter};
use std::io::ErrorKind;
use actix::MailboxError; use actix::MailboxError;
use actix_web::http::StatusCode; use actix_web::http::StatusCode;
use actix_web::HttpResponse; use actix_web::HttpResponse;
use config::ConfigError;
use serde::ser::SerializeStruct; use serde::ser::SerializeStruct;
use serde::{Serialize, Serializer}; use serde::{Serialize, Serializer};
@ -15,6 +18,7 @@ pub enum EmgauwaError {
Uid(uuid::Error), Uid(uuid::Error),
Serialization(serde_json::Error), Serialization(serde_json::Error),
Database(DatabaseError), Database(DatabaseError),
Other(String),
Internal(String), Internal(String),
Connection(ControllerUid), Connection(ControllerUid),
} }
@ -28,6 +32,7 @@ impl EmgauwaError {
EmgauwaError::Uid(_) => StatusCode::BAD_REQUEST, EmgauwaError::Uid(_) => StatusCode::BAD_REQUEST,
EmgauwaError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, EmgauwaError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
EmgauwaError::Connection(_) => StatusCode::GATEWAY_TIMEOUT, 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::Serialization(_) => String::from("error during (de-)serialization"),
EmgauwaError::Database(err) => String::from(err), EmgauwaError::Database(err) => String::from(err),
EmgauwaError::Uid(_) => String::from("the uid is in a bad format"), 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::Connection(_) => String::from("the target controller is not connected"),
EmgauwaError::Other(err) => format!("other error: {}", err),
} }
} }
} }
@ -81,12 +87,26 @@ impl From<MailboxError> for EmgauwaError {
} }
} }
impl From<ConfigError> for EmgauwaError {
fn from(value: ConfigError) -> Self {
Self::Other(value.to_string())
}
}
impl From<&EmgauwaError> for HttpResponse { impl From<&EmgauwaError> for HttpResponse {
fn from(err: &EmgauwaError) -> Self { fn from(err: &EmgauwaError) -> Self {
HttpResponse::build(err.get_code()).json(err) HttpResponse::build(err.get_code()).json(err)
} }
} }
impl Error for EmgauwaError {}
impl From<EmgauwaError> for std::io::Error {
fn from(value: EmgauwaError) -> Self {
std::io::Error::new(ErrorKind::Other, value)
}
}
impl Serialize for EmgauwaError { impl Serialize for EmgauwaError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where where

View file

@ -62,15 +62,20 @@ impl Type<Sqlite> for ControllerUid {
impl<'q> Encode<'q, Sqlite> for ControllerUid { impl<'q> Encode<'q, Sqlite> for ControllerUid {
//noinspection DuplicatedCode //noinspection DuplicatedCode
fn encode_by_ref(&self, buf: &mut <Sqlite as HasArguments<'q>>::ArgumentBuffer) -> IsNull { fn encode_by_ref(&self, buf: &mut <Sqlite as HasArguments<'q>>::ArgumentBuffer) -> IsNull {
let uuid_val = self.0.as_bytes().to_vec(); <Vec<u8> as Encode<Sqlite>>::encode(Vec::from(self), buf)
<&Vec<u8> as Encode<Sqlite>>::encode(&uuid_val, buf)
} }
} }
impl<'r> Decode<'r, Sqlite> for ControllerUid { impl<'r> Decode<'r, Sqlite> for ControllerUid {
//noinspection DuplicatedCode //noinspection DuplicatedCode
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> { fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(Self::from(<&[u8] as Decode<Sqlite>>::decode(value)?)) Self::try_from(<&[u8] as Decode<Sqlite>>::decode(value)?).map_err(Into::into)
}
}
impl From<&ControllerUid> for Vec<u8> {
fn from(uid: &ControllerUid) -> Vec<u8> {
uid.0.as_bytes().to_vec()
} }
} }
@ -83,14 +88,16 @@ impl TryFrom<&str> for ControllerUid {
} }
} }
impl From<&[u8]> for ControllerUid { impl TryFrom<&[u8]> for ControllerUid {
fn from(value: &[u8]) -> Self { type Error = uuid::Error;
Self(Uuid::from_slice(value).expect("Failed to parse controller uid from database"))
fn try_from(value: &[u8]) -> Result<ControllerUid, uuid::Error> {
Ok(Self(Uuid::from_slice(value)?))
} }
} }
impl From<Vec<u8>> for ControllerUid { impl From<Vec<u8>> for ControllerUid {
fn from(value: Vec<u8>) -> Self { fn from(value: Vec<u8>) -> Self {
Self::from(value.as_slice()) Self::try_from(value.as_slice()).expect("Failed to parse controller uid from database")
} }
} }

View file

@ -55,14 +55,14 @@ impl Type<Sqlite> for ScheduleUid {
impl<'q> Encode<'q, Sqlite> for ScheduleUid { impl<'q> Encode<'q, Sqlite> for ScheduleUid {
//noinspection DuplicatedCode //noinspection DuplicatedCode
fn encode_by_ref(&self, buf: &mut <Sqlite as HasArguments<'q>>::ArgumentBuffer) -> IsNull { fn encode_by_ref(&self, buf: &mut <Sqlite as HasArguments<'q>>::ArgumentBuffer) -> IsNull {
<&Vec<u8> as Encode<Sqlite>>::encode(&Vec::from(self), buf) <Vec<u8> as Encode<Sqlite>>::encode(Vec::from(self), buf)
} }
} }
impl<'r> Decode<'r, Sqlite> for ScheduleUid { impl<'r> Decode<'r, Sqlite> for ScheduleUid {
//noinspection DuplicatedCode //noinspection DuplicatedCode
fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> { fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(Self::from(<&[u8] as Decode<Sqlite>>::decode(value)?)) Self::try_from(<&[u8] as Decode<Sqlite>>::decode(value)?).map_err(Into::into)
} }
} }
@ -140,20 +140,21 @@ impl From<&ScheduleUid> for Vec<u8> {
} }
} }
impl From<&[u8]> for ScheduleUid { impl TryFrom<&[u8]> for ScheduleUid {
fn from(value: &[u8]) -> Self { type Error = uuid::Error;
match value {
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let result = match value {
[Self::OFF_U8] => Self::Off, [Self::OFF_U8] => Self::Off,
[Self::ON_U8] => Self::On, [Self::ON_U8] => Self::On,
value_bytes => Self::Any( value_bytes => Self::Any(Uuid::from_slice(value_bytes)?),
Uuid::from_slice(value_bytes).expect("Failed to parse schedule uid from database"), };
), Ok(result)
}
} }
} }
impl From<Vec<u8>> for ScheduleUid { impl From<Vec<u8>> for ScheduleUid {
fn from(value: Vec<u8>) -> Self { fn from(value: Vec<u8>) -> Self {
Self::from(value.as_slice()) Self::try_from(value.as_slice()).expect("Failed to parse schedule uid from database")
} }
} }

View file

@ -4,9 +4,10 @@ use chrono::Datelike;
use log::LevelFilter; use log::LevelFilter;
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
use crate::errors::EmgauwaError;
use crate::types::Weekday; use crate::types::Weekday;
pub fn load_settings<T>(config_name: &str, env_prefix: &str) -> T pub fn load_settings<T>(config_name: &str, env_prefix: &str) -> Result<T, EmgauwaError>
where where
for<'de> T: serde::Deserialize<'de>, for<'de> T: serde::Deserialize<'de>,
{ {
@ -19,20 +20,24 @@ where
.prefix_separator("__") .prefix_separator("__")
.separator("__"), .separator("__"),
) )
.build() .build()?
.expect("Error building settings")
.try_deserialize::<T>() .try_deserialize::<T>()
.expect("Error reading settings") .map_err(EmgauwaError::from)
} }
pub fn init_logging(level: &str) { pub fn init_logging(level: &str) -> Result<(), EmgauwaError> {
let log_level: LevelFilter = LevelFilter::from_str(level).expect("Error parsing log level."); 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); log::trace!("Log level set to {:?}", log_level);
SimpleLogger::new() SimpleLogger::new()
.with_level(log_level) .with_level(log_level)
.init() .init()
.expect("Error initializing logger."); .map_err(|err| {
EmgauwaError::Other(format!("Failed to initialize logger: {}", err.to_string()))
})?;
Ok(())
} }
pub fn get_weekday() -> Weekday { pub fn get_weekday() -> Weekday {