Craft Features
Craft allows developers to automatically generate handlers and endpoints using simple annotations, while seamlessly integrating with OpenAPI documentation generation.
Use Cases
The Craft feature is particularly useful in the following scenarios:
- When you need to quickly create route handlers from struct methods.
- When you want to reduce the boilerplate code for manually writing parameter extraction and error handling.
- When you need to automatically generate OpenAPI documentation for your API.
- When you want to decouple business logic from the web framework.
Basic Usage
Using the Craft feature requires importing the following modules:
use salvo::oapi::extract::*;
use salvo::prelude::*;
Creating a Service Struct
Using the #[craft]
macro to annotate your impl
block allows you to convert struct methods into handlers or endpoints.
#[derive(Clone)]
pub struct Opts {
state: i64,
}
#[craft]
impl Opts {
// Constructor
fn new(state: i64) -> Self {
Self { state }
}
// More methods...
}
Creating a Handler
Use #[craft(handler)]
to convert a method into a handler:
#[craft(handler)]
fn add1(&self, left: QueryParam<i64>, right: QueryParam<i64>) -> String {
(self.state + *left + *right).to_string()
}
This method becomes a handler that:
- Automatically extracts
left
and right
values from query parameters.
- Accesses the
state
within the struct.
- Returns the calculation result as a string response.
Creating an Endpoint
Use #[craft(endpoint)]
to convert a method into an endpoint:
#[craft(endpoint)]
pub(crate) fn add2(
self: ::std::sync::Arc<Self>,
left: QueryParam<i64>,
right: QueryParam<i64>,
) -> String {
(self.state + *left + *right).to_string()
}
Endpoints can utilize Arc
to share state, which is particularly useful when handling concurrent requests.
Static Endpoint
You can also create static endpoints that do not depend on instance state:
#[craft(endpoint(responses((status_code = 400, description = "Wrong request parameters."))))]
pub fn add3(left: QueryParam<i64>, right: QueryParam<i64>) -> String {
(*left + *right).to_string()
}
In this example, a description for a custom error response has also been added, which will be reflected in the generated OpenAPI documentation.
Salvo's oapi::extract
module provides various parameter extractors, the most common include:
QueryParam<T>
: Extracts parameters from the query string.
PathParam<T>
: Extracts parameters from the URL path.
FormData<T>
: Extracts parameters from form data.
JsonBody<T>
: Extracts parameters from the JSON request body.
These extractors automatically handle parameter parsing and type conversion, greatly simplifying the writing of handlers.
OpenAPI Integration
The Craft feature can automatically generate API documentation that conforms to the OpenAPI specification. In the example:
let router = Router::new()
.push(Router::with_path("add1").get(opts.add1()))
.push(Router::with_path("add2").get(opts.add2()))
.push(Router::with_path("add3").get(Opts::add3()));
// Generate OpenAPI documentation
let doc = OpenApi::new("Example API", "0.0.1").merge_router(&router);
// Add routes for OpenAPI documentation and Swagger UI
let router = router
.push(doc.into_router("/api-doc/openapi.json"))
.push(SwaggerUi::new("/api-doc/openapi.json").into_router("swagger-ui"));
With this configuration, the API documentation will be available at the /api-doc/openapi.json
endpoint, and Swagger UI will be available at the /swagger-ui
path.
Complete Example
Below is a complete example demonstrating how to use the Craft feature to create three different types of endpoints:
use salvo::oapi::extract::*;
use salvo::prelude::*;
use std::sync::Arc;
#[derive(Clone)]
pub struct Opts {
state: i64,
}
#[craft]
impl Opts {
fn new(state: i64) -> Self {
Self { state }
}
#[craft(handler)]
fn add1(&self, left: QueryParam<i64>, right: QueryParam<i64>) -> String {
(self.state + *left + *right).to_string()
}
#[craft(endpoint)]
pub(crate) fn add2(
self: ::std::sync::Arc<Self>,
left: QueryParam<i64>,
right: QueryParam<i64>,
) -> String {
(self.state + *left + *right).to_string()
}
#[craft(endpoint(responses((status_code = 400, description = "Wrong request parameters."))))]
pub fn add3(left: QueryParam<i64>, right: QueryParam<i64>) -> String {
(*left + *right).to_string()
}
}
#[tokio::main]
async fn main() {
// Create shared state with an initial value of 1
let opts = Arc::new(Opts::new(1));
// Configure routes for the three endpoints
let router = Router::new()
.push(Router::with_path("add1").get(opts.add1()))
.push(Router::with_path("add2").get(opts.add2()))
.push(Router::with_path("add3").get(Opts::add3()));
// Generate OpenAPI documentation
let doc = OpenApi::new("Example API", "0.0.1").merge_router(&router);
// Add routes for OpenAPI documentation and Swagger UI
let router = router
.push(doc.into_router("/api-doc/openapi.json"))
.push(SwaggerUi::new("/api-doc/openapi.json").into_router("swagger-ui"));
// Start the server on localhost:5800
let acceptor = TcpListener::new("127.0.0.1:5800").bind().await;
Server::new(acceptor).serve(router).await;
}