I have a requirement that I need to handle a request with multiple ways in a same HTTP route, according to the JSON in the body.
There is a total different JSON object in route root("/")
like
{
"challenge": "b397743d-51c9-40c2-a529-098fd2ff7b4a",
"token": "1p37CgbHAxiVCmzPhY8epfT2rV7YBSQi",
"type": "url_verification"
}
{
"schema": "2.0",
"header": {
"event_id": "f7984f25108f8137722bb63cee927e66",
"token": "066zT6pS4QCbgj5Do145GfDbbagCHGgF",
"create_time": "1603977298000000",
"event_type": "contact.user_group.created_v3",
"tenant_key": "xxxxxxx",
"app_id": "cli_xxxxxxxx",
},
"event":{
}
}
Here is my Rust code:
#[derive(Deserialize, Serialize)]
struct UrlVerification {
challenge: String,
token: String,
r#type: String,
}
#[derive(Deserialize, Serialize)]
struct UrlVerificationResp {
challenge: String,
}
#[tokio::main]
async fn main() {
pretty_env_logger::init();
let app = Router::new()
.route("/", post(url_verification));
// .route("/", post(msg_recv)); // Overlapping method route
let addr = SocketAddr::from(([127,0,0,1], 3000));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
// can not receive a parsed json as a params
async fn url_verification(Json(verified): Json<UrlVerification>) -> impl IntoResponse {
(StatusCode::OK, Json(UrlVerificationResp {
challenge: verified.challenge
}))
}
So in Axum, how to solve this kind of problem elegantly?
serde
can have "untagged enums", where it will try to deserialize into the first variant it can. You can use this to represent data types that may be one of a number of formats.
#[derive(Deserialize)]
#[serde(untagged)]
enum RootRequest {
UrlVerification(UrlVerification),
RecieveMessage(ReceiveMessage),
}
With this, you can have a single route that deserializes the body to this untagged enum type, and delegates to different implementations based on what the actual received request is:
#[tokio::main]
async fn main() {
// ...
let app = Router::new()
.route("/", post(root_route));
// ...
}
async fn root_route(Json(body): Json<RootRequest>) -> Response {
match body {
RootRequest::UrlVerification(verified) => {
url_verification(verified).await.into_response()
}
RootRequest::ReceiveMessage(msg) => {
msg_recv(msg).await.into_response()
}
}
}
async fn url_verification(verified: UrlVerification) -> impl IntoResponse {
// ...
}
async fn recv_msg(msg: ReceiveMessage) -> impl IntoResponse {
// ...
}