summaryrefslogtreecommitdiff
path: root/src/channel
diff options
context:
space:
mode:
Diffstat (limited to 'src/channel')
-rw-r--r--src/channel/app.rs10
-rw-r--r--src/channel/routes.rs121
-rw-r--r--src/channel/routes/channel/delete.rs39
-rw-r--r--src/channel/routes/channel/mod.rs9
-rw-r--r--src/channel/routes/channel/post.rs47
-rw-r--r--src/channel/routes/channel/test.rs (renamed from src/channel/routes/test/on_send.rs)12
-rw-r--r--src/channel/routes/mod.rs19
-rw-r--r--src/channel/routes/post.rs49
-rw-r--r--src/channel/routes/test.rs (renamed from src/channel/routes/test/on_create.rs)37
-rw-r--r--src/channel/routes/test/mod.rs2
10 files changed, 187 insertions, 158 deletions
diff --git a/src/channel/app.rs b/src/channel/app.rs
index 5d6cada..46eaba8 100644
--- a/src/channel/app.rs
+++ b/src/channel/app.rs
@@ -122,7 +122,7 @@ pub enum CreateError {
#[error("channel named {0} already exists")]
DuplicateName(String),
#[error(transparent)]
- DatabaseError(#[from] sqlx::Error),
+ Database(#[from] sqlx::Error),
}
#[derive(Debug, thiserror::Error)]
@@ -130,11 +130,5 @@ pub enum Error {
#[error("channel {0} not found")]
NotFound(Id),
#[error(transparent)]
- DatabaseError(#[from] sqlx::Error),
-}
-
-#[derive(Debug, thiserror::Error)]
-pub enum InternalError {
- #[error(transparent)]
- DatabaseError(#[from] sqlx::Error),
+ Database(#[from] sqlx::Error),
}
diff --git a/src/channel/routes.rs b/src/channel/routes.rs
deleted file mode 100644
index eaf7962..0000000
--- a/src/channel/routes.rs
+++ /dev/null
@@ -1,121 +0,0 @@
-use axum::{
- extract::{Json, Path, State},
- http::StatusCode,
- response::{IntoResponse, Response},
- routing::{delete, post},
- Router,
-};
-
-use super::{app, Channel, Id};
-use crate::{
- app::App,
- clock::RequestedAt,
- error::{Internal, NotFound},
- login::Login,
- message::app::SendError,
-};
-
-#[cfg(test)]
-mod test;
-
-pub fn router() -> Router<App> {
- Router::new()
- .route("/api/channels", post(on_create))
- .route("/api/channels/:channel", post(on_send))
- .route("/api/channels/:channel", delete(on_delete))
-}
-
-#[derive(Clone, serde::Deserialize)]
-struct CreateRequest {
- name: String,
-}
-
-async fn on_create(
- State(app): State<App>,
- _: Login, // requires auth, but doesn't actually care who you are
- RequestedAt(created_at): RequestedAt,
- Json(form): Json<CreateRequest>,
-) -> Result<Json<Channel>, CreateError> {
- let channel = app
- .channels()
- .create(&form.name, &created_at)
- .await
- .map_err(CreateError)?;
-
- Ok(Json(channel))
-}
-
-#[derive(Debug)]
-struct CreateError(app::CreateError);
-
-impl IntoResponse for CreateError {
- fn into_response(self) -> Response {
- let Self(error) = self;
- match error {
- duplicate @ app::CreateError::DuplicateName(_) => {
- (StatusCode::CONFLICT, duplicate.to_string()).into_response()
- }
- other => Internal::from(other).into_response(),
- }
- }
-}
-
-#[derive(Clone, serde::Deserialize)]
-struct SendRequest {
- body: String,
-}
-
-async fn on_send(
- State(app): State<App>,
- Path(channel): Path<Id>,
- RequestedAt(sent_at): RequestedAt,
- login: Login,
- Json(request): Json<SendRequest>,
-) -> Result<StatusCode, SendErrorResponse> {
- app.messages()
- .send(&channel, &login, &sent_at, &request.body)
- .await?;
-
- Ok(StatusCode::ACCEPTED)
-}
-
-#[derive(Debug, thiserror::Error)]
-#[error(transparent)]
-struct SendErrorResponse(#[from] SendError);
-
-impl IntoResponse for SendErrorResponse {
- fn into_response(self) -> Response {
- let Self(error) = self;
- match error {
- not_found @ SendError::ChannelNotFound(_) => NotFound(not_found).into_response(),
- other => Internal::from(other).into_response(),
- }
- }
-}
-
-async fn on_delete(
- State(app): State<App>,
- Path(channel): Path<Id>,
- RequestedAt(deleted_at): RequestedAt,
- _: Login,
-) -> Result<StatusCode, ErrorResponse> {
- app.channels().delete(&channel, &deleted_at).await?;
-
- Ok(StatusCode::ACCEPTED)
-}
-
-#[derive(Debug, thiserror::Error)]
-#[error(transparent)]
-struct ErrorResponse(#[from] app::Error);
-
-impl IntoResponse for ErrorResponse {
- fn into_response(self) -> Response {
- let Self(error) = self;
- match error {
- not_found @ app::Error::NotFound(_) => {
- (StatusCode::NOT_FOUND, not_found.to_string()).into_response()
- }
- other => Internal::from(other).into_response(),
- }
- }
-}
diff --git a/src/channel/routes/channel/delete.rs b/src/channel/routes/channel/delete.rs
new file mode 100644
index 0000000..efac0c0
--- /dev/null
+++ b/src/channel/routes/channel/delete.rs
@@ -0,0 +1,39 @@
+use axum::{
+ extract::{Path, State},
+ http::StatusCode,
+ response::{IntoResponse, Response},
+};
+
+use crate::{
+ app::App,
+ channel::app,
+ clock::RequestedAt,
+ error::{Internal, NotFound},
+ login::Login,
+};
+
+pub async fn handler(
+ State(app): State<App>,
+ Path(channel): Path<super::PathInfo>,
+ RequestedAt(deleted_at): RequestedAt,
+ _: Login,
+) -> Result<StatusCode, Error> {
+ app.channels().delete(&channel, &deleted_at).await?;
+
+ Ok(StatusCode::ACCEPTED)
+}
+
+#[derive(Debug, thiserror::Error)]
+#[error(transparent)]
+pub struct Error(#[from] pub app::Error);
+
+impl IntoResponse for Error {
+ fn into_response(self) -> Response {
+ let Self(error) = self;
+ #[allow(clippy::match_wildcard_for_single_variants)]
+ match error {
+ app::Error::NotFound(_) => NotFound(error).into_response(),
+ other => Internal::from(other).into_response(),
+ }
+ }
+}
diff --git a/src/channel/routes/channel/mod.rs b/src/channel/routes/channel/mod.rs
new file mode 100644
index 0000000..31a9142
--- /dev/null
+++ b/src/channel/routes/channel/mod.rs
@@ -0,0 +1,9 @@
+use crate::channel::Id;
+
+pub mod delete;
+pub mod post;
+
+#[cfg(test)]
+mod test;
+
+type PathInfo = Id;
diff --git a/src/channel/routes/channel/post.rs b/src/channel/routes/channel/post.rs
new file mode 100644
index 0000000..a71a3a0
--- /dev/null
+++ b/src/channel/routes/channel/post.rs
@@ -0,0 +1,47 @@
+use axum::{
+ extract::{Json, Path, State},
+ http::StatusCode,
+ response::{IntoResponse, Response},
+};
+
+use crate::{
+ app::App,
+ clock::RequestedAt,
+ error::{Internal, NotFound},
+ login::Login,
+ message::app::SendError,
+};
+
+pub async fn handler(
+ State(app): State<App>,
+ Path(channel): Path<super::PathInfo>,
+ RequestedAt(sent_at): RequestedAt,
+ login: Login,
+ Json(request): Json<Request>,
+) -> Result<StatusCode, Error> {
+ app.messages()
+ .send(&channel, &login, &sent_at, &request.body)
+ .await?;
+
+ Ok(StatusCode::ACCEPTED)
+}
+
+#[derive(serde::Deserialize)]
+pub struct Request {
+ pub body: String,
+}
+
+#[derive(Debug, thiserror::Error)]
+#[error(transparent)]
+pub struct Error(#[from] pub SendError);
+
+impl IntoResponse for Error {
+ fn into_response(self) -> Response {
+ let Self(error) = self;
+ #[allow(clippy::match_wildcard_for_single_variants)]
+ match error {
+ SendError::ChannelNotFound(_) => NotFound(error).into_response(),
+ other => Internal::from(other).into_response(),
+ }
+ }
+}
diff --git a/src/channel/routes/test/on_send.rs b/src/channel/routes/channel/test.rs
index 293cc56..bc02b20 100644
--- a/src/channel/routes/test/on_send.rs
+++ b/src/channel/routes/channel/test.rs
@@ -1,9 +1,9 @@
use axum::extract::{Json, Path, State};
use futures::stream::StreamExt;
+use super::post;
use crate::{
channel,
- channel::routes,
event::{self, Sequenced},
message::{self, app::SendError},
test::fixtures::{self, future::Immediately as _},
@@ -25,14 +25,14 @@ async fn messages_in_order() {
];
for (sent_at, body) in &requests {
- let request = routes::SendRequest { body: body.clone() };
+ let request = post::Request { body: body.clone() };
- routes::on_send(
+ post::handler(
State(app.clone()),
Path(channel.id.clone()),
sent_at.clone(),
sender.clone(),
- Json(request.clone()),
+ Json(request),
)
.await
.expect("sending to a valid channel");
@@ -72,10 +72,10 @@ async fn nonexistent_channel() {
let sent_at = fixtures::now();
let channel = channel::Id::generate();
- let request = routes::SendRequest {
+ let request = post::Request {
body: fixtures::message::propose(),
};
- let routes::SendErrorResponse(error) = routes::on_send(
+ let post::Error(error) = post::handler(
State(app),
Path(channel.clone()),
sent_at,
diff --git a/src/channel/routes/mod.rs b/src/channel/routes/mod.rs
new file mode 100644
index 0000000..696bd72
--- /dev/null
+++ b/src/channel/routes/mod.rs
@@ -0,0 +1,19 @@
+use axum::{
+ routing::{delete, post},
+ Router,
+};
+
+use crate::app::App;
+
+mod channel;
+mod post;
+
+#[cfg(test)]
+mod test;
+
+pub fn router() -> Router<App> {
+ Router::new()
+ .route("/api/channels", post(post::handler))
+ .route("/api/channels/:channel", post(channel::post::handler))
+ .route("/api/channels/:channel", delete(channel::delete::handler))
+}
diff --git a/src/channel/routes/post.rs b/src/channel/routes/post.rs
new file mode 100644
index 0000000..d694f8b
--- /dev/null
+++ b/src/channel/routes/post.rs
@@ -0,0 +1,49 @@
+use axum::{
+ extract::{Json, State},
+ http::StatusCode,
+ response::{self, IntoResponse},
+};
+
+use crate::{
+ app::App,
+ channel::{app, Channel},
+ clock::RequestedAt,
+ error::Internal,
+ login::Login,
+};
+
+pub async fn handler(
+ State(app): State<App>,
+ _: Login, // requires auth, but doesn't actually care who you are
+ RequestedAt(created_at): RequestedAt,
+ Json(request): Json<Request>,
+) -> Result<Json<Channel>, Error> {
+ let channel = app
+ .channels()
+ .create(&request.name, &created_at)
+ .await
+ .map_err(Error)?;
+
+ Ok(Json(channel))
+}
+
+#[derive(serde::Deserialize)]
+pub struct Request {
+ pub name: String,
+}
+
+#[derive(Debug)]
+pub struct Error(pub app::CreateError);
+
+impl IntoResponse for Error {
+ fn into_response(self) -> response::Response {
+ let Self(error) = self;
+ #[allow(clippy::match_wildcard_for_single_variants)]
+ match error {
+ app::CreateError::DuplicateName(_) => {
+ (StatusCode::CONFLICT, error.to_string()).into_response()
+ }
+ other => Internal::from(other).into_response(),
+ }
+ }
+}
diff --git a/src/channel/routes/test/on_create.rs b/src/channel/routes/test.rs
index eeecc7f..81f1465 100644
--- a/src/channel/routes/test/on_create.rs
+++ b/src/channel/routes/test.rs
@@ -1,8 +1,9 @@
use axum::extract::{Json, State};
use futures::stream::StreamExt as _;
+use super::post;
use crate::{
- channel::{self, app, routes},
+ channel::{self, app},
event,
test::fixtures::{self, future::Immediately as _},
};
@@ -17,19 +18,15 @@ async fn new_channel() {
// Call the endpoint
let name = fixtures::channel::propose();
- let request = routes::CreateRequest { name };
- let Json(response_channel) = routes::on_create(
- State(app.clone()),
- creator,
- fixtures::now(),
- Json(request.clone()),
- )
- .await
- .expect("new channel in an empty app");
+ let request = post::Request { name: name.clone() };
+ let Json(response_channel) =
+ post::handler(State(app.clone()), creator, fixtures::now(), Json(request))
+ .await
+ .expect("new channel in an empty app");
// Verify the structure of the response
- assert_eq!(request.name, response_channel.name);
+ assert_eq!(name, response_channel.name);
// Verify the semantics
@@ -69,20 +66,18 @@ async fn duplicate_name() {
// Call the endpoint
- let request = routes::CreateRequest { name: channel.name };
- let routes::CreateError(error) = routes::on_create(
- State(app.clone()),
- creator,
- fixtures::now(),
- Json(request.clone()),
- )
- .await
- .expect_err("duplicate channel name");
+ let request = post::Request {
+ name: channel.name.clone(),
+ };
+ let post::Error(error) =
+ post::handler(State(app.clone()), creator, fixtures::now(), Json(request))
+ .await
+ .expect_err("duplicate channel name should fail the request");
// Verify the structure of the response
assert!(matches!(
error,
- app::CreateError::DuplicateName(name) if request.name == name
+ app::CreateError::DuplicateName(name) if channel.name == name
));
}
diff --git a/src/channel/routes/test/mod.rs b/src/channel/routes/test/mod.rs
deleted file mode 100644
index 3e5aa17..0000000
--- a/src/channel/routes/test/mod.rs
+++ /dev/null
@@ -1,2 +0,0 @@
-mod on_create;
-mod on_send;