summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/channel/routes.rs24
-rw-r--r--src/events.rs56
-rw-r--r--src/index/routes.rs28
-rw-r--r--src/login/routes.rs41
4 files changed, 116 insertions, 33 deletions
diff --git a/src/channel/routes.rs b/src/channel/routes.rs
index 1379153..847e0b4 100644
--- a/src/channel/routes.rs
+++ b/src/channel/routes.rs
@@ -1,10 +1,12 @@
use axum::{
extract::{Form, Path, State},
- response::{IntoResponse, Redirect},
+ http::StatusCode,
+ response::{IntoResponse, Redirect, Response},
routing::post,
Router,
};
+use super::app::EventsError;
use crate::{
app::App,
clock::RequestedAt,
@@ -44,10 +46,26 @@ async fn on_send(
State(app): State<App>,
login: Login,
Form(form): Form<SendRequest>,
-) -> Result<impl IntoResponse, InternalError> {
+) -> Result<impl IntoResponse, ErrorResponse> {
app.channels()
.send(&login, &channel, &form.message, &sent_at)
- .await?;
+ .await
+ // Could impl `From` here, but it's more code and this is used once.
+ .map_err(ErrorResponse)?;
Ok(Redirect::to(&format!("/{}", channel)))
}
+
+struct ErrorResponse(EventsError);
+
+impl IntoResponse for ErrorResponse {
+ fn into_response(self) -> Response {
+ let Self(error) = self;
+ match error {
+ not_found @ EventsError::ChannelNotFound(_) => {
+ (StatusCode::NOT_FOUND, not_found.to_string()).into_response()
+ }
+ EventsError::DatabaseError(error) => InternalError::from(error).into_response(),
+ }
+ }
+}
diff --git a/src/events.rs b/src/events.rs
index 5d2dcf0..fd73d63 100644
--- a/src/events.rs
+++ b/src/events.rs
@@ -1,15 +1,16 @@
use axum::{
extract::State,
+ http::StatusCode,
response::{
sse::{self, Sse},
- IntoResponse,
+ IntoResponse, Response,
},
routing::get,
Router,
};
use axum_extra::extract::Query;
-use chrono::{format::SecondsFormat, DateTime};
-use futures::stream::{self, StreamExt as _, TryStreamExt as _};
+use chrono::{self, format::SecondsFormat, DateTime};
+use futures::stream::{self, Stream, StreamExt as _, TryStreamExt as _};
use crate::{
app::App,
@@ -34,11 +35,13 @@ async fn on_events(
_: Login, // requires auth, but doesn't actually care who you are
last_event_id: Option<LastEventId>,
Query(query): Query<EventsQuery>,
-) -> Result<impl IntoResponse, InternalError> {
+) -> Result<Events<impl Stream<Item = ChannelEvent<broadcast::Message>>>, ErrorResponse> {
let resume_at = last_event_id
.map(|LastEventId(header)| header)
.map(|header| DateTime::parse_from_rfc3339(&header))
- .transpose()?
+ .transpose()
+ // impl From would take more code; this is used once.
+ .map_err(ErrorResponse::LastEventIdError)?
.map(|ts| ts.to_utc());
let streams = stream::iter(query.channels)
@@ -55,12 +58,47 @@ async fn on_events(
}
})
.try_collect::<Vec<_>>()
- .await?;
+ .await
+ // impl From would take more code; this is used once.
+ .map_err(ErrorResponse::EventsError)?;
- let stream = stream::select_all(streams).map(to_sse_event);
- let sse = Sse::new(stream).keep_alive(sse::KeepAlive::default());
+ let stream = stream::select_all(streams);
- Ok(sse)
+ Ok(Events(stream))
+}
+
+struct Events<S>(S);
+
+impl<S> IntoResponse for Events<S>
+where
+ S: Stream<Item = ChannelEvent<broadcast::Message>> + Send + 'static,
+{
+ fn into_response(self) -> Response {
+ let Self(stream) = self;
+ let stream = stream.map(to_sse_event);
+ Sse::new(stream)
+ .keep_alive(sse::KeepAlive::default())
+ .into_response()
+ }
+}
+
+enum ErrorResponse {
+ EventsError(EventsError),
+ LastEventIdError(chrono::ParseError),
+}
+
+impl IntoResponse for ErrorResponse {
+ fn into_response(self) -> Response {
+ match self {
+ Self::EventsError(not_found @ EventsError::ChannelNotFound(_)) => {
+ (StatusCode::NOT_FOUND, not_found.to_string()).into_response()
+ }
+ Self::EventsError(other) => InternalError::from(other).into_response(),
+ Self::LastEventIdError(other) => {
+ (StatusCode::BAD_REQUEST, other.to_string()).into_response()
+ }
+ }
+ }
}
fn to_sse_event(event: ChannelEvent<broadcast::Message>) -> Result<sse::Event, serde_json::Error> {
diff --git a/src/index/routes.rs b/src/index/routes.rs
index 32c7f12..37f6dc9 100644
--- a/src/index/routes.rs
+++ b/src/index/routes.rs
@@ -1,13 +1,13 @@
use axum::{
extract::{Path, State},
http::{header, StatusCode},
- response::IntoResponse,
+ response::{IntoResponse, Response},
routing::get,
Router,
};
use maud::Markup;
-use super::templates;
+use super::{app, templates};
use crate::{
app::App,
error::InternalError,
@@ -49,11 +49,31 @@ async fn channel(
State(app): State<App>,
_: Login,
Path(channel): Path<channel::Id>,
-) -> Result<Markup, InternalError> {
- let channel = app.index().channel(&channel).await?;
+) -> Result<Markup, ChannelError> {
+ let channel = app
+ .index()
+ .channel(&channel)
+ .await
+ // impl From would work here, but it'd take more code.
+ .map_err(ChannelError)?;
Ok(templates::channel(&channel))
}
+#[derive(Debug)]
+struct ChannelError(app::Error);
+
+impl IntoResponse for ChannelError {
+ fn into_response(self) -> Response {
+ let Self(error) = self;
+ match error {
+ not_found @ app::Error::ChannelNotFound(_) => {
+ (StatusCode::NOT_FOUND, not_found.to_string()).into_response()
+ }
+ app::Error::DatabaseError(error) => InternalError::from(error).into_response(),
+ }
+ }
+}
+
pub fn router() -> Router<App> {
Router::new()
.route("/", get(index))
diff --git a/src/login/routes.rs b/src/login/routes.rs
index 3c58b10..1ed61ce 100644
--- a/src/login/routes.rs
+++ b/src/login/routes.rs
@@ -8,7 +8,7 @@ use axum::{
use crate::{app::App, clock::RequestedAt, error::InternalError};
-use super::{app::LoginError, extract::IdentityToken};
+use super::{app, extract::IdentityToken};
pub fn router() -> Router<App> {
Router::new()
@@ -27,29 +27,36 @@ async fn on_login(
RequestedAt(now): RequestedAt,
identity: IdentityToken,
Form(form): Form<LoginRequest>,
-) -> Result<impl IntoResponse, InternalError> {
- match app.logins().login(&form.name, &form.password, now).await {
- Ok(token) => {
- let identity = identity.set(&token);
- Ok(LoginResponse::Successful(identity))
- }
- Err(LoginError::Rejected) => Ok(LoginResponse::Rejected),
- Err(other) => Err(other.into()),
- }
+) -> Result<LoginSuccess, LoginError> {
+ let token = app
+ .logins()
+ .login(&form.name, &form.password, now)
+ .await
+ .map_err(LoginError)?;
+ let identity = identity.set(&token);
+ Ok(LoginSuccess(identity))
}
-enum LoginResponse {
- Rejected,
- Successful(IdentityToken),
+struct LoginSuccess(IdentityToken);
+
+impl IntoResponse for LoginSuccess {
+ fn into_response(self) -> Response {
+ let Self(identity) = self;
+ (identity, Redirect::to("/")).into_response()
+ }
}
-impl IntoResponse for LoginResponse {
+struct LoginError(app::LoginError);
+
+impl IntoResponse for LoginError {
fn into_response(self) -> Response {
- match self {
- Self::Successful(identity) => (identity, Redirect::to("/")).into_response(),
- Self::Rejected => {
+ let Self(error) = self;
+ match error {
+ app::LoginError::Rejected => {
(StatusCode::UNAUTHORIZED, "invalid name or password").into_response()
}
+ app::LoginError::DatabaseError(error) => InternalError::from(error).into_response(),
+ app::LoginError::PasswordHashError(error) => InternalError::from(error).into_response(),
}
}
}