Is it possible to extract URL path params into a Axum route_service
handler that is implemented using the tower::service_fn
error handling method seen in my main
below? From the Tower docs I see that service_fn
receives the request, but beyond that I can't figure out how to use Path
to extract the person_id
path param part of my /person/:person_id
path.
Here's the page from the Axum docs I used to implement what I have so far: https://docs.rs/axum/latest/axum/error_handling/index.html#routing-to-fallible-services
I've arrived at this implementation for a couple of reasons.
- I want to use the
?
operator in order get the actual response from the external API, whether that was success or error. - I want to be able to use the
reqwest::Error
status code when there is an error and return that status code for my API's error response.
async fn get_person(/*
Is it possible to extract the person_id here given my implemntation?
*/) -> Result<Json<serde_json::Value>, reqwest::Error> {
let request_url = format!("https://swapi.dev/api/people/{}", "1asdf".to_owned());
let response = reqwest::get(request_url).await?;
if response.status().is_success() {
let json = response.json::<serde_json::Value>().await?;
Ok(Json(json))
} else {
Err(response.error_for_status().unwrap_err())
}
}
#[tokio::main]
async fn main() {
dotenv().ok();
let client = reqwest::Client::new();
let cors = CorsLayer::new()
.allow_methods([Method::GET])
.allow_origin(Any);
let faillible_person_service = tower::service_fn(|req: Request| async {
let body = get_person().await?;
Ok::<_, reqwest::Error>(body.into_response())
});
let app = Router::new()
.route("/", get(get_weather))
.route_service(
"/person/:person_id",
HandleError::new(faillible_person_service, handle_reqwest_error),
)
.layer(cors)
.with_state(client);
let listener = tokio::net::TcpListener::bind("127.0.0.1:1337")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}
async fn handle_reqwest_error(err: reqwest::Error) -> Response {
// Rather than have my API return a 200, return the status code from the Reqwest call
let status = err.status();
let status = status.unwrap_or(reqwest::StatusCode::INTERNAL_SERVER_ERROR);
let status_as_u16 = status.as_u16();
let axum_status =
StatusCode::from_u16(status_as_u16).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
let res_json = Json(serde_json::json!({
"error": {
"message": format!("Something went wrong: {}", err),
},
}));
return (axum_status, res_json).into_response();
}