Skip to content

Commit f973764

Browse files
committed
refactor: split off db with migration
The entity module requires common, common requires migration due to the DB setup functions. However, if we want to use the entities in the migration as well, then that would lead to a circular dependency situation. Moving the setup methods into a special "db" modules solves this issue.
1 parent 207f7a7 commit f973764

File tree

20 files changed

+151
-97
lines changed

20 files changed

+151
-97
lines changed

Cargo.lock

Lines changed: 20 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ resolver = "3"
33
members = [
44
"common",
55
"common/auth",
6+
"common/db",
67
"common/infrastructure",
78
"cvss",
89
"entity",
@@ -154,6 +155,7 @@ zip = "4"
154155
trustify-auth = { path = "common/auth", features = ["actix", "swagger"] }
155156
trustify-common = { path = "common" }
156157
trustify-cvss = { path = "cvss" }
158+
trustify-db = { path = "common/db" }
157159
trustify-entity = { path = "entity" }
158160
trustify-infrastructure = { path = "common/infrastructure" }
159161
trustify-migration = { path = "migration" }

common/Cargo.toml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ publish.workspace = true
66
license.workspace = true
77

88
[dependencies]
9-
trustify-migration = { workspace = true }
10-
119
actix-web = { workspace = true }
1210
anyhow = { workspace = true }
1311
bytes = { workspace = true }
@@ -25,13 +23,13 @@ log = { workspace = true }
2523
native-tls = { workspace = true }
2624
packageurl = { workspace = true }
2725
pem = { workspace = true }
28-
postgresql_embedded = { workspace = true, features = ["blocking", "tokio"] }
2926
regex = { workspace = true }
3027
reqwest = { workspace = true, features = ["native-tls"] }
3128
ring = { workspace = true }
3229
sbom-walker = { workspace = true }
3330
schemars = { workspace = true }
3431
sea-orm = { workspace = true, features = ["sea-query-binder", "sqlx-postgres", "runtime-tokio-rustls", "macros"] }
32+
sea-orm-migration = { workspace = true }
3533
sea-query = { workspace = true }
3634
serde = { workspace = true, features = ["derive"] }
3735
serde_json = { workspace = true }
@@ -46,15 +44,14 @@ tracing = { workspace = true }
4644
urlencoding = { workspace = true }
4745
utoipa = { workspace = true, features = ["url"] }
4846
uuid = { workspace = true, features = ["v5", "serde"] }
49-
walker-common = { workspace = true, features = ["bzip2", "liblzma", "flate2"]}
47+
walker-common = { workspace = true, features = ["bzip2", "liblzma", "flate2"] }
5048
humantime = { workspace = true }
5149

5250
[dev-dependencies]
5351
chrono = { workspace = true }
5452
rand = { workspace = true }
5553
rstest = { workspace = true }
5654
serde_json = { workspace = true }
57-
test-context = { workspace = true }
5855
test-log = { workspace = true, features = ["log", "trace"] }
5956
time = { workspace = true, features = ["macros"] }
6057
tokio = { workspace = true, features = ["full"] }

common/db/Cargo.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
name = "trustify-db"
3+
version.workspace = true
4+
edition.workspace = true
5+
publish.workspace = true
6+
license.workspace = true
7+
8+
[dependencies]
9+
trustify-migration = { workspace = true }
10+
trustify-common = { workspace = true }
11+
12+
anyhow = { workspace = true }
13+
log = { workspace = true }
14+
postgresql_embedded = { workspace = true, features = ["blocking", "tokio"] }
15+
sea-orm = { workspace = true }
16+
sea-orm-migration = { workspace = true }
17+
tracing = { workspace = true }

common/src/db/embedded.rs renamed to common/db/src/embedded.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
use crate::db::Database;
21
use anyhow::Context;
32
use postgresql_embedded::{PostgreSQL, Settings, VersionReq};
43
use std::path::Path;
54
use tracing::{Instrument, info_span};
5+
use trustify_common::db::Database;
66

77
/// Create common default settings for the embedded database
88
fn default_settings() -> anyhow::Result<Settings> {
@@ -62,7 +62,7 @@ async fn create_for(settings: Settings) -> anyhow::Result<(Database, PostgreSQL)
6262
port: postgresql.settings().port,
6363
..crate::config::Database::from_env()?
6464
};
65-
let db = Database::bootstrap(&config)
65+
let db = super::Database::bootstrap(&config)
6666
.await
6767
.context("Bootstrapping the test database")?;
6868

common/db/src/lib.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
pub mod embedded;
2+
3+
use anyhow::ensure;
4+
use migration::Migrator;
5+
use sea_orm::{ConnectionTrait, Statement};
6+
use sea_orm_migration::prelude::MigratorTrait;
7+
use tracing::instrument;
8+
use trustify_common::{config, db};
9+
10+
pub struct Database<'a>(pub &'a db::Database);
11+
12+
impl<'a> Database<'a> {
13+
#[instrument(skip(self), err)]
14+
pub async fn migrate(&self) -> Result<(), anyhow::Error> {
15+
log::debug!("applying migrations");
16+
Migrator::up(self.0, None).await?;
17+
log::debug!("applied migrations");
18+
19+
Ok(())
20+
}
21+
22+
#[instrument(skip(self), err)]
23+
pub async fn refresh(&self) -> Result<(), anyhow::Error> {
24+
log::warn!("refreshing database schema...");
25+
Migrator::refresh(self.0).await?;
26+
log::warn!("refreshing database schema... done!");
27+
28+
Ok(())
29+
}
30+
31+
#[instrument(err)]
32+
pub async fn bootstrap(database: &config::Database) -> Result<db::Database, anyhow::Error> {
33+
ensure!(
34+
database.url.is_none(),
35+
"Unable to bootstrap database with '--db-url'"
36+
);
37+
38+
let url = crate::config::Database {
39+
name: "postgres".into(),
40+
..database.clone()
41+
}
42+
.to_url();
43+
44+
log::debug!("bootstrap to {}", url);
45+
let db = sea_orm::Database::connect(url).await?;
46+
47+
db.execute(Statement::from_string(
48+
db.get_database_backend(),
49+
format!("DROP DATABASE IF EXISTS \"{}\";", database.name),
50+
))
51+
.await?;
52+
53+
db.execute(Statement::from_string(
54+
db.get_database_backend(),
55+
format!("CREATE DATABASE \"{}\";", database.name),
56+
))
57+
.await?;
58+
db.close().await?;
59+
60+
let db = db::Database::new(database).await?;
61+
db.execute_unprepared("CREATE EXTENSION IF NOT EXISTS \"pg_stat_statements\";")
62+
.await?;
63+
Database(&db).migrate().await?;
64+
65+
Ok(db)
66+
}
67+
}

common/src/db/func.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use migration::Iden;
1+
use sea_orm::Iden;
22
use sea_orm::{ConnectionTrait, DbErr, ExecResult};
33
use sea_query::{Func, SelectStatement};
44
use std::fmt::Write;

common/src/db/mod.rs

Lines changed: 23 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
1-
mod func;
2-
31
pub mod chunk;
4-
pub mod embedded;
52
pub mod limiter;
63
pub mod multi_model;
74
pub mod query;
85

6+
mod func;
97
pub use func::*;
108

11-
use anyhow::{Context, ensure};
12-
use migration::{Migrator, MigratorTrait};
9+
use anyhow::Context;
1310
use reqwest::Url;
1411
use sea_orm::{
1512
AccessMode, ConnectOptions, ConnectionTrait, DatabaseConnection, DatabaseTransaction,
1613
DbBackend, DbErr, ExecResult, IsolationLevel, QueryResult, RuntimeErr, Statement, StreamTrait,
1714
TransactionError, TransactionTrait, prelude::async_trait,
1815
};
16+
use sea_orm_migration::{IntoSchemaManagerConnection, SchemaManagerConnection};
1917
use sqlx::error::ErrorKind;
2018
use std::{
2119
ops::{Deref, DerefMut},
@@ -24,6 +22,23 @@ use std::{
2422
};
2523
use tracing::instrument;
2624

25+
/// A trait to help working with database errors
26+
pub trait DatabaseErrors {
27+
/// return `true` if the error is a duplicate key error
28+
fn is_duplicate(&self) -> bool;
29+
}
30+
31+
impl DatabaseErrors for DbErr {
32+
fn is_duplicate(&self) -> bool {
33+
match self {
34+
DbErr::Query(RuntimeErr::SqlxError(sqlx::error::Error::Database(err))) => {
35+
err.kind() == ErrorKind::UniqueViolation
36+
}
37+
_ => false,
38+
}
39+
}
40+
}
41+
2742
#[derive(Clone, Debug)]
2843
pub struct Database {
2944
/// the database connection
@@ -57,61 +72,6 @@ impl Database {
5772
Ok(Self { db, name })
5873
}
5974

60-
#[instrument(skip(self), err)]
61-
pub async fn migrate(&self) -> Result<(), anyhow::Error> {
62-
log::debug!("applying migrations");
63-
Migrator::up(&self.db, None).await?;
64-
log::debug!("applied migrations");
65-
66-
Ok(())
67-
}
68-
69-
#[instrument(skip(self), err)]
70-
pub async fn refresh(&self) -> Result<(), anyhow::Error> {
71-
log::warn!("refreshing database schema...");
72-
Migrator::refresh(&self.db).await?;
73-
log::warn!("refreshing database schema... done!");
74-
75-
Ok(())
76-
}
77-
78-
#[instrument(err)]
79-
pub async fn bootstrap(database: &crate::config::Database) -> Result<Self, anyhow::Error> {
80-
ensure!(
81-
database.url.is_none(),
82-
"Unable to bootstrap database with '--db-url'"
83-
);
84-
85-
let url = crate::config::Database {
86-
name: "postgres".into(),
87-
..database.clone()
88-
}
89-
.to_url();
90-
91-
log::debug!("bootstrap to {}", url);
92-
let db = sea_orm::Database::connect(url).await?;
93-
94-
db.execute(Statement::from_string(
95-
db.get_database_backend(),
96-
format!("DROP DATABASE IF EXISTS \"{}\";", database.name),
97-
))
98-
.await?;
99-
100-
db.execute(Statement::from_string(
101-
db.get_database_backend(),
102-
format!("CREATE DATABASE \"{}\";", database.name),
103-
))
104-
.await?;
105-
db.close().await?;
106-
107-
let db = Self::new(database).await?;
108-
db.execute_unprepared("CREATE EXTENSION IF NOT EXISTS \"pg_stat_statements\";")
109-
.await?;
110-
db.migrate().await?;
111-
112-
Ok(db)
113-
}
114-
11575
#[instrument(skip(self), err)]
11676
pub async fn close(self) -> anyhow::Result<()> {
11777
Ok(self.db.close().await?)
@@ -286,20 +246,9 @@ impl<'b> StreamTrait for &'b Database {
286246
}
287247
}
288248

289-
/// A trait to help working with database errors
290-
pub trait DatabaseErrors {
291-
/// return `true` if the error is a duplicate key error
292-
fn is_duplicate(&self) -> bool;
293-
}
294-
295-
impl DatabaseErrors for DbErr {
296-
fn is_duplicate(&self) -> bool {
297-
match self {
298-
DbErr::Query(RuntimeErr::SqlxError(sqlx::error::Error::Database(err))) => {
299-
err.kind() == ErrorKind::UniqueViolation
300-
}
301-
_ => false,
302-
}
249+
impl<'a> IntoSchemaManagerConnection<'a> for &'a Database {
250+
fn into_schema_manager_connection(self) -> SchemaManagerConnection<'a> {
251+
self.db.into_schema_manager_connection()
303252
}
304253
}
305254

0 commit comments

Comments
 (0)