1
0
mirror of https://github.com/actix/examples synced 2024-12-18 00:13:57 +01:00

Merge pull request #235 from Dowwie/master

added async_pg example
This commit is contained in:
Darin 2020-01-15 19:20:05 -05:00 committed by GitHub
commit a6475ad63e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 185 additions and 0 deletions

View File

@ -3,6 +3,7 @@ members = [
"async_db",
"async_ex1",
"async_ex2",
"async_pg",
"basics",
"cookie-auth",
"cookie-session",

2
async_pg/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
**/*.rs.bk

16
async_pg/Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
name = "async_pg"
version = "0.1.0"
authors = ["dowwie <dkcdkg@gmail.com>"]
edition = "2018"
[dependencies]
actix-rt = "1.0.0"
actix-web = "2.0.0"
deadpool-postgres = "0.4.3"
derive_more = "0.99.2"
serde = { version = "1.0.104", features=["derive"] }
tokio-pg-mapper = "0.1.4"
tokio-pg-mapper-derive = "0.1.4"
tokio-postgres = "0.5.1"

12
async_pg/README.md Normal file
View File

@ -0,0 +1,12 @@
This example illustrates:
- tokio_postgres
- use of tokio_pg_mapper for postgres data mapping
- deadpool_postgres for connection pooling
# Instructions
1. Set up the testing database by running /sql/create_db.sh
2. `cargo run`
3. from the command line (linux), POST a user to the endpoint:
echo '{"email": "ferris@thecrab.com", "first_name": "ferris", "last_name": "crab", "username": "ferreal"}' | http -f --json --print h POST http://127.0.0.1:8080/users
- a unique constraint exists for username, so running this twice will return a 500

View File

@ -0,0 +1,3 @@
INSERT INTO testing.users(email, first_name, last_name, username)
VALUES ($1, $2, $3, $4)
RETURNING $table_fields;

3
async_pg/sql/create_db.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
psql -U postgres -h 127.0.0.1 -f setup_db_and_user.sql

View File

@ -0,0 +1 @@
SELECT $table_fields FROM testing.users;

View File

@ -0,0 +1,20 @@
DROP DATABASE IF EXISTS testing_db;
CREATE USER test_user WITH PASSWORD 'testing';
CREATE DATABASE testing_db OWNER test_user;
\connect testing_db;
DROP SCHEMA IF EXISTS testing CASCADE;
CREATE SCHEMA testing;
CREATE TABLE testing.users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(200) NOT NULL,
first_name VARCHAR(200) NOT NULL,
last_name VARCHAR(200) NOT NULL,
username VARCHAR(50) UNIQUE NOT NULL,
UNIQUE (username)
);

127
async_pg/src/main.rs Normal file
View File

@ -0,0 +1,127 @@
mod models {
use serde::{Deserialize, Serialize};
use tokio_pg_mapper_derive::PostgresMapper;
#[derive(Deserialize, PostgresMapper, Serialize)]
#[pg_mapper(table = "users")] // singular 'user' is a keyword..
pub struct User {
pub email: String,
pub first_name: String,
pub last_name: String,
pub username: String
}
}
mod errors {
use actix_web::{HttpResponse, ResponseError};
use derive_more::{Display, From};
use deadpool_postgres::PoolError;
use tokio_postgres::error::Error as PGError;
use tokio_pg_mapper::Error as PGMError;
#[derive(Display, From, Debug)]
pub enum MyError {
NotFound,
PGError(PGError),
PGMError(PGMError),
PoolError(PoolError)
}
impl std::error::Error for MyError {}
impl ResponseError for MyError {
fn error_response(&self) -> HttpResponse {
match *self {
MyError::NotFound => HttpResponse::NotFound().finish(),
_ => HttpResponse::InternalServerError().finish()
}
}
}
}
mod db {
use crate::{errors::MyError, models::User};
use deadpool_postgres::Client;
use tokio_pg_mapper::FromTokioPostgresRow;
pub async fn add_user(client: &Client, user_info: User) -> Result<User, MyError> {
let _stmt = include_str!("../sql/add_user.sql");
let _stmt = _stmt.replace("$table_fields", &User::sql_table_fields());
let stmt = client.prepare(&_stmt)
.await
.unwrap();
client.query(&stmt,
&[&user_info.email,
&user_info.first_name,
&user_info.last_name,
&user_info.username
])
.await?
.iter()
.map(|row| User::from_row_ref(row).unwrap())
.collect::<Vec<User>>()
.pop()
.ok_or(MyError::NotFound) // more applicable for SELECTs
}
}
mod handlers {
use actix_web::{HttpResponse, web, Error};
use deadpool_postgres::{Client, Pool};
use crate::{db, errors::MyError, models::User};
pub async fn add_user(user: web::Json<User>, db_pool: web::Data<Pool>)
-> Result<HttpResponse, Error> {
let user_info: User = user.into_inner();
let client: Client =
db_pool.get()
.await
.map_err(|err| MyError::PoolError(err))?;
let new_user = db::add_user(&client, user_info).await?;
Ok(HttpResponse::Ok().json(new_user))
}
}
use actix_web::{App, HttpServer, web};
use deadpool_postgres::{Pool, Manager};
use handlers::add_user;
use tokio_postgres::{Config, NoTls};
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
const SERVER_ADDR: &str = "127.0.0.1:8080";
let pg_config = "postgres://test_user:testing@127.0.0.1:5432/testing_db"
.parse::<Config>()
.unwrap();
let pool = Pool::new(
Manager::new(pg_config, NoTls),
16 // # of connections in pool
);
let server = HttpServer::new(move ||
App::new()
.data(pool.clone())
.service(web::resource("/users").route(web::post().to(add_user)))
)
.bind(SERVER_ADDR)?
.run();
println!("Server running at http://{}/", SERVER_ADDR);
server.await
}