1
0
mirror of https://github.com/actix/examples synced 2024-11-23 14:31: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",
"autoreload",
"chrono",
"deadpool",
"dotenv",
"dotenvy",
"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/postgres",
"databases/redis",
# "databases/sqlite",
"docker",
"forms/form",
"forms/multipart-s3",
@ -69,6 +68,9 @@ members = [
"websockets/echo-actorless",
"websockets/echo",
]
exclude = [
"databases/sqlite",
]
[workspace.package]
publish = false

View File

@ -1,11 +1,11 @@
[package]
name = "async_pg"
name = "db-postgres"
version = "1.0.0"
edition = "2021"
[dependencies]
actix-web.workspace = true
config = "0.13"
confik = "0.11"
deadpool-postgres = { version = "0.12", features = ["serde"] }
derive_more.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,147 +1,48 @@
mod config {
use serde::Deserialize;
#[derive(Debug, Default, Deserialize)]
pub struct ExampleConfig {
pub server_addr: String,
pub pg: deadpool_postgres::Config,
}
}
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 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 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 users = db::get_users(&client).await?;
Ok(HttpResponse::Ok().json(users))
}
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(MyError::PoolError)?;
let new_user = db::add_user(&client, user_info).await?;
Ok(HttpResponse::Ok().json(new_user))
}
}
use ::config::Config;
use actix_web::{web, App, HttpServer};
use actix_web::{web, App, Error, HttpResponse, HttpServer};
use confik::{Configuration as _, EnvSource};
use deadpool_postgres::{Client, Pool};
use dotenvy::dotenv;
use handlers::{add_user, get_users};
use tokio_postgres::NoTls;
use crate::config::ExampleConfig;
mod config;
mod db;
mod errors;
mod models;
use self::{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 users = db::get_users(&client).await?;
Ok(HttpResponse::Ok().json(users))
}
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(MyError::PoolError)?;
let new_user = db::add_user(&client, user_info).await?;
Ok(HttpResponse::Ok().json(new_user))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
dotenv().ok();
let config_ = Config::builder()
.add_source(::config::Environment::default())
.build()
let config = ExampleConfig::builder()
.override_with(EnvSource::new())
.try_build()
.unwrap();
let config: ExampleConfig = config_.try_deserialize().unwrap();
let pool = config.pg.create_pool(None, NoTls).unwrap();
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
log.workspace = true
redis = { version = "0.24", default-features = false, features = [
"tokio-comp",
"connection-manager",
] }
redis = { version = "0.25", features = ["tokio-comp", "connection-manager"] }
serde.workspace = true

View File

@ -1,5 +1,5 @@
# [SeaORM](https://github.com/SeaQL/sea-orm)
# [SeaORM](https://github.com/SeaQL/sea-orm)
You can find the SeaORM with Actix 4 example in the [sea-orm/examples/actix_example/](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example)
And with the Actix 3 here: [sea-orm/examples/actix3_example/](https://github.com/SeaQL/sea-orm/tree/master/examples/actix3_example)
You can find the SeaORM with Actix 4 example in the [sea-orm/examples/actix_example/](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example)
And with the Actix 3 here: [sea-orm/examples/actix3_example/](https://github.com/SeaQL/sea-orm/tree/master/examples/actix3_example)

View File

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

View File

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

View File

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