Skip to content

Commit 8ee3217

Browse files
committed
Let API handlers return arbitrary response parts
1 parent 596eaf3 commit 8ee3217

7 files changed

Lines changed: 33 additions & 9 deletions

File tree

backend/api.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,31 @@ impl<T: Serialize> IntoResponse for Json<T> {
187187
struct Query<T>(pub T);
188188

189189
/// An API response type.
190-
type Response<T> = std::result::Result<(StatusCode, Json<T>), Error>;
190+
trait Response<T>: IntoResponse {}
191+
192+
/// Implements [`Response`] for types with various tuple lengths.
193+
macro_rules! impl_response {
194+
( $($ty:ident),* $(,)? ) => {
195+
// Always require an explicit status code in `Ok` responses so the most appropriate
196+
// successful status code is more likely to be considered each time.
197+
impl<R, $($ty,)*> Response<R> for std::result::Result<(StatusCode, $($ty,)* Json<R>), Error>
198+
where
199+
std::result::Result<(StatusCode, $($ty,)* Json<R>), Error>: IntoResponse,
200+
{
201+
}
202+
}
203+
}
204+
205+
impl_response!();
206+
impl_response!(T1);
207+
impl_response!(T1, T2);
208+
impl_response!(T1, T2, T3);
209+
impl_response!(T1, T2, T3, T4);
210+
impl_response!(T1, T2, T3, T4, T5);
211+
impl_response!(T1, T2, T3, T4, T5, T6);
212+
impl_response!(T1, T2, T3, T4, T5, T6, T7);
213+
impl_response!(T1, T2, T3, T4, T5, T6, T7, T8);
214+
impl_response!(T1, T2, T3, T4, T5, T6, T7, T8, T9);
191215

192216
/// Routes a request to an API endpoint.
193217
pub(super) async fn handle(request: Request) -> axum::response::Response {

backend/api/routes/v1/email_verification.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ pub(crate) enum GetQuery {
4747
///
4848
/// See [`crate::api::Error`].
4949
#[debug_handler]
50-
pub(crate) async fn get(Query(query): Query<GetQuery>) -> Response<GetResponse> {
50+
pub(crate) async fn get(Query(query): Query<GetQuery>) -> impl Response<GetResponse> {
5151
let email = match query {
5252
GetQuery::Token { token } => {
5353
let token_hash = hash_without_salt(&token);
@@ -122,7 +122,7 @@ pub(crate) struct PostRequest {
122122
///
123123
/// See [`crate::api::Error`].
124124
#[debug_handler]
125-
pub(crate) async fn post(Json(body): Json<PostRequest>) -> Response<PostResponse> {
125+
pub(crate) async fn post(Json(body): Json<PostRequest>) -> impl Response<PostResponse> {
126126
// We don't want bots creating accounts or spamming people with verification emails.
127127
if !captcha::verify(&body.captcha_token).await? {
128128
return Err(api::Error::CaptchaFailed);

backend/api/routes/v1/email_verification/code.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub(crate) struct PostQuery {
2525
///
2626
/// See [`crate::api::Error`].
2727
#[debug_handler]
28-
pub(crate) async fn post(Query(query): Query<PostQuery>) -> Response<PostResponse> {
28+
pub(crate) async fn post(Query(query): Query<PostQuery>) -> impl Response<PostResponse> {
2929
let token_hash = hash_without_salt(&query.token);
3030

3131
let code = generate_short_code();

backend/api/routes/v1/password_reset.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ pub(crate) struct GetQuery {
3535
///
3636
/// See [`crate::api::Error`].
3737
#[debug_handler]
38-
pub(crate) async fn get(Query(query): Query<GetQuery>) -> Response<GetResponse> {
38+
pub(crate) async fn get(Query(query): Query<GetQuery>) -> impl Response<GetResponse> {
3939
let token_hash = hash_without_salt(&query.token);
4040

4141
let Some(password_reset) = db::transaction!(async |tx| -> TxResult<_, api::Error> {
@@ -87,7 +87,7 @@ pub(crate) struct PostRequest {
8787
///
8888
/// See [`crate::api::Error`].
8989
#[debug_handler]
90-
pub(crate) async fn post(Json(body): Json<PostRequest>) -> Response<PostResponse> {
90+
pub(crate) async fn post(Json(body): Json<PostRequest>) -> impl Response<PostResponse> {
9191
// We don't want bots spamming people with password reset emails.
9292
if !captcha::verify(&body.captcha_token).await? {
9393
return Err(api::Error::CaptchaFailed);

backend/api/routes/v1/password_reset/password.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ pub(crate) struct PostRequest {
3636
pub(crate) async fn post(
3737
Query(query): Query<PostQuery>,
3838
Json(body): Json<PostRequest>,
39-
) -> Response<PostResponse> {
39+
) -> impl Response<PostResponse> {
4040
let token_hash = hash_without_salt(&query.token);
4141

4242
let password_hash = hash_with_salt(&body.password);

backend/api/routes/v1/sessions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ pub(crate) struct PostRequest {
4949
pub(crate) async fn post(
5050
cookies: Cookies,
5151
Json(body): Json<PostRequest>,
52-
) -> Response<PostResponse> {
52+
) -> impl Response<PostResponse> {
5353
let token = db::transaction!(async |tx| -> TxResult<_, api::Error> {
5454
let Some(user) = sqlx::query!(
5555
"SELECT id, password_hash FROM users

backend/api/routes/v1/users.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ pub(crate) struct PostRequest {
3939
///
4040
/// See [`crate::api::Error`].
4141
#[debug_handler]
42-
pub(crate) async fn post(Json(body): Json<PostRequest>) -> Response<PostResponse> {
42+
pub(crate) async fn post(Json(body): Json<PostRequest>) -> impl Response<PostResponse> {
4343
let mut user_id = NewUserId::generate();
4444

4545
let password_hash = hash_with_salt(&body.password);

0 commit comments

Comments
 (0)