use std::error::Error;
use axum::{response::IntoResponse, routing::get, Json};
use http::StatusCode;
type Tx = axum_sqlx_tx::Tx<sqlx::Sqlite>;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let pool = sqlx::SqlitePool::connect("sqlite::memory:").await?;
sqlx::query("CREATE TABLE IF NOT EXISTS numbers (number INT PRIMARY KEY);")
.execute(&pool)
.await?;
let (state, layer) = Tx::setup(pool);
let app = axum::Router::new()
.route("/numbers", get(list_numbers).post(generate_number))
.layer(layer)
.with_state(state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:0").await.unwrap();
println!("Listening on {}", listener.local_addr().unwrap());
axum::serve(listener, app).await?;
Ok(())
}
async fn list_numbers(mut tx: Tx) -> Result<Json<Vec<i32>>, DbError> {
let numbers: Vec<(i32,)> = sqlx::query_as("SELECT * FROM numbers")
.fetch_all(&mut tx)
.await?;
Ok(Json(numbers.into_iter().map(|n| n.0).collect()))
}
async fn generate_number(mut tx: Tx) -> Result<(StatusCode, Json<i32>), DbError> {
let (number,): (i32,) =
sqlx::query_as("INSERT INTO numbers VALUES (random()) RETURNING number;")
.fetch_one(&mut tx)
.await?;
let status = if number > 0 {
StatusCode::OK
} else {
StatusCode::IM_A_TEAPOT
};
Ok((status, Json(number)))
}
struct DbError(sqlx::Error);
impl From<sqlx::Error> for DbError {
fn from(error: sqlx::Error) -> Self {
Self(error)
}
}
impl IntoResponse for DbError {
fn into_response(self) -> axum::response::Response {
println!("ERROR: {}", self.0);
(StatusCode::INTERNAL_SERVER_ERROR, "internal server error").into_response()
}
}