Rust: actix-web global extractor error handlers.
Continuing with our actix-web learning application, we implement global extractor error handlers for both application/json
and application/x-www-form-urlencoded
data. This enhances the robustness of the code. Subsequently, we refactor the login data extraction process to leverage the global extractor error handlers.
π¦ Index of the Complete Series.
Rust: actix-web global extractor error handlers. |
π Please note, complete code for this post can be downloaded from GitHub with:
git clone -b v0.9.0 https://github.com/behai-nguyen/rust_web_01.git
The actix-web learning application mentioned above has been discussed in the following eight previous posts:
- Rust web application: MySQL server, sqlx, actix-web and tera.
- Rust: learning actix-web middleware 01.
- Rust: retrofit integration tests to an existing actix-web application.
- Rust: adding actix-session and actix-identity to an existing actix-web application.
- Rust: actix-web endpoints which accept both
application/x-www-form-urlencoded
andapplication/json
content types. - Rust: simple actix-web email-password login and request authentication using middleware.
- Rust: actix-web get SSL/HTTPS for localhost.
- Rust: actix-web CORS, Cookies and AJAX calls.
The code weβre developing in this post is a continuation of the code from the eighth post above. π To get the code of this eighth post, please use the following command:
git clone -b v0.8.0 https://github.com/behai-nguyen/rust_web_01.git
β Note the tag v0.8.0
.
βΆ We are not adding any new files to the project; it remains the same as in the seventh post. We are only making changes to some modules.
.
βββ Cargo.toml β
βββ README.md β
βββ src
β βββ auth_handlers.rs β
β βββ handlers.rs β
β βββ helper
β β βββ app_utils.rs β
β β βββ endpoint.rs β
β β βββ messages.rs β
β βββ helper.rs β
β βββ lib.rs β
βββ tests
βββ test_auth_handlers.rs β
βββ test_handlers.rs β
β Please note, those marked with β are updated, and those marked with β are new.
β· Currently, the application does not handle extraction errors for both
application/json
and application/x-www-form-urlencoded
data in data-related routes.
π As a reminder, we have the following existing data-related routes. Briefly:
-
Route
https://0.0.0.0:5000/data/employees
acceptsapplication/json
. For example{"last_name": "%chi", "first_name": "%ak"}
. -
Route
https://0.0.0.0:5000/ui/employees
acceptsapplication/x-www-form-urlencoded
. For examplelast_name=%chi&first_name=%ak
.
Unlike the data-related routes, the login route https://0.0.0.0:5000/api/login
currently implements a custom extractor that also handles extraction errors. Please refer
to the sections
Implementations of Routes /ui/login
and /api/login
and
How the Email-Password Login Process Works
in previous posts for more details. π₯ We will refactor this implementation
to eliminate the custom extractor and fully leverage the global extractor error
handlers that we are going to implement.
Letβs demonstrate some unhandled extraction errors for both content types.
π Please note that the ajax_test.html page is used in the examples below.
β΅ application/json
content type. First, we make an invalid
submission with empty data. Then, we submit data with an invalid field
name:
The above screenshots indicate that there is some implicit default extraction
error handling in place: the response status code is 400
for
BAD REQUEST
, and the response text contains the actual extraction
error message.
π₯ However, this behavior is not consistent with the existing
implementation for the https://0.0.0.0:5000/api/login
route, where
an extraction error always results in a JSON serialisation of
ApiStatus with a code of 400
for BAD REQUEST
, and the message containing the exact extraction error.
For more details, refer to the current implementation of
pub fn extract_employee_login(body: &Bytes, content_type: &str) -> Result<EmployeeLogin, ApiStatus>
Itβs worth noting that, as mentioned earlier,
we are also refactoring this custom extractor while retaining its current handling
of extraction errors.
βΆ application/x-www-form-urlencoded
content type.
Similar to the previous example, we also submit two invalid
requests: one with empty data and another with data containing
an invalid field name:
βΈ Implementing βglobal extractor error handlersβ for application/json
and application/x-www-form-urlencoded
data.
This involves configuring extractor configurations provided by the
actix-web crate, namely
JsonConfig and
FormConfig, respectively.
We can define custom error handlers for each content type using their
error_handler(...)
method.
In our context, we refer to these custom error handlers as βglobal extractor error handlersβ.
Based on the documentation, we implement the functions fn json_config() -> web::JsonConfig and fn form_config() -> web::FormConfig, and then register them according to the official example.
The key part is the error_handler(...)
function within
both extractor configurations:
...
.error_handler(|err, _req| {
let err_str: String = String::from(err.to_string());
error::InternalError::from_response(err,
make_api_status_response(StatusCode::BAD_REQUEST, &err_str, None)).into()
})
...
Here, err_str
represents the actual extraction error message.
We utilise the function
pub fn make_api_status_response( status_code: StatusCode, message: &str, session_id: Option<String>) -> HttpResponse
to construct a response, which is a JSON serialisation of
ApiStatus.
We can verify the effectiveness of the global extractor error handlers by repeating the previous two examples.
β΅ application/json
content type:
The screenshots confirm that we receive the expected response, which contrasts the example prior to refactoring.
βΆ application/x-www-form-urlencoded
content type:
We get the expected response. This is the example before refactoring.
β· Letβs try another example via Postman:
When an extraction error occurs, the response is a JSON serialisation of
ApiStatus. When a request to
route https://0.0.0.0:5000/ui/employees
is successful,
the response is HTML. (As a reminder, we need to set
the request authorization
header to something, for example,
chirstian.koblick.10004@gmail.com
.)
βΉ Integration tests for data-related routes.
To ensure that the global extractor error handlers function correctly, we need tests to verify their behavior.
In
tests/test_handlers.rs, weβve
implemented four failed extraction tests, each ending with _error_empty
and _error_missing_field
.
These tests closely resemble the examples shown previously. The code for the new tests is similar to existing ones, so we wonβt walk through it as they are self-explanatory.
π₯ In the new tests, take note of the error messages: "Content type error"
and "Content type error."
!
βΊ Refactoring the login data extraction process.
In the fifth post, Rust: actix-web endpoints which accept both application/x-www-form-urlencoded
and application/json
content types,
we implemented the custom extractor function
pub fn extract_employee_login(body: &Bytes, content_type: &str) -> Result<EmployeeLogin, ApiStatus>
which accepts both application/x-www-form-urlencoded
and
application/json
content types, and deserialises the byte
stream to the
EmployeeLogin struct
.
This function is currently functional. As mentioned previously, we intend to refactor the code while retaining its extraction error handling behaviors, which are now available automatically due to the introduction of global extractor error handlers.
We are eliminating this helper function and instead using the enum Either, which provides a mechanism for trying two extractors: a primary and a fallback.
In
src/auth_handlers.rs, the
login function, the endpoint
handler for route /api/login
, is updated as follows:
#[post("/login")]
pub async fn login(
request: HttpRequest,
app_state: web::Data<super::AppState>,
body: Either<web::Json<EmployeeLogin>, web::Form<EmployeeLogin>>
) -> HttpResponse {
let submitted_login = match body {
Either::Left(json) => json.into_inner(),
Either::Right(form) => form.into_inner(),
};
...
The last parameter and the return type have changed. The parameter
body
is now an
enum Either, which is the focal
point of this refactoring. The extraction process is more elegant, and
we are taking advantage of a built-in feature, which should be well-tested.
The global extractor error handlers enforce the same validations on the submitted data as the previous custom extractor helper function.
Please note the previous return type of this function:
#[post("/login")]
pub async fn login(
request: HttpRequest,
app_state: web::Data<super::AppState>,
body: Bytes
) -> Either<impl Responder, HttpResponse> {
...
There are other minor changes throughout the function, but they are self-explanatory.
Letβs observe the refactored login code in action.
β΅ application/json
content type. Two invalid requests and one valid request:
βΆ application/x-www-form-urlencoded
content type.
Two invalid requests and one valid request:
β· application/x-www-form-urlencoded
content type.
Using Postman.
Two invalid requests and one valid request:
βΈ application/x-www-form-urlencoded
content type.
Using the applicationβs login page, first log in with an invalid email,
then log in again with a valid email and password.
β» Integration tests for invalid login data.
These tests should have been written earlier, immediately after completing the login functionalities.
In the test module,
tests/test_auth_handlers.rs, weβve added four failed extraction
tests, denoted by functions ending with _error_empty
and
_error_missing_field
.
βΌ We have reached the conclusion of this post. I donβt feel that implementing the
function extract_employee_login
was a waste of time. Through this process,
Iβve gained valuable insights into Rust.
As for the next post for this project, Iβm not yet sure what it will entail πβ¦ There are still several functionalities I would like to implement. Iβll let my intuition guide me in deciding the topic for the next post.
Thank you for reading, and I hope you find the information in this post useful. Stay safe, as always.
βΏβΏβΏ
Feature image source:
- https://www.omgubuntu.co.uk/2022/09/ubuntu-2210-kinetic-kudu-default-wallpaper
- https://in.pinterest.com/pin/337277459600111737/
- https://www.rust-lang.org/
- https://www.pngitem.com/download/ibmJoR_rust-language-hd-png-download/