#![deny(clippy::all)]
use axum::{
Router,
extract::Path,
http::{HeaderValue, StatusCode, header},
response::{IntoResponse, Response},
routing::{delete, get},
};
use std::net::SocketAddr;
use tokio::net::TcpListener;
const KNOWN_IDS: &[&str] = &["1", "42", "100"];
const ALLOW_VALUE: &str = "GET, HEAD, PATCH";
fn is_known(id: &str) -> bool {
KNOWN_IDS.contains(&id)
}
async fn resource_get(_id: Path<String>) -> Response {
StatusCode::OK.into_response()
}
async fn resource_delete(Path(id): Path<String>) -> Response {
if is_known(&id) {
let mut resp = StatusCode::METHOD_NOT_ALLOWED.into_response();
resp.headers_mut().insert(
header::ALLOW,
HeaderValue::from_static(ALLOW_VALUE),
);
resp
} else {
StatusCode::NOT_FOUND.into_response()
}
}
async fn deletable(Path(id): Path<String>) -> Response {
if is_known(&id) {
StatusCode::NO_CONTENT.into_response()
} else {
StatusCode::NOT_FOUND.into_response()
}
}
async fn deletable_200(Path(id): Path<String>) -> Response {
if is_known(&id) {
let body = serde_json::json!({"deleted": true, "id": id});
(StatusCode::OK, axum::Json(body)).into_response()
} else {
StatusCode::NOT_FOUND.into_response()
}
}
async fn async_delete(Path(id): Path<String>) -> Response {
if is_known(&id) {
let body = serde_json::json!({"status": "queued"});
(StatusCode::ACCEPTED, axum::Json(body)).into_response()
} else {
StatusCode::NOT_FOUND.into_response()
}
}
async fn has_deps(Path(id): Path<String>) -> Response {
if is_known(&id) {
let body = serde_json::json!({
"error": "conflict",
"detail": "resource has active dependencies"
});
(StatusCode::CONFLICT, axum::Json(body)).into_response()
} else {
StatusCode::NOT_FOUND.into_response()
}
}
async fn forbidden(Path(id): Path<String>) -> Response {
if is_known(&id) {
StatusCode::FORBIDDEN.into_response()
} else {
StatusCode::NOT_FOUND.into_response()
}
}
async fn normalized(_id: Path<String>) -> Response {
StatusCode::NOT_FOUND.into_response()
}
fn router() -> Router {
Router::new()
.route("/resource/{id}", get(resource_get).delete(resource_delete))
.route("/deletable/{id}", delete(deletable))
.route("/deletable-200/{id}", delete(deletable_200))
.route("/async-delete/{id}", delete(async_delete))
.route("/has-deps/{id}", delete(has_deps))
.route("/forbidden/{id}", delete(forbidden))
.route("/normalized/{id}", delete(normalized))
}
pub async fn spawn() -> SocketAddr {
let listener = TcpListener::bind("127.0.0.1:0")
.await
.expect("failed to bind test server listener");
let addr = listener
.local_addr()
.expect("failed to read local_addr from listener");
tokio::spawn(async move {
axum::serve(listener, router())
.await
.expect("test server failed");
});
addr
}