From 32c9ab3c5682a2f9599f8e5ccb0b45b5e71f59c1 Mon Sep 17 00:00:00 2001 From: FlakM <maciej.jan.flak@gmail.com> Date: Tue, 25 Jan 2022 16:20:00 +0100 Subject: [PATCH] Add sqlx example with migrations (#722) * Add sqlx example with migrations Simple use case for sqlx based on tokio postgres example. Sqlite database is created on execution in ./target directory and migrations are then run against it. * sqlx example uses postgres instead of sqlite3 Also removed migrations and database creation code. --- examples/sqlx-postgres/Cargo.toml | 14 ++++ examples/sqlx-postgres/src/main.rs | 112 +++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 examples/sqlx-postgres/Cargo.toml create mode 100644 examples/sqlx-postgres/src/main.rs diff --git a/examples/sqlx-postgres/Cargo.toml b/examples/sqlx-postgres/Cargo.toml new file mode 100644 index 00000000..32bfb916 --- /dev/null +++ b/examples/sqlx-postgres/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "example-sqlx-postgres" +version = "0.1.0" +edition = "2018" +publish = false + +[dependencies] +axum = { path = "../../axum" } +tokio = { version = "1.0", features = ["full"] } +tracing = "0.1" +tracing-subscriber = { version="0.3", features = ["env-filter"] } + + +sqlx = { version = "0.5.10", features = ["runtime-tokio-rustls", "any", "postgres"] } diff --git a/examples/sqlx-postgres/src/main.rs b/examples/sqlx-postgres/src/main.rs new file mode 100644 index 00000000..655f9c74 --- /dev/null +++ b/examples/sqlx-postgres/src/main.rs @@ -0,0 +1,112 @@ +//! Example of application using https://github.com/launchbadge/sqlx +//! +//! Run with +//! +//! ```not_rust +//! cargo run -p example-sqlx-postgres +//! ``` +//! +//! Test with curl: +//! +//! ```not_rust +//! curl 127.0.0.1:3000 +//! curl -X POST 127.0.0.1:3000 +//! ``` + +use axum::{ + async_trait, + extract::{Extension, FromRequest, RequestParts}, + http::StatusCode, + routing::get, + AddExtensionLayer, Router, +}; +use sqlx::postgres::{PgPool, PgPoolOptions}; + +use std::{net::SocketAddr, time::Duration}; + +#[tokio::main] +async fn main() { + // Set the RUST_LOG, if it hasn't been explicitly defined + if std::env::var_os("RUST_LOG").is_none() { + std::env::set_var("RUST_LOG", "example_tokio_postgres=debug") + } + tracing_subscriber::fmt::init(); + + let db_connection_str = std::env::var("DATABASE_URL") + .unwrap_or_else(|_| "postgres://postgres:password@localhost".to_string()); + + // setup connection pool + let pool = PgPoolOptions::new() + .max_connections(5) + .connect_timeout(Duration::from_secs(3)) + .connect(&db_connection_str) + .await + .expect("can connect to database"); + + // build our application with some routes + let app = Router::new() + .route( + "/", + get(using_connection_pool_extractor).post(using_connection_extractor), + ) + .layer(AddExtensionLayer::new(pool)); + + // run it with hyper + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + tracing::debug!("listening on {}", addr); + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); +} + +// we can exact the connection pool with `Extension` +async fn using_connection_pool_extractor( + Extension(pool): Extension<PgPool>, +) -> Result<String, (StatusCode, String)> { + sqlx::query_scalar("select 'hello world from pg'") + .fetch_one(&pool) + .await + .map_err(internal_error) +} + +// we can also write a custom extractor that grabs a connection from the pool +// which setup is appropriate depends on your application +struct DatabaseConnection(sqlx::pool::PoolConnection<sqlx::Postgres>); + +#[async_trait] +impl<B> FromRequest<B> for DatabaseConnection +where + B: Send, +{ + type Rejection = (StatusCode, String); + + async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> { + let Extension(pool) = Extension::<PgPool>::from_request(req) + .await + .map_err(internal_error)?; + + let conn = pool.acquire().await.map_err(internal_error)?; + + Ok(Self(conn)) + } +} + +async fn using_connection_extractor( + DatabaseConnection(conn): DatabaseConnection, +) -> Result<String, (StatusCode, String)> { + let mut conn = conn; + sqlx::query_scalar("select 'hello world from pg'") + .fetch_one(&mut conn) + .await + .map_err(internal_error) +} + +/// Utility function for mapping any error into a `500 Internal Server Error` +/// response. +fn internal_error<E>(err: E) -> (StatusCode, String) +where + E: std::error::Error, +{ + (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) +}