♻️ refactor: actual 404 error handling

This commit is contained in:
fawn 2023-08-22 23:30:34 +03:00
parent aa56704c20
commit 5849c1a7aa
Signed by: fawn
GPG key ID: 1F1D882E2C3A12D1
4 changed files with 42 additions and 25 deletions

View file

@ -2,4 +2,4 @@
fn main() {
// trigger recompilation when a new migration is added
println!("cargo:rerun-if-changed=migrations");
}
}

View file

@ -1,10 +1,8 @@
pub use actix_web::main;
use actix_web::{
delete,
error::{ErrorBadRequest, ErrorForbidden, ErrorNotFound},
get, post, web, HttpRequest, HttpResponse,
};
use chrono::{DateTime, Utc};
use chrono_tz::Tz;
use once_cell::sync::Lazy;
@ -15,6 +13,8 @@ use crate::{auth, db::Database};
mod snowflake;
mod webhook;
use self::snowflake::Snowflake;
/// The host of the server
pub static HOST: Lazy<String> =
Lazy::new(|| std::env::var("TAMAKO_HOST").unwrap_or_else(|_| "127.0.0.1".to_owned()));
@ -27,7 +27,13 @@ pub static PORT: Lazy<u16> = Lazy::new(|| {
});
/// The snowflake generator
static SNOWFLAKE: Lazy<snowflake::Snowflake> = Lazy::new(snowflake::Snowflake::new);
static SNOWFLAKE: Lazy<Snowflake> = Lazy::new(Snowflake::new);
macro_rules! bail {
($e:expr) => {
return Err($e);
};
}
/// The representation of a whisper
#[derive(Deserialize, Serialize, Debug, Clone)]
@ -56,15 +62,15 @@ impl Whisper {
fn validate(&mut self) -> actix_web::Result<()> {
self.name = self.name.take().filter(|name| !name.is_empty());
if self.message.is_empty() {
return Err(ErrorBadRequest("whispers cannot be empty"));
bail!(ErrorBadRequest("whispers cannot be empty"));
}
if let Some(name) = &self.name {
if name.len() > 32 {
return Err(ErrorBadRequest("name cannot be longer than 32 characters"));
bail!(ErrorBadRequest("name cannot be longer than 32 characters"));
}
}
if self.message.len() > 1024 {
return Err(ErrorBadRequest(
bail!(ErrorBadRequest(
"whispers cannot be longer than 1024 characters",
));
}
@ -124,15 +130,15 @@ impl Private for Vec<Whisper> {
}
}
/// Authenticates the secret key
/// Validates the secret key
#[allow(clippy::unused_async)]
#[post("/api/auth")]
pub async fn authentication(req: HttpRequest) -> actix_web::Result<HttpResponse> {
if !auth::validate_header(&req) {
return Err(ErrorForbidden("Invalid token"));
bail!(ErrorForbidden("Invalid token"));
}
Ok(HttpResponse::Ok().body("Authenticated"))
Ok(HttpResponse::Ok().body("Validated"))
}
/// Adds a new whisper
@ -222,11 +228,10 @@ pub async fn delete(
database: web::Data<Database>,
) -> Result<HttpResponse, Box<dyn std::error::Error>> {
if !auth::validate_header(&req) {
return Err(actix_web::error::ErrorForbidden("Invalid token").into());
bail!(Box::new(ErrorForbidden("Invalid token")));
}
let snowflake = path.into_inner();
database
.delete(snowflake)
.await

View file

@ -1,4 +1,10 @@
use actix_web::{middleware, web, App, HttpServer};
use actix_files::Files;
use actix_governor::{Governor, GovernorConfigBuilder};
use actix_web::{
http::StatusCode,
middleware::{Compress, ErrorHandlers, NormalizePath, TrailingSlash},
web, App, HttpServer,
};
mod api;
mod auth;
@ -17,21 +23,20 @@ async fn main() -> eyre::Result<()> {
.wrap(actix_logger::Logger::new(twink::fmt!(
"<green>%s <purple>%r</> took <cyan>%Dms</> | %{X-Forwarded-For}i <i>%{User-Agent}i</>"
)))
.wrap(middleware::Compress::default())
.wrap(middleware::NormalizePath::new(
middleware::TrailingSlash::Trim,
))
.wrap(Compress::default())
.wrap(NormalizePath::new(TrailingSlash::Trim))
.wrap(ErrorHandlers::new().handler(StatusCode::NOT_FOUND, templates::not_found))
.service(templates::home)
.service(templates::auth)
.service(actix_files::Files::new("/assets", "assets"))
.service(Files::new("/assets", "assets"))
.service(web::resource("/api/health").route(web::get().to(|| async { "💚" })))
.service(api::list)
.service(api::get)
.service(
web::resource("/api/whisper")
.route(web::post().to(api::add))
.wrap(actix_governor::Governor::new(
&actix_governor::GovernorConfigBuilder::default()
.wrap(Governor::new(
&GovernorConfigBuilder::default()
.per_second(360)
.burst_size(2)
.finish()
@ -40,7 +45,6 @@ async fn main() -> eyre::Result<()> {
)
.service(api::delete)
.service(api::authentication)
.default_service(web::to(templates::not_found))
})
.bind((api::HOST.as_str(), *api::PORT))?
.run()

View file

@ -1,4 +1,7 @@
use actix_web::{get, web, HttpRequest, Responder};
use actix_web::{
dev::ServiceResponse, get, middleware::ErrorHandlerResponse, web, HttpRequest, HttpResponse,
Responder,
};
use askama::Template;
use crate::{
@ -112,7 +115,12 @@ impl NotFoundTemplate {
}
/// Renders the not found page
#[allow(clippy::unused_async)]
pub async fn not_found() -> impl Responder {
NotFoundTemplate::new()
#[allow(clippy::unnecessary_wraps)]
pub fn not_found<B>(res: ServiceResponse<B>) -> actix_web::Result<ErrorHandlerResponse<B>> {
Ok(ErrorHandlerResponse::Response(ServiceResponse::new(
res.into_parts().0,
HttpResponse::NotFound()
.body(NotFoundTemplate::new().render().unwrap())
.map_into_right_body(),
)))
}