1
0
mirror of https://github.com/actix/examples synced 2024-11-23 22:41:07 +01:00

refactor: use confik instead of config

This commit is contained in:
Rob Ede 2024-07-07 02:01:13 +01:00
parent 03879899a5
commit 0bdd5479ff
No known key found for this signature in database
GPG Key ID: 97C636207D3EF933
14 changed files with 322 additions and 477 deletions

View File

@ -6,6 +6,7 @@
"autoclean", "autoclean",
"autoreload", "autoreload",
"chrono", "chrono",
"deadpool",
"dotenv", "dotenv",
"dotenvy", "dotenvy",
"graphiql", "graphiql",

484
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,6 @@ members = [
"databases/mysql", "databases/mysql",
"databases/postgres", "databases/postgres",
"databases/redis", "databases/redis",
# "databases/sqlite",
"docker", "docker",
"forms/form", "forms/form",
"forms/multipart-s3", "forms/multipart-s3",
@ -69,6 +68,9 @@ members = [
"websockets/echo-actorless", "websockets/echo-actorless",
"websockets/echo", "websockets/echo",
] ]
exclude = [
"databases/sqlite",
]
[workspace.package] [workspace.package]
publish = false publish = false

View File

@ -1,11 +1,11 @@
[package] [package]
name = "async_pg" name = "db-postgres"
version = "1.0.0" version = "1.0.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
actix-web.workspace = true actix-web.workspace = true
config = "0.13" confik = "0.11"
deadpool-postgres = { version = "0.12", features = ["serde"] } deadpool-postgres = { version = "0.12", features = ["serde"] }
derive_more.workspace = true derive_more.workspace = true
dotenvy.workspace = true dotenvy.workspace = true

View File

@ -0,0 +1,23 @@
use confik::Configuration;
use serde::Deserialize;
#[derive(Debug, Default, Configuration)]
pub struct ExampleConfig {
pub server_addr: String,
#[confik(from = DbConfig)]
pub pg: deadpool_postgres::Config,
}
#[derive(Debug, Deserialize)]
#[serde(transparent)]
struct DbConfig(deadpool_postgres::Config);
impl From<DbConfig> for deadpool_postgres::Config {
fn from(value: DbConfig) -> Self {
value.0
}
}
impl confik::Configuration for DbConfig {
type Builder = Option<Self>;
}

View File

@ -0,0 +1,42 @@
use deadpool_postgres::Client;
use tokio_pg_mapper::FromTokioPostgresRow;
use crate::{errors::MyError, models::User};
pub async fn get_users(client: &Client) -> Result<Vec<User>, MyError> {
let stmt = include_str!("../sql/get_users.sql");
let stmt = stmt.replace("$table_fields", &User::sql_table_fields());
let stmt = client.prepare(&stmt).await.unwrap();
let results = client
.query(&stmt, &[])
.await?
.iter()
.map(|row| User::from_row_ref(row).unwrap())
.collect::<Vec<User>>();
Ok(results)
}
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
}

View File

@ -0,0 +1,25 @@
use actix_web::{HttpResponse, ResponseError};
use deadpool_postgres::PoolError;
use derive_more::{Display, Error, From};
use tokio_pg_mapper::Error as PGMError;
use tokio_postgres::error::Error as PGError;
#[derive(Debug, Display, Error, From)]
pub enum MyError {
NotFound,
PGError(PGError),
PGMError(PGMError),
PoolError(PoolError),
}
impl ResponseError for MyError {
fn error_response(&self) -> HttpResponse {
match *self {
MyError::NotFound => HttpResponse::NotFound().finish(),
MyError::PoolError(ref err) => {
HttpResponse::InternalServerError().body(err.to_string())
}
_ => HttpResponse::InternalServerError().finish(),
}
}
}

View File

@ -1,118 +1,30 @@
mod config { use actix_web::{web, App, Error, HttpResponse, HttpServer};
use serde::Deserialize; use confik::{Configuration as _, EnvSource};
#[derive(Debug, Default, Deserialize)] use deadpool_postgres::{Client, Pool};
pub struct ExampleConfig { use dotenvy::dotenv;
pub server_addr: String, use tokio_postgres::NoTls;
pub pg: deadpool_postgres::Config,
}
}
mod models { use crate::config::ExampleConfig;
use serde::{Deserialize, Serialize};
use tokio_pg_mapper_derive::PostgresMapper;
#[derive(Deserialize, PostgresMapper, Serialize)] mod config;
#[pg_mapper(table = "users")] // singular 'user' is a keyword.. mod db;
pub struct User { mod errors;
pub email: String, mod models;
pub first_name: String,
pub last_name: String,
pub username: String,
}
}
mod errors { use self::{errors::MyError, models::User};
use actix_web::{HttpResponse, ResponseError};
use deadpool_postgres::PoolError;
use derive_more::{Display, From};
use tokio_pg_mapper::Error as PGMError;
use tokio_postgres::error::Error as PGError;
#[derive(Display, From, Debug)] pub async fn get_users(db_pool: web::Data<Pool>) -> Result<HttpResponse, Error> {
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(),
MyError::PoolError(ref err) => {
HttpResponse::InternalServerError().body(err.to_string())
}
_ => HttpResponse::InternalServerError().finish(),
}
}
}
}
mod db {
use deadpool_postgres::Client;
use tokio_pg_mapper::FromTokioPostgresRow;
use crate::{errors::MyError, models::User};
pub async fn get_users(client: &Client) -> Result<Vec<User>, MyError> {
let stmt = include_str!("../sql/get_users.sql");
let stmt = stmt.replace("$table_fields", &User::sql_table_fields());
let stmt = client.prepare(&stmt).await.unwrap();
let results = client
.query(&stmt, &[])
.await?
.iter()
.map(|row| User::from_row_ref(row).unwrap())
.collect::<Vec<User>>();
Ok(results)
}
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::{web, Error, HttpResponse};
use deadpool_postgres::{Client, Pool};
use crate::{db, errors::MyError, models::User};
pub async fn get_users(db_pool: web::Data<Pool>) -> Result<HttpResponse, Error> {
let client: Client = db_pool.get().await.map_err(MyError::PoolError)?; let client: Client = db_pool.get().await.map_err(MyError::PoolError)?;
let users = db::get_users(&client).await?; let users = db::get_users(&client).await?;
Ok(HttpResponse::Ok().json(users)) Ok(HttpResponse::Ok().json(users))
} }
pub async fn add_user( pub async fn add_user(
user: web::Json<User>, user: web::Json<User>,
db_pool: web::Data<Pool>, db_pool: web::Data<Pool>,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let user_info: User = user.into_inner(); let user_info: User = user.into_inner();
let client: Client = db_pool.get().await.map_err(MyError::PoolError)?; let client: Client = db_pool.get().await.map_err(MyError::PoolError)?;
@ -120,28 +32,17 @@ mod handlers {
let new_user = db::add_user(&client, user_info).await?; let new_user = db::add_user(&client, user_info).await?;
Ok(HttpResponse::Ok().json(new_user)) Ok(HttpResponse::Ok().json(new_user))
}
} }
use ::config::Config;
use actix_web::{web, App, HttpServer};
use dotenvy::dotenv;
use handlers::{add_user, get_users};
use tokio_postgres::NoTls;
use crate::config::ExampleConfig;
#[actix_web::main] #[actix_web::main]
async fn main() -> std::io::Result<()> { async fn main() -> std::io::Result<()> {
dotenv().ok(); dotenv().ok();
let config_ = Config::builder() let config = ExampleConfig::builder()
.add_source(::config::Environment::default()) .override_with(EnvSource::new())
.build() .try_build()
.unwrap(); .unwrap();
let config: ExampleConfig = config_.try_deserialize().unwrap();
let pool = config.pg.create_pool(None, NoTls).unwrap(); let pool = config.pg.create_pool(None, NoTls).unwrap();
let server = HttpServer::new(move || { let server = HttpServer::new(move || {

View File

@ -0,0 +1,11 @@
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,
}

View File

@ -8,8 +8,5 @@ actix-web.workspace = true
env_logger.workspace = true env_logger.workspace = true
log.workspace = true log.workspace = true
redis = { version = "0.24", default-features = false, features = [ redis = { version = "0.25", features = ["tokio-comp", "connection-manager"] }
"tokio-comp",
"connection-manager",
] }
serde.workspace = true serde.workspace = true

View File

@ -3,13 +3,13 @@ name = "db-sqlite"
version = "1.0.0" version = "1.0.0"
edition = "2021" edition = "2021"
# Do not use workspace deps as they this package isn't part of the workspace.
[dependencies] [dependencies]
actix-web.workspace = true actix-web = "4"
env_logger = "0.11"
env_logger.workspace = true futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
futures-util.workspace = true log = "0.4"
log.workspace = true
r2d2 = "0.8" r2d2 = "0.8"
r2d2_sqlite = "0.22" r2d2_sqlite = "0.24"
rusqlite = "0.29" rusqlite = "0.31"
serde.workspace = true serde = { version = "1", features = ["derive"] }

View File

@ -7,13 +7,10 @@ edition = "2021"
actix-web.workspace = true actix-web.workspace = true
actix-web-lab.workspace = true actix-web-lab.workspace = true
actix-cors.workspace = true actix-cors.workspace = true
juniper = "0.16"
juniper = "0.15.10"
mysql = "24" mysql = "24"
r2d2 = "0.8" r2d2 = "0.8"
r2d2_mysql = "24" r2d2_mysql = "24"
dotenvy.workspace = true dotenvy.workspace = true
env_logger.workspace = true env_logger.workspace = true
log.workspace = true log.workspace = true

View File

@ -7,8 +7,6 @@ edition = "2021"
actix-web.workspace = true actix-web.workspace = true
actix-web-lab.workspace = true actix-web-lab.workspace = true
actix-cors.workspace = true actix-cors.workspace = true
juniper = "0.16"
juniper = "0.15.10"
env_logger.workspace = true env_logger.workspace = true
log.workspace = true log.workspace = true