Skip to content

Commit f3d804e

Browse files
feat(shield-axum): use JSON for errors (#27)
1 parent 0a72f08 commit f3d804e

File tree

8 files changed

+87
-31
lines changed

8 files changed

+87
-31
lines changed

packages/core/shield/src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,6 @@ pub enum ShieldError {
5555
Request(String),
5656
#[error("{0}")]
5757
Validation(String),
58+
#[error("Unauthorized")]
59+
Unauthorized,
5860
}

packages/core/shield/src/user.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,20 @@ pub struct UpdateUser {
2727
}
2828

2929
#[derive(Clone, Debug, Deserialize, Serialize)]
30+
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
3031
#[serde(rename_all = "camelCase")]
3132
pub struct EmailAddress {
3233
pub id: String,
3334
pub email: String,
3435
pub is_primary: bool,
3536
pub is_verified: bool,
37+
#[serde(skip)]
3638
pub verification_token: Option<String>,
39+
#[serde(skip)]
3740
pub verification_token_expired_at: Option<DateTime<FixedOffset>>,
41+
#[serde(skip)]
3842
pub verified_at: Option<DateTime<FixedOffset>>,
43+
#[serde(skip)]
3944
pub user_id: String,
4045
}
4146

packages/integrations/shield-axum/src/error.rs

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,55 @@
11
use axum::{
22
http::StatusCode,
33
response::{IntoResponse, Response},
4+
Json,
45
};
5-
use shield::ShieldError;
6+
use serde::Serialize;
7+
use shield::{ShieldError, StorageError};
8+
9+
#[derive(Serialize)]
10+
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
11+
#[cfg_attr(feature = "utoipa", schema(as = Error))]
12+
#[serde(rename_all = "camelCase")]
13+
pub struct ErrorBody {
14+
status_code: u16,
15+
status_reason: Option<String>,
16+
message: String,
17+
}
18+
19+
impl ErrorBody {
20+
fn new(status_code: StatusCode, error: ShieldError) -> Self {
21+
Self {
22+
status_code: status_code.as_u16(),
23+
status_reason: status_code.canonical_reason().map(ToOwned::to_owned),
24+
message: error.to_string(),
25+
}
26+
}
27+
}
628

729
pub struct RouteError(ShieldError);
830

931
impl IntoResponse for RouteError {
1032
fn into_response(self) -> Response {
11-
(StatusCode::INTERNAL_SERVER_ERROR, format!("{:?}", self.0)).into_response()
33+
let status_code = match &self.0 {
34+
ShieldError::Provider(provider_error) => match provider_error {
35+
shield::ProviderError::ProviderNotFound(_) => StatusCode::NOT_FOUND,
36+
shield::ProviderError::SubproviderMissing => StatusCode::BAD_REQUEST,
37+
shield::ProviderError::SubproviderNotFound(_) => StatusCode::NOT_FOUND,
38+
},
39+
ShieldError::Configuration(_) => StatusCode::INTERNAL_SERVER_ERROR,
40+
ShieldError::Session(_) => StatusCode::INTERNAL_SERVER_ERROR,
41+
ShieldError::Storage(storage_error) => match storage_error {
42+
StorageError::Configuration(_) => StatusCode::INTERNAL_SERVER_ERROR,
43+
StorageError::Validation(_) => StatusCode::BAD_REQUEST,
44+
StorageError::NotFound(_, _) => StatusCode::NOT_FOUND,
45+
StorageError::Engine(_) => StatusCode::INTERNAL_SERVER_ERROR,
46+
},
47+
ShieldError::Request(_) => StatusCode::INTERNAL_SERVER_ERROR,
48+
ShieldError::Validation(_) => StatusCode::BAD_REQUEST,
49+
ShieldError::Unauthorized => StatusCode::UNAUTHORIZED,
50+
};
51+
52+
(status_code, Json(ErrorBody::new(status_code, self.0))).into_response()
1253
}
1354
}
1455

packages/integrations/shield-axum/src/routes/sign_in.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use axum::extract::Path;
22
use shield::{SignInRequest, User};
33

44
use crate::{
5-
error::RouteError,
5+
error::{ErrorBody, RouteError},
66
extract::{ExtractSession, ExtractShield},
77
path::AuthPathParams,
88
response::RouteResponse,
@@ -21,7 +21,9 @@ use crate::{
2121
responses(
2222
(status = 200, description = "Successfully signed in."),
2323
(status = 303, description = "Redirect to authentication provider for sign in."),
24-
(status = 500, description = "Internal server error."),
24+
(status = 400, description = "Bad request.", body = ErrorBody),
25+
(status = 404, description = "Not found.", body = ErrorBody),
26+
(status = 500, description = "Internal server error.", body = ErrorBody),
2527
)
2628
)
2729
)]

packages/integrations/shield-axum/src/routes/sign_in_callback.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use serde_json::Value;
33
use shield::{SignInCallbackRequest, User};
44

55
use crate::{
6-
error::RouteError,
6+
error::{ErrorBody, RouteError},
77
extract::{ExtractSession, ExtractShield},
88
path::AuthPathParams,
99
response::RouteResponse,
@@ -21,7 +21,9 @@ use crate::{
2121
),
2222
responses(
2323
(status = 200, description = "Successfully signed in."),
24-
(status = 500, description = "Internal server error."),
24+
(status = 400, description = "Bad request.", body = ErrorBody),
25+
(status = 404, description = "Not found.", body = ErrorBody),
26+
(status = 500, description = "Internal server error.", body = ErrorBody),
2527
)
2628
)
2729
)]

packages/integrations/shield-axum/src/routes/sign_out.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use shield::User;
22

33
use crate::{
4-
error::RouteError,
4+
error::{ErrorBody, RouteError},
55
extract::{ExtractSession, ExtractShield},
66
response::RouteResponse,
77
};
@@ -15,7 +15,8 @@ use crate::{
1515
description = "Sign out of the current account.",
1616
responses(
1717
(status = 201, description = "Successfully signed out."),
18-
(status = 500, description = "Internal server error.")
18+
(status = 400, description = "Bad request.", body = ErrorBody),
19+
(status = 500, description = "Internal server error.", body = ErrorBody),
1920
)
2021
)
2122
)]

packages/integrations/shield-axum/src/routes/subproviders.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
use axum::Json;
22
use shield::{SubproviderVisualisation, User};
33

4-
use crate::{error::RouteError, extract::ExtractShield};
4+
use crate::{
5+
error::{ErrorBody, RouteError},
6+
extract::ExtractShield,
7+
};
58

69
#[cfg_attr(
710
feature = "utoipa",
@@ -12,7 +15,7 @@ use crate::{error::RouteError, extract::ExtractShield};
1215
description = "Get a list of authentication subproviders.",
1316
responses(
1417
(status = 200, description = "List of authentication subproviders.", body = Vec<SubproviderVisualisation>),
15-
(status = 500, description = "Internal server error.")
18+
(status = 500, description = "Internal server error.", body = ErrorBody),
1619
)
1720
)
1821
)]
Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
1-
use axum::{
2-
http::StatusCode,
3-
response::{IntoResponse, Response},
4-
Json,
5-
};
1+
use axum::Json;
62
use serde::{Deserialize, Serialize};
7-
use shield::User;
3+
use shield::{EmailAddress, ShieldError, User};
84

9-
use crate::extract::ExtractUser;
5+
use crate::{
6+
error::{ErrorBody, RouteError},
7+
extract::ExtractUser,
8+
};
109

1110
#[derive(Deserialize, Serialize)]
1211
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1312
#[cfg_attr(feature = "utoipa", schema(as = User))]
1413
#[serde(rename_all = "camelCase")]
15-
struct UserBody {
14+
pub struct UserBody {
1615
id: String,
1716
name: Option<String>,
17+
email_addresses: Vec<EmailAddress>,
1818
}
1919

2020
impl UserBody {
21-
async fn new<U: User>(user: U) -> Self {
22-
// TODO: Include email addresses.
23-
// let email_addresses = user.email_addresses().await;
21+
async fn new<U: User>(user: U) -> Result<Self, ShieldError> {
22+
let email_addresses = user.email_addresses().await?;
2423

25-
Self {
24+
Ok(Self {
2625
id: user.id(),
2726
name: user.name(),
28-
}
27+
email_addresses,
28+
})
2929
}
3030
}
3131

@@ -38,15 +38,15 @@ impl UserBody {
3838
description = "Get the current user account.",
3939
responses(
4040
(status = 200, description = "Current user account.", body = UserBody),
41-
(status = 401, description = "No account signed in."),
42-
(status = 500, description = "Internal server error."),
41+
(status = 401, description = "No account signed in.", body = ErrorBody),
42+
(status = 500, description = "Internal server error.", body = ErrorBody),
4343
)
4444
)
4545
)]
46-
pub async fn user<U: User>(ExtractUser(user): ExtractUser<U>) -> Response {
47-
// TODO: Send JSON error using some util.
48-
match user {
49-
Some(user) => Json(UserBody::new(user).await).into_response(),
50-
None => (StatusCode::UNAUTHORIZED, "Unauthorized").into_response(),
51-
}
46+
pub async fn user<U: User>(
47+
ExtractUser(user): ExtractUser<U>,
48+
) -> Result<Json<UserBody>, RouteError> {
49+
let user = user.ok_or(ShieldError::Unauthorized)?;
50+
51+
Ok(Json(UserBody::new(user).await?))
5252
}

0 commit comments

Comments
 (0)