Restful API的開發者應該都熟悉,我們會撰寫符合OpenAPI規格的文件以便於串接。在Rust中,除了poem這個框架原生支持OpenAPI開發外,其他框架則可以通過utoipa
這個套件來協助生成OpenAPI文件。首先,為專案添加套件依賴:
cargo add utoipa --features "utoipa/axum_extras utoipa/uuid"
utoipa對於幾個常見的框架都有支援。如果需要使用UUID,則需要額外開啟特性。另外讓我們再安裝一個套件,幫站台加上Swagger UI:
cargo add utoipa-swagger-ui --features axum
接下來我們就要來開始撰寫文件了。
先增加doc.rs
用來放文件
use crate::handler::*;
use utoipa::OpenApi;
#[derive(OpenApi)]
#[openapi(
paths(health_check, subscribe),
components(schemas(NewSubscriber))
)]
pub struct ApiDoc;
我們要先建立一個結構用來操作OpenAPI,[derive(OpenApi)]
讓套件可以透過這個結構生成文件。在utoipa中我們需要手動將handler與操作資源加入文件中,這邊可以注意一下因為restful api為資源導向的,所以在設計承接HTTP請求的物件時(components區塊),應該要思考這個物件對外呈現名稱的意義。
接下來我們就直接把文件寫在handler上,以訂閱的API為例:
#[utoipa::path(
post,
path = "/subscriptions",
tag = "subscription",
request_body(
content = NewSubscriber,
content_type = "application/x-www-form-urlencoded"),
responses(
(status = 200),
(status = 400)
))]
pub async fn subscribe(state: State<AppState>, Form(data): Form<NewSubscriber>) -> Result<StatusCode,StatusCode> {
//// 省略內容
}
#[derive(Deserialize, Debug, ToSchema)]
pub struct NewSubscriber {
/// Subscriber Email
pub email: Option<String>,
/// Subscriber Name
pub name: Option<String>,
}
透過utoipa::path
來撰寫API的規格,我們要注意他的HTTP Method,標示出路徑。關於request body可以直接參考使用的物件,最後要在NewSubscriber
上加上ToSchema
特性,並且用註解標示出屬性的名稱,這樣就可以順利的產出文件了。
最後我們調整一下路由
//// application.rs
pub fn build() -> Router {
let database = Database::connect("postgres://postgres:postgres@localhost:5432/marvinhsu_zero_to_production")
.unwrap();
let app = Router::new()
.route("/", get(health_check))
.route("/subscriptions", post(subscribe))
/// 在路由中加入swagger
.merge(SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", ApiDoc::openapi()))
.with_state(AppState {
database
});;
app
}
因為前面已經透過#[derive(OpenApi)]對ApiDoc
加工,utoipa自動幫它加上了openapi()方法。merge
是Axum提供的方法,可以將多個路由結構合併,有利於將應用程式拆分成更小的模組。接下來運行程式應該就可以看到 Swagger UI了。
在加上OpenAPI的過程中,對我來說最大的收穫其實是好好的讀了一下OpenAPI的規格,由於平常比較少直接撰寫文件(.Net自動生成太方便了),也因此不會注意到一些細節,比如說我們在看swagger的時候只會把焦點放在api的部份,但其實對於API溝通來說,schema的語意也非常重要。