From 9da189768838eca1c31227edd6190fb2599041f2 Mon Sep 17 00:00:00 2001 From: Georg Semmler Date: Tue, 28 Mar 2023 15:32:00 +0000 Subject: [PATCH] Add a diesel and diesel-async example (#1853) Co-authored-by: David Pedersen --- examples/diesel-async-postgres/Cargo.toml | 17 +++ .../2023-03-14-180127_add_users/down.sql | 2 + .../2023-03-14-180127_add_users/up.sql | 6 + examples/diesel-async-postgres/src/main.rs | 142 ++++++++++++++++++ examples/diesel-postgres/Cargo.toml | 17 +++ .../2023-03-14-180127_add_users/down.sql | 2 + .../2023-03-14-180127_add_users/up.sql | 6 + examples/diesel-postgres/src/main.rs | 131 ++++++++++++++++ 8 files changed, 323 insertions(+) create mode 100644 examples/diesel-async-postgres/Cargo.toml create mode 100644 examples/diesel-async-postgres/migrations/2023-03-14-180127_add_users/down.sql create mode 100644 examples/diesel-async-postgres/migrations/2023-03-14-180127_add_users/up.sql create mode 100644 examples/diesel-async-postgres/src/main.rs create mode 100644 examples/diesel-postgres/Cargo.toml create mode 100644 examples/diesel-postgres/migrations/2023-03-14-180127_add_users/down.sql create mode 100644 examples/diesel-postgres/migrations/2023-03-14-180127_add_users/up.sql create mode 100644 examples/diesel-postgres/src/main.rs diff --git a/examples/diesel-async-postgres/Cargo.toml b/examples/diesel-async-postgres/Cargo.toml new file mode 100644 index 00000000..86bcc3de --- /dev/null +++ b/examples/diesel-async-postgres/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "example-diesel-async-postgres" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +axum = { path = "../../axum" } +axum-macros = { path = "../../axum-macros" } +bb8 = "0.8" +diesel = "2" +diesel-async = { version = "0.2", features = ["postgres", "bb8"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1" +tokio = { version = "1.0", features = ["full"] } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/examples/diesel-async-postgres/migrations/2023-03-14-180127_add_users/down.sql b/examples/diesel-async-postgres/migrations/2023-03-14-180127_add_users/down.sql new file mode 100644 index 00000000..ca53fc11 --- /dev/null +++ b/examples/diesel-async-postgres/migrations/2023-03-14-180127_add_users/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in "up.sql" +DROP TABLE "users"; diff --git a/examples/diesel-async-postgres/migrations/2023-03-14-180127_add_users/up.sql b/examples/diesel-async-postgres/migrations/2023-03-14-180127_add_users/up.sql new file mode 100644 index 00000000..6aaa1a31 --- /dev/null +++ b/examples/diesel-async-postgres/migrations/2023-03-14-180127_add_users/up.sql @@ -0,0 +1,6 @@ +-- Your SQL goes here +CREATE TABLE "users"( + "id" SERIAL PRIMARY KEY, + "name" TEXT NOT NULL, + "hair_color" TEXT +); diff --git a/examples/diesel-async-postgres/src/main.rs b/examples/diesel-async-postgres/src/main.rs new file mode 100644 index 00000000..0cb9ba80 --- /dev/null +++ b/examples/diesel-async-postgres/src/main.rs @@ -0,0 +1,142 @@ +//! Run with +//! +//! ```sh +//! export DATABASE_URL=postgres://localhost/your_db +//! diesel migration run +//! cargo run -p example-diesel-async-postgres +//! ``` +//! +//! Checkout the [diesel webpage](https://diesel.rs) for +//! longer guides about diesel +//! +//! Checkout the [crates.io source code](https://github.com/rust-lang/crates.io/) +//! for a real world application using axum and diesel + +use axum::{ + async_trait, + extract::{FromRef, FromRequestParts, State}, + http::{request::Parts, StatusCode}, + response::Json, + routing::{get, post}, + Router, +}; +use diesel::prelude::*; +use diesel_async::{ + pooled_connection::AsyncDieselConnectionManager, AsyncPgConnection, RunQueryDsl, +}; +use std::net::SocketAddr; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; + +// normally part of your generated schema.rs file +table! { + users (id) { + id -> Integer, + name -> Text, + hair_color -> Nullable, + } +} + +#[derive(serde::Serialize, Selectable, Queryable)] +struct User { + id: i32, + name: String, + hair_color: Option, +} + +#[derive(serde::Deserialize, Insertable)] +#[diesel(table_name = users)] +struct NewUser { + name: String, + hair_color: Option, +} + +type Pool = bb8::Pool>; + +#[tokio::main] +async fn main() { + tracing_subscriber::registry() + .with( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| "example_diesel_async_postgres=debug".into()), + ) + .with(tracing_subscriber::fmt::layer()) + .init(); + + let db_url = std::env::var("DATABASE_URL").unwrap(); + + // set up connection pool + let config = AsyncDieselConnectionManager::::new(db_url); + let pool = bb8::Pool::builder().build(config).await.unwrap(); + + // build our application with some routes + let app = Router::new() + .route("/user/list", get(list_users)) + .route("/user/create", post(create_user)) + .with_state(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(); +} + +async fn create_user( + State(pool): State, + Json(new_user): Json, +) -> Result, (StatusCode, String)> { + let mut conn = pool.get().await.map_err(internal_error)?; + + let res = diesel::insert_into(users::table) + .values(new_user) + .returning(User::as_returning()) + .get_result(&mut conn) + .await + .map_err(internal_error)?; + Ok(Json(res)) +} + +// we can also write a custom extractor that grabs a connection from the pool +// which setup is appropriate depends on your application +struct DatabaseConnection( + bb8::PooledConnection<'static, AsyncDieselConnectionManager>, +); + +#[async_trait] +impl FromRequestParts for DatabaseConnection +where + S: Send + Sync, + Pool: FromRef, +{ + type Rejection = (StatusCode, String); + + async fn from_request_parts(_parts: &mut Parts, state: &S) -> Result { + let pool = Pool::from_ref(state); + + let conn = pool.get_owned().await.map_err(internal_error)?; + + Ok(Self(conn)) + } +} + +async fn list_users( + DatabaseConnection(mut conn): DatabaseConnection, +) -> Result>, (StatusCode, String)> { + let res = users::table + .select(User::as_select()) + .load(&mut conn) + .await + .map_err(internal_error)?; + Ok(Json(res)) +} + +/// Utility function for mapping any error into a `500 Internal Server Error` +/// response. +fn internal_error(err: E) -> (StatusCode, String) +where + E: std::error::Error, +{ + (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) +} diff --git a/examples/diesel-postgres/Cargo.toml b/examples/diesel-postgres/Cargo.toml new file mode 100644 index 00000000..9c7a6e2d --- /dev/null +++ b/examples/diesel-postgres/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "example-diesel-postgres" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +axum = { path = "../../axum" } +axum-macros = { path = "../../axum-macros" } +deadpool-diesel = { version = "0.4.1", features = ["postgres"] } +diesel = { version = "2", features = ["postgres"] } +diesel_migrations = "2" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1" +tokio = { version = "1.0", features = ["full"] } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/examples/diesel-postgres/migrations/2023-03-14-180127_add_users/down.sql b/examples/diesel-postgres/migrations/2023-03-14-180127_add_users/down.sql new file mode 100644 index 00000000..ca53fc11 --- /dev/null +++ b/examples/diesel-postgres/migrations/2023-03-14-180127_add_users/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in "up.sql" +DROP TABLE "users"; diff --git a/examples/diesel-postgres/migrations/2023-03-14-180127_add_users/up.sql b/examples/diesel-postgres/migrations/2023-03-14-180127_add_users/up.sql new file mode 100644 index 00000000..6aaa1a31 --- /dev/null +++ b/examples/diesel-postgres/migrations/2023-03-14-180127_add_users/up.sql @@ -0,0 +1,6 @@ +-- Your SQL goes here +CREATE TABLE "users"( + "id" SERIAL PRIMARY KEY, + "name" TEXT NOT NULL, + "hair_color" TEXT +); diff --git a/examples/diesel-postgres/src/main.rs b/examples/diesel-postgres/src/main.rs new file mode 100644 index 00000000..968b1dce --- /dev/null +++ b/examples/diesel-postgres/src/main.rs @@ -0,0 +1,131 @@ +//! Run with +//! +//! ```not_rust +//! cargo run -p example-diesel-postgres +//! ``` +//! +//! Checkout the [diesel webpage](https://diesel.rs) for +//! longer guides about diesel +//! +//! Checkout the [crates.io source code](https://github.com/rust-lang/crates.io/) +//! for a real world application using axum and diesel + +use axum::{ + extract::State, + http::StatusCode, + response::Json, + routing::{get, post}, + Router, +}; +use diesel::prelude::*; +use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; +use std::net::SocketAddr; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; + +// this embeddes the migrations into the application binary +// the migration path is releative to the `CARGO_MANIFEST_DIR` +pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/"); + +// normally part of your generated schema.rs file +table! { + users (id) { + id -> Integer, + name -> Text, + hair_color -> Nullable, + } +} + +#[derive(serde::Serialize, Selectable, Queryable)] +struct User { + id: i32, + name: String, + hair_color: Option, +} + +#[derive(serde::Deserialize, Insertable)] +#[diesel(table_name = users)] +struct NewUser { + name: String, + hair_color: Option, +} + +#[tokio::main] +async fn main() { + tracing_subscriber::registry() + .with( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| "example_tokio_postgres=debug".into()), + ) + .with(tracing_subscriber::fmt::layer()) + .init(); + + let db_url = std::env::var("DATABASE_URL").unwrap(); + + // set up connection pool + let manager = deadpool_diesel::postgres::Manager::new(db_url, deadpool_diesel::Runtime::Tokio1); + let pool = deadpool_diesel::postgres::Pool::builder(manager) + .build() + .unwrap(); + + // run the migrations on server startup + { + let conn = pool.get().await.unwrap(); + conn.interact(|conn| conn.run_pending_migrations(MIGRATIONS).map(|_| ())) + .await + .unwrap() + .unwrap(); + } + + // build our application with some routes + let app = Router::new() + .route("/user/list", get(list_users)) + .route("/user/create", post(create_user)) + .with_state(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(); +} + +async fn create_user( + State(pool): State, + Json(new_user): Json, +) -> Result, (StatusCode, String)> { + let conn = pool.get().await.map_err(internal_error)?; + let res = conn + .interact(|conn| { + diesel::insert_into(users::table) + .values(new_user) + .returning(User::as_returning()) + .get_result(conn) + }) + .await + .map_err(internal_error)? + .map_err(internal_error)?; + Ok(Json(res)) +} + +async fn list_users( + State(pool): State, +) -> Result>, (StatusCode, String)> { + let conn = pool.get().await.map_err(internal_error)?; + let res = conn + .interact(|conn| users::table.select(User::as_select()).load(conn)) + .await + .map_err(internal_error)? + .map_err(internal_error)?; + Ok(Json(res)) +} + +/// Utility function for mapping any error into a `500 Internal Server Error` +/// response. +fn internal_error(err: E) -> (StatusCode, String) +where + E: std::error::Error, +{ + (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) +}