1 unstable release
0.1.0 | Jun 22, 2023 |
---|
#859 in Procedural macros
10KB
62 lines
Table of Contents
What is this?
api_type_handler
is a procedural macro designed to simplify the process of creating APIs with Actix-web, making your code cleaner and more maintainable.
Motivation
Ordinarily, when creating APIs with Actix-web, functions carry arguments and return values as follows:
pub async fn register(
query: actix_web::web::Json<MyQuery>,
path: actix_web::web::Path<MyPath>,
body: actix_web::web::Form<MyForm>,
db_pool: actix_web::web::Data<Pool<MySql>>) -> impl Responder;
However, this approach has some limitations. Firstly, you must frequently use the into_inner
method on path or query to access the underlying struct. Secondly, since the return type is not a Result, you have to write extensive error branching code.
This crate was developed to overcome these hurdles. With this macro, you can now write your function as follows:
pub async fn register(
query: MyQuery,
path: MyPath,
body: MyForm,
db_pool: actix_web::web::Data<Pool<MySql>>) -> Result<MyResponse, ApiError>;
Benefit
- Simpler Arguments: Arguments are passed directly as structs, making the code cleaner and easier to read.
- Error Handling: The return type is Result, which allows for more straightforward error handling within the function using the ? operator.
- Custom Error Responses: You can control the behavior for error cases by implementing
actix_web::error::ResponseError
for ApiError. - Flexible Types: The function takes MyStruct as an argument and a Result-wrapped MyResponse as a return type, enabling the creation of unique argument and return types for each API.
By using the crate, you can achieve streamlined and maintainable code structures, reduce redundancy, and improve the overall readability and maintainability of your Actix-web applications.
Usage
Create ApiResponse
#[derive(Serialize, Deserialize)]
pub struct ApiResponse<T> {
pub message: String,
pub data: T,
}
This becomes your response type.
Create an ApiError with Thiserror
#[derive(Error, Debug)]
pub enum ApiError {
#[error("Invalid credentials")]
InvalidCredentials,
// and so on
}
impl actix_web::error::ResponseError for ApiError {
fn error_response(&self) -> actix_web::HttpResponse {
use actix_web::http::StatusCode;
let status_code = match self {
ApiError::InvalidCredentials(_) => StatusCode::UNAUTHORIZED,
};
actix_web::HttpResponse::build(status_code).json(ApiResponse {
message: self.to_string(),
data: (),
})
}
}
This is where you define the information to be returned to the user in the event of an error. Since the error type is enum, it is possible to define error messages and statuses without omissions.
Create your argument and return types
#[derive(Deserialize, Serialize)]
pub struct MyOwnQuery {
user_name: String,
}
#[derive(Deserialize, Serialize)]
pub struct MyResponse {
id: String,
}
Note that Serialize and Deserialize are required. While the argument type may be omitted, the return type is mandatory.
Write your API's logic
#[actix_type_handler::type_handler]
pub async fn get_user_id(
query: MyOwnQuery,
db_pool: web::Data<Pool<MySql>>,
req: HttpRequest,
) -> Result<MyResponse, ApiError> {
let user= get_user(&db_pool, &req).await?;
Ok(MyResponse{id:user.id})
// any logic
}
Special arguments are query, path, and body.
query
query is a reserved argument name to receive query parameters. For example, it corresponds to a URL such as /api/search?s=123.
path
path is a reserved argument name to receive path parameters.
For example, it corresponds to a URL such as /user/{user_id
}/email.
body
body is a reserved argument name to receive body by POST and so on.
Note that these are not required arguments, but if they are taken as arguments, they must be named query, path, or body to be accepted. The type name may be defined freely.
Add to your router
Please add _api
as a postfix for your defined function name.
.route("/api/auth/register", web::post().to(auth::register_api))
Dependencies
~225–670KB
~16K SLoC