前后端分离开发中,接口需要规范化、标准化、文档化,这里使用utoipa
OpenAPI是什么
OpenAPI 是一个规范,用于描述、生产、消费和可视化 RESTful 风格的 Web 服务。它提供了一种标准的、与编程语言无关的方式来定义 API 的接口,包括路径(endpoints)、操作(如 GET、POST 等 HTTP 方法)、请求和响应的格式、参数、安全机制等细节
添加crate
tokio = { version = "1", features = ["full"] }
axum = "0.7.7"
# openapi
utoipa = { version = "5.1.1", features = ["axum_extras", "debug"] }
# openapi 文档
utoipa-swagger-ui = { version = "8.0.0", features = ["axum"] }
utoipa-axum = { version = "0", features = ["debug"] }
serde = "1"
serde_json = "1"
配置
定义Api文档结构体
// 定义 API 文档结构体
#[derive(OpenApi)]
#[openapi(
tags(
// 指定标签信息,后续用标签区给api分组
(name = CUSTOMER_TAG, description = "Customer API endpoints"),
(name = ORDER_TAG, description = "Order API endpoints")
)
)]
struct ApiDoc;
定义健康检查内部路由
/// 获取健康检查
#[utoipa::path(
method(get, head), // 方法
path = "/api/health", // 路径
responses(
// 响应状态、描述、返回内容、类型
(status = OK, description = "Success", body = str, content_type = "text/plain")
)
)]
async fn health() -> &'static str {
"ok"
}
定义内部处理器(非必须),这里的路由是swagger的内部路由
mod inner {
pub mod secret_handlers {
/// swagger内部处理器
#[utoipa::path(get, path = "/api/inner/secret", responses((status = OK, body = str)))]
pub async fn get_secret() -> &'static str {
"secret"
}
/// swagger内部处理器
#[utoipa::path(
post,
path = "/api/inner/secret",
responses((status = OK, description = "OK"))
)]
pub async fn post_secret() {
println!("You posted a secret")
}
}
}
对应界面
添加swagger-ui外部路由
#[tokio::main]
async fn main() -> Result<(), io::Error> {
let (router, api) = OpenApiRouter::with_openapi(ApiDoc::openapi())
.routes(routes!(health))
.nest("/api/customer", customer::router())
.nest("/api/order", order::router())
.routes(routes!(inner::secret_handlers::get_secret, inner::secret_handlers::post_secret))
.split_for_parts();
// 合并路由和swagger ui路由
let router = router.merge(SwaggerUi::new("/swagger-ui").url("/apidoc/openapi.json", api));
let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 8080)).await?;
axum::serve(listener, router).await
}
使用
访问:http://localhost:8080/swagger-ui/
1、utoipa
可以添加方法定义和描述,可以看到方法上的文档注释///
变成了swagger的方法描述,rust doc支持markdown
,你可以在swagger中使用markdown
语法
2、api分组,指定tag
字段
src/customer.rs
use axum::Json;
use serde::Serialize;
use utoipa::ToSchema;
use utoipa_axum::router::OpenApiRouter;
use utoipa_axum::routes;
/// 客户定义
#[derive(ToSchema, Serialize)]
struct Customer {
name: String,
}
/// 向父模块公开路由
pub fn router() -> OpenApiRouter {
OpenApiRouter::new().routes(routes!(get_customer))
}
/// 获取客户
///
/// 描述: 获取客户信息
#[utoipa::path(
get,
path = "",
responses((status = OK, body = Customer)),
tag = super::CUSTOMER_TAG
)]
async fn get_customer() -> Json<Customer> {
Json(Customer {
name: String::from("Bill Book"),
})
}
3、测试接口
src/order.rs
use axum::Json;
use serde::{ Deserialize, Serialize };
use utoipa::ToSchema;
use utoipa_axum::router::OpenApiRouter;
use utoipa_axum::routes;
/// 订单定义
#[derive(ToSchema, Serialize)]
struct Order {
id: i32,
name: String,
}
/// 订单请求定义
#[derive(ToSchema, Deserialize, Serialize)]
struct OrderRequest {
name: String,
}
/// 向父模块公开路由
pub fn router() -> OpenApiRouter {
OpenApiRouter::new().routes(routes!(get_order, create_order))
}
/// 获取订单
///
/// 获取id100的订单
#[utoipa::path(get, path = "", responses((status = OK, body = Order)), tag = super::ORDER_TAG)]
async fn get_order() -> Json<Order> {
Json(Order {
id: 100,
name: String::from("Bill Book"),
})
}
/// 创建订单
///
/// 这里用于描述方法的详细信息
#[utoipa::path(
post,
path = "",
responses((status = OK, body = Order)),
params(("name" = String, Path, description = "订单名")),
tag = super::ORDER_TAG
)]
async fn create_order(Json(order): Json<OrderRequest>) -> Json<Order> {
Json(Order {
id: 120,
name: order.name,
})
}
4、使用ToSchema
可以将结构体映射到swagger-ui