Skip to content

Commit

Permalink
chore: Replace anyhow with just basic error handling (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
ikornaselur committed Mar 23, 2024
1 parent d8896dd commit 9654ccc
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 49 deletions.
7 changes: 0 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ repository = "https://github.com/ikornaselur/notiflux"
actix = "0.13.3"
actix-web = "4"
actix-web-actors = "4.3.0"
anyhow = "1.0.81"
base64 = "0.22.0"
env_logger = "0.11.3"
jsonwebtoken = "9.3.0"
Expand Down
25 changes: 14 additions & 11 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use actix::*;
use actix_web::{middleware::Logger, web, App, Error, HttpRequest, HttpResponse, HttpServer};
use actix_web_actors::ws;
use anyhow::Result;
use serde::Deserialize;
use std::time::Instant;
use ulid::Ulid;

use crate::{config, message, server, session};
use crate::{config, message, server, session, NotifluxError};

async fn ws_route(
req: HttpRequest,
Expand All @@ -24,29 +24,32 @@ async fn ws_route(
)
}

#[derive(Deserialize)]
struct BroadcastPayload {
channel: String,
message: String,
token: String,
}

/// POST /broadcast
/// Broadcast a message to all clients connected to a channel.
/// The request body should be a JSON object with the following fields:
/// - channel: the channel to broadcast the message to
/// - message: the message to broadcast
async fn broadcast(
req: web::Json<serde_json::Value>,
req: web::Json<BroadcastPayload>,
srv: web::Data<Addr<server::Server>>,
) -> HttpResponse {
let channel = req["channel"].as_str().unwrap();
let message = req["message"].as_str().unwrap();
let token = req["token"].as_str().unwrap();

srv.get_ref().do_send(message::Broadcast {
msg: message.to_owned(),
channel: channel.to_owned(),
token: token.to_owned(),
msg: req.message.to_owned(),
channel: req.channel.to_owned(),
token: req.token.to_owned(),
});

HttpResponse::Ok().finish()
}

pub async fn run() -> Result<()> {
pub async fn run() -> Result<(), NotifluxError> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("debug"));

let config = config::get_config();
Expand Down
26 changes: 15 additions & 11 deletions src/auth.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::error::{NotifluxError, NotifluxErrorType};
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
Expand All @@ -16,12 +15,17 @@ pub enum Action {
Broadcast(String),
}

pub fn get_action(token: &str, public_key: &[u8]) -> Result<Action> {
let key = jsonwebtoken::DecodingKey::from_ec_pem(public_key)
.context("Unable to decode public key")?;
pub fn get_action(token: &str, public_key: &[u8]) -> Result<Action, NotifluxError> {
let key = jsonwebtoken::DecodingKey::from_ec_pem(public_key).map_err(|_| NotifluxError {
message: Some("Invalid public key".to_owned()),
error_type: NotifluxErrorType::JWTError,
})?;
let validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::ES256);
let token_data = jsonwebtoken::decode::<Claims>(token, &key, &validation)
.context("Unable to decode JWT token")?;
let token_data =
jsonwebtoken::decode::<Claims>(token, &key, &validation).map_err(|_| NotifluxError {
message: Some("Invalid token".to_owned()),
error_type: NotifluxErrorType::JWTError,
})?;

let Claims {
sub: _,
Expand All @@ -36,10 +40,9 @@ pub fn get_action(token: &str, public_key: &[u8]) -> Result<Action> {
Ok(Action::Broadcast(channel))
} else {
Err(NotifluxError {
message: Some("Invalid scope".to_owned()),
error_type: NotifluxErrorType::ValidationError,
}
.into())
message: Some(format!("Invalid scope: {}", scope)),
error_type: NotifluxErrorType::JWTError,
})
}
}

Expand Down Expand Up @@ -80,6 +83,7 @@ mod tests {
assert!(action.is_err());

let err = action.unwrap_err();
assert_eq!(err.to_string(), "Invalid scope");
assert_eq!(err.error_type, NotifluxErrorType::JWTError);
assert_eq!(err.message, Some("Invalid scope: invalid".to_owned()));
}
}
26 changes: 19 additions & 7 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use anyhow::{Context, Result};
use base64::prelude::*;
use std::env;
use std::sync::OnceLock;

use crate::{NotifluxError, NotifluxErrorType};

#[derive(Debug, Clone)]
pub struct Config {
pub jwt_public_key: Vec<u8>,
Expand All @@ -18,13 +19,18 @@ const DEFAULT_WORKER_COUNT: usize = 4;
static CONFIG: OnceLock<Config> = OnceLock::new();

impl Config {
pub fn init_from_env() -> Result<Self> {
let jwt_public_key_b64 =
env::var("JWT_PUBLIC_KEY_B64").context("Unable to read JWT_PUBLIC_KEY_B64 from env")?;
pub fn init_from_env() -> Result<Self, NotifluxError> {
let jwt_public_key_b64 = env::var("JWT_PUBLIC_KEY_B64").map_err(|_| NotifluxError {
message: Some("JWT_PUBLIC_KEY_B64 is not set in env".to_string()),
error_type: NotifluxErrorType::EnvError,
})?;

let jwt_public_key = BASE64_STANDARD
.decode(jwt_public_key_b64.as_bytes())
.context("Unable to Base64 decode JWT_PUBLIC_KEY_B64")?;

.map_err(|_| NotifluxError {
message: Some("Unable to Base64 decode JWT_PUBLIC_KEY_B64".to_string()),
error_type: NotifluxErrorType::Base64DecodeError,
})?;
let port = env::var("PORT")
.unwrap_or_else(|_| DEFAULT_PORT.to_string())
.parse::<u16>()?;
Expand All @@ -43,5 +49,11 @@ impl Config {
}

pub fn get_config() -> &'static Config {
CONFIG.get_or_init(|| Config::init_from_env().expect("Failed to initialize config"))
CONFIG.get_or_init(|| match Config::init_from_env() {
Ok(config) => config,
Err(e) => {
log::error!("Error initializing config: {}", e);
std::process::exit(1);
}
})
}
19 changes: 9 additions & 10 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ pub enum NotifluxErrorType {
Error,
ValidationError,
Base64DecodeError,
JWTDecodeError,
JWTError,
ConfigError,
}

#[derive(Debug, Eq, PartialEq)]
Expand Down Expand Up @@ -37,7 +38,10 @@ impl ResponseError for NotifluxErrorType {

impl fmt::Display for NotifluxError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "NotifluxError: {:?}", self.error_type)
match self.message {
Some(ref message) => write!(f, "[{}] {}", self.error_type, message),
None => write!(f, "{}", self.error_type),
}
}
}

Expand All @@ -48,7 +52,8 @@ impl ResponseError for NotifluxError {
| NotifluxErrorType::Error
| NotifluxErrorType::IOError
| NotifluxErrorType::Base64DecodeError
| NotifluxErrorType::JWTDecodeError => StatusCode::INTERNAL_SERVER_ERROR,
| NotifluxErrorType::ConfigError
| NotifluxErrorType::JWTError => StatusCode::INTERNAL_SERVER_ERROR,
NotifluxErrorType::ValidationError => StatusCode::BAD_REQUEST,
}
}
Expand Down Expand Up @@ -117,13 +122,7 @@ impl From<jsonwebtoken::errors::Error> for NotifluxError {
log::error!("JWT decode error: {}", error);
NotifluxError {
message: Some("Unexpected JWT decode error".to_string()),
error_type: NotifluxErrorType::JWTDecodeError,
error_type: NotifluxErrorType::JWTError,
}
}
}

impl From<NotifluxError> for anyhow::Error {
fn from(error: NotifluxError) -> Self {
anyhow::Error::msg(error.message())
}
}
4 changes: 2 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::Result;
use notiflux::NotifluxError;

#[actix_web::main]
async fn main() -> Result<()> {
async fn main() -> Result<(), NotifluxError> {
notiflux::run().await
}

0 comments on commit 9654ccc

Please sign in to comment.