RustのWebフレームワークgothamでPostgreSQLとRedisにアクセスする(async/await対応版 gotham0.6)

# 目的
以前書いた記事のバージョンアップ版です。
[RustのWebフレームワークgothamでPostgreSQLとRedisにアクセスする](https://qiita.com/aoyagikouhei/items/c1613658a4dcbc1e12d5)

以前との違いはasync/awaitに対応して、Gothamのバージョンを0.6にしています。Tokioも1系になっています。

# コード
## docker-compose
```yaml:docker-compose.yaml
version: '3.8'
services:
  api:
    image: rust:buster
    working_dir: /apps/api
    environment:
      - CARGO_BUILD_TARGET_DIR=/tmp/target
      - CARGO_TARGET_DIR=/tmp/target
    volumes:
      - ./apps/api:/apps/api
      - rust_target:/tmp/target
      - rust_cache:/usr/local/cargo/registry
    security_opt:
      - seccomp:unconfined
    tty: true
    ports:
      - "7878:7878"
    depends_on:
      - db
      - redis

  db:
    image: postgres:13-buster
    volumes:
      - postgres:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: app

  redis:
    image: redis:6.2-buster
    command: redis-server --appendonly yes
    ports:
      - "6379:6379"
    volumes:
      - redis:/var/lib/redis/data

volumes:
  rust_cache:
  rust_target:
  postgres:
  redis:
```

## Rust
```rust:Cargo.toml
[package]
name = "api"
version = "0.1.0"
edition = "2018"

[dependencies]
anyhow = "^1"
bb8-postgres = "^0.7"
bb8-redis = "^0.10"
http = "^0.2"
gotham = "^0.6"
gotham_derive = "^0.6"
mime = "^0.3"
serde = { version = "^1", features = ["derive"] }
tokio = { version = "^1", features = ["macros"] }
```

```rust:main.rs
use gotham::{
    handler::{SimpleHandlerResult},
    helpers::http::response::create_response,
    middleware::state::StateMiddleware,
    pipeline::{single::single_pipeline, single_middleware},
    router::{builder::*, Router},
    state::{FromState, State},
};

#[macro_use]
extern crate gotham_derive;

#[derive(Clone, StateData)]
pub struct ConfigData {
    pub pg: bb8_postgres::bb8::Pool<
        bb8_postgres::PostgresConnectionManager<bb8_postgres::tokio_postgres::NoTls>,
    >,
    pub redis: bb8_redis::bb8::Pool<bb8_redis::RedisConnectionManager>,
}
impl std::panic::RefUnwindSafe for ConfigData {}
impl ConfigData {
    pub async fn new(pg_url: &str, redis_url: &str) -> anyhow::Result<Self> {
        let pg_manager = bb8_postgres::PostgresConnectionManager::new(
            pg_url.parse().unwrap(),
            bb8_postgres::tokio_postgres::NoTls,
        );
        let pg_pool = bb8_postgres::bb8::Pool::builder().build(pg_manager).await?;

        let redis_manager = bb8_redis::RedisConnectionManager::new(redis_url).unwrap();
        let redis_pool = bb8_redis::bb8::Pool::builder()
            .build(redis_manager)
            .await
            .unwrap();

        Ok(Self {
            pg: pg_pool,
            redis: redis_pool,
        })
    }
}

pub async fn say_hello(state: &mut State) -> SimpleHandlerResult {
    let content = {
        let data = ConfigData::borrow_from(&state);
        let mut redis = data.redis.get().await?;
        let pg = data.pg.get().await?;
        let redis_res: String = bb8_redis::redis::cmd("GET")
            .arg("aaa")
            .query_async(&mut *redis)
            .await
            .unwrap_or("".to_string());
        let pg_res: String = pg
            .query("SELECT $1::TEXT", &[&"予定表〜①ﾊﾝｶｸだ3".to_string()])
            .await?
            .get(0)
            .unwrap()
            .get(0);
        format!("{}:{}", redis_res, pg_res)
    };

    let response = create_response(
        &state,
        gotham::hyper::StatusCode::OK,
        mime::TEXT_PLAIN,
        content,
    );

    Ok(response)
}

async fn router(config_data: ConfigData) -> Router {
    let middleware = StateMiddleware::new(config_data);

    let pipeline = single_middleware(middleware);

    let (chain, pipelines) = single_pipeline(pipeline);

    build_router(chain, pipelines, |route| {
        route.get("/").to_async_borrowing(say_hello);
    })
}

#[tokio::main]
async fn main() {
    let pg_url = std::env::var("PG_URL").unwrap();
    let redis_url = std::env::var("REDIS_URL").unwrap();
    let config_data = ConfigData::new(&pg_url, &redis_url).await.unwrap();
    let addr = "0.0.0.0:7878";
    println!("Listening for requests at http://{}", addr);
    let _ = gotham::init_server(addr, router(config_data).await).await;
}
```

```sh
PG_URL=postgresql://user:pass@db/app REDIS_URL=redis://redis:6379 cargo run
```

# おまけ
コネクションプールはbb8, deadpool, r2d2がありますが、実は最初deadpoolで試していたんですが、redisはうまく来ましたがpostgresqlがランタイムが無いみたいなエラーで出て動きませんでした。本来ならdeadpool-postgresを使うだけでdefaultでランタイムが指定されるはずなんですが、うまくいきません。r2d2は非同期をサポートしてなかったのでbb8に行きつきました。