restructure generator
This commit is contained in:
83
Cargo.lock
generated
83
Cargo.lock
generated
@@ -108,15 +108,6 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "atomic"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994"
|
|
||||||
dependencies = [
|
|
||||||
"bytemuck",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@@ -180,12 +171,6 @@ version = "3.17.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bytemuck"
|
|
||||||
version = "1.22.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -595,19 +580,6 @@ version = "2.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "figment"
|
|
||||||
version = "0.10.19"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3"
|
|
||||||
dependencies = [
|
|
||||||
"atomic",
|
|
||||||
"serde",
|
|
||||||
"serde_yaml",
|
|
||||||
"uncased",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flume"
|
name = "flume"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
@@ -1379,6 +1351,12 @@ version = "1.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef"
|
checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathdiff"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pem-rfc7468"
|
name = "pem-rfc7468"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@@ -1675,23 +1653,22 @@ dependencies = [
|
|||||||
"color-eyre",
|
"color-eyre",
|
||||||
"comfy-table",
|
"comfy-table",
|
||||||
"comment-parser",
|
"comment-parser",
|
||||||
"figment",
|
|
||||||
"handlebars",
|
"handlebars",
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"include_dir",
|
"include_dir",
|
||||||
"indicatif",
|
"indicatif",
|
||||||
"inquire",
|
"inquire",
|
||||||
"path-clean",
|
"path-clean",
|
||||||
|
"pathdiff",
|
||||||
"quote",
|
"quote",
|
||||||
"sea-orm-codegen",
|
"sea-orm-codegen",
|
||||||
"sea-schema",
|
"sea-schema",
|
||||||
"serde",
|
"serde",
|
||||||
"serde-inline-default",
|
"serde-inline-default",
|
||||||
"serde_json",
|
|
||||||
"serde_yaml",
|
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"syn",
|
"syn",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"toml",
|
||||||
"toml_edit",
|
"toml_edit",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
@@ -1821,19 +1798,6 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_yaml"
|
|
||||||
version = "0.9.34+deprecated"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
|
||||||
dependencies = [
|
|
||||||
"indexmap",
|
|
||||||
"itoa",
|
|
||||||
"ryu",
|
|
||||||
"serde",
|
|
||||||
"unsafe-libyaml",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha1"
|
name = "sha1"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
@@ -2304,6 +2268,18 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.8.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_spanned",
|
||||||
|
"toml_datetime",
|
||||||
|
"toml_edit",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.6.8"
|
version = "0.6.8"
|
||||||
@@ -2410,15 +2386,6 @@ version = "0.1.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
|
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "uncased"
|
|
||||||
version = "0.9.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697"
|
|
||||||
dependencies = [
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.18"
|
version = "0.3.18"
|
||||||
@@ -2464,12 +2431,6 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unsafe-libyaml"
|
|
||||||
version = "0.2.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.4"
|
version = "2.5.4"
|
||||||
@@ -2787,9 +2748,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.7.4"
|
version = "0.7.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36"
|
checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -10,23 +10,22 @@ clap = { version = "4.5.32", features = ["derive", "env"] }
|
|||||||
color-eyre = "0.6.3"
|
color-eyre = "0.6.3"
|
||||||
comfy-table = { version = "7.1.4", default-features = false }
|
comfy-table = { version = "7.1.4", default-features = false }
|
||||||
comment-parser = "0.1.0"
|
comment-parser = "0.1.0"
|
||||||
figment = { version = "0.10.19", features = ["yaml"] }
|
|
||||||
handlebars = "6.3.2"
|
handlebars = "6.3.2"
|
||||||
heck = "0.5.0"
|
heck = "0.5.0"
|
||||||
include_dir = "0.7.4"
|
include_dir = "0.7.4"
|
||||||
indicatif = "0.17.11"
|
indicatif = "0.17.11"
|
||||||
inquire = "0.7.5"
|
inquire = "0.7.5"
|
||||||
path-clean = "1.0.1"
|
path-clean = "1.0.1"
|
||||||
|
pathdiff = "0.2.3"
|
||||||
quote = "1.0.40"
|
quote = "1.0.40"
|
||||||
sea-orm-codegen = "1.1.8"
|
sea-orm-codegen = "1.1.8"
|
||||||
sea-schema = { version = "0.16.1", features = ["sqlx-all"] }
|
sea-schema = { version = "0.16.1", features = ["sqlx-all"] }
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde-inline-default = "0.2.3"
|
serde-inline-default = "0.2.3"
|
||||||
serde_json = "1.0.140"
|
|
||||||
serde_yaml = "0.9.34"
|
|
||||||
sqlx = { version = "0.8.3", features = ["mysql", "postgres", "sqlite", "runtime-tokio"] }
|
sqlx = { version = "0.8.3", features = ["mysql", "postgres", "sqlite", "runtime-tokio"] }
|
||||||
syn = { version = "2.0.100", features = ["extra-traits", "full"] }
|
syn = { version = "2.0.100", features = ["extra-traits", "full"] }
|
||||||
tokio = { version = "1.44.1", features = ["full"] }
|
tokio = { version = "1.44.1", features = ["full"] }
|
||||||
|
toml = "0.8.20"
|
||||||
toml_edit = { version = "0.22.24", features = ["serde"] }
|
toml_edit = { version = "0.22.24", features = ["serde"] }
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
# This file is used to configure the SeaORM generator.
|
# This file is used to configure the SeaORM generator.
|
||||||
[modules.discovery]
|
[modules.discovery]
|
||||||
enable = true
|
enable = true
|
||||||
[modules.discovery.filter]
|
|
||||||
include_hidden = false
|
|
||||||
skip_seaql_migrations = true
|
|
||||||
|
|
||||||
[modules.sea_orm]
|
[modules.sea_orm]
|
||||||
enable = true
|
enable = true
|
||||||
path = "./tests/src/models/_entities"
|
path = "./tests/src/models/_entities"
|
||||||
|
|
||||||
|
[modules.template]
|
||||||
|
enable = true
|
||||||
|
[modules.template.tables]
|
||||||
|
|
||||||
|
[modules.model]
|
||||||
|
enable = true
|
||||||
|
prelude = true
|
||||||
|
path = "./tests/src/models"
|
||||||
|
|
||||||
|
# [modules.annotate]
|
||||||
|
# enable = true
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
processes = {
|
processes = {
|
||||||
frontend = {
|
frontend = {
|
||||||
command = ''
|
command = ''
|
||||||
RUST_LOG=debug,sqlx=warn ${pkgs.cargo-watch}/bin/cargo-watch -x 'run'
|
RUST_LOG=debug,sqlx=warn ${pkgs.cargo-watch}/bin/cargo-watch -i tests/src -x 'run'
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,154 +0,0 @@
|
|||||||
use core::time;
|
|
||||||
|
|
||||||
use color_eyre::eyre::{eyre, ContextCompat, Report, Result};
|
|
||||||
use sea_schema::sea_query::TableCreateStatement;
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
use crate::config::db::DbConfig;
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum DbType {
|
|
||||||
MySql,
|
|
||||||
Postgres,
|
|
||||||
Sqlite,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_tables(
|
|
||||||
database_url: String,
|
|
||||||
filter: Box<dyn Fn(&String) -> bool>,
|
|
||||||
database_config: &DbConfig,
|
|
||||||
) -> Result<(Vec<TableCreateStatement>, DbType)> {
|
|
||||||
let url = Url::parse(&database_url)?;
|
|
||||||
|
|
||||||
tracing::trace!(?url);
|
|
||||||
|
|
||||||
let is_sqlite = url.scheme() == "sqlite";
|
|
||||||
|
|
||||||
let database_name: &str = (if !is_sqlite {
|
|
||||||
let database_name = url
|
|
||||||
.path_segments()
|
|
||||||
.context("No database name as part of path")?
|
|
||||||
.next()
|
|
||||||
.context("No database name as part of path")?;
|
|
||||||
|
|
||||||
if database_name.is_empty() {
|
|
||||||
return Err(eyre!("Database path name is empty"));
|
|
||||||
}
|
|
||||||
Ok::<&str, Report>(database_name)
|
|
||||||
} else {
|
|
||||||
Ok(Default::default())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let (table_stmts, db_type) = match url.scheme() {
|
|
||||||
"mysql" => {
|
|
||||||
use sea_schema::mysql::discovery::SchemaDiscovery;
|
|
||||||
use sqlx::MySql;
|
|
||||||
|
|
||||||
tracing::info!("Connecting to MySQL");
|
|
||||||
let connection = sqlx_connect::<MySql>(
|
|
||||||
database_config.max_connections,
|
|
||||||
database_config.acquire_timeout,
|
|
||||||
url.as_str(),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
tracing::info!("Discovering schema");
|
|
||||||
let schema_discovery = SchemaDiscovery::new(connection, database_name);
|
|
||||||
let schema = schema_discovery.discover().await?;
|
|
||||||
let table_stmts = schema
|
|
||||||
.tables
|
|
||||||
.into_iter()
|
|
||||||
.filter(|schema| filter(&schema.info.name))
|
|
||||||
.map(|schema| schema.write())
|
|
||||||
.collect();
|
|
||||||
(table_stmts, DbType::MySql)
|
|
||||||
}
|
|
||||||
"sqlite" => {
|
|
||||||
use sea_schema::sqlite::discovery::SchemaDiscovery;
|
|
||||||
use sqlx::Sqlite;
|
|
||||||
|
|
||||||
tracing::info!("Connecting to SQLite");
|
|
||||||
let connection = sqlx_connect::<Sqlite>(
|
|
||||||
database_config.max_connections,
|
|
||||||
database_config.acquire_timeout,
|
|
||||||
url.as_str(),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
tracing::info!("Discovering schema");
|
|
||||||
let schema_discovery = SchemaDiscovery::new(connection);
|
|
||||||
let schema = schema_discovery
|
|
||||||
.discover()
|
|
||||||
.await?
|
|
||||||
.merge_indexes_into_table();
|
|
||||||
let table_stmts = schema
|
|
||||||
.tables
|
|
||||||
.into_iter()
|
|
||||||
.filter(|schema| filter(&schema.name))
|
|
||||||
.map(|schema| schema.write())
|
|
||||||
.collect();
|
|
||||||
(table_stmts, DbType::Sqlite)
|
|
||||||
}
|
|
||||||
"postgres" | "potgresql" => {
|
|
||||||
use sea_schema::postgres::discovery::SchemaDiscovery;
|
|
||||||
use sqlx::Postgres;
|
|
||||||
|
|
||||||
tracing::info!("Connecting to Postgres");
|
|
||||||
let schema = &database_config
|
|
||||||
.database_schema
|
|
||||||
.as_deref()
|
|
||||||
.unwrap_or("public");
|
|
||||||
let connection = sqlx_connect::<Postgres>(
|
|
||||||
database_config.max_connections,
|
|
||||||
database_config.acquire_timeout,
|
|
||||||
url.as_str(),
|
|
||||||
Some(schema),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
tracing::info!("Discovering schema");
|
|
||||||
let schema_discovery = SchemaDiscovery::new(connection, schema);
|
|
||||||
let schema = schema_discovery.discover().await?;
|
|
||||||
tracing::info!(?schema);
|
|
||||||
let table_stmts = schema
|
|
||||||
.tables
|
|
||||||
.into_iter()
|
|
||||||
.filter(|schema| filter(&schema.info.name))
|
|
||||||
.map(|schema| schema.write())
|
|
||||||
.collect();
|
|
||||||
(table_stmts, DbType::Postgres)
|
|
||||||
}
|
|
||||||
_ => unimplemented!("{} is not supported", url.scheme()),
|
|
||||||
};
|
|
||||||
tracing::info!("Schema discovered");
|
|
||||||
|
|
||||||
Ok((table_stmts, db_type))
|
|
||||||
}
|
|
||||||
async fn sqlx_connect<DB>(
|
|
||||||
max_connections: u32,
|
|
||||||
acquire_timeout: u64,
|
|
||||||
url: &str,
|
|
||||||
schema: Option<&str>,
|
|
||||||
) -> Result<sqlx::Pool<DB>>
|
|
||||||
where
|
|
||||||
DB: sqlx::Database,
|
|
||||||
for<'a> &'a mut <DB as sqlx::Database>::Connection: sqlx::Executor<'a>,
|
|
||||||
{
|
|
||||||
let mut pool_options = sqlx::pool::PoolOptions::<DB>::new()
|
|
||||||
.max_connections(max_connections)
|
|
||||||
.acquire_timeout(time::Duration::from_secs(acquire_timeout));
|
|
||||||
// Set search_path for Postgres, E.g. Some("public") by default
|
|
||||||
// MySQL & SQLite connection initialize with schema `None`
|
|
||||||
if let Some(schema) = schema {
|
|
||||||
let sql = format!("SET search_path = '{schema}'");
|
|
||||||
pool_options = pool_options.after_connect(move |conn, _| {
|
|
||||||
let sql = sql.clone();
|
|
||||||
Box::pin(async move {
|
|
||||||
sqlx::Executor::execute(conn, sql.as_str())
|
|
||||||
.await
|
|
||||||
.map(|_| ())
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
pool_options.connect(url).await.map_err(Into::into)
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use path_clean::PathClean;
|
use path_clean::PathClean;
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
use tokio::{fs::File, io::AsyncWriteExt};
|
||||||
|
|
||||||
pub fn pathbuf_to_rust_path(path: PathBuf) -> String {
|
pub fn pathbuf_to_rust_path(path: PathBuf) -> String {
|
||||||
let clean_path = path.clean();
|
let clean_path = path.clean();
|
||||||
@@ -29,6 +30,7 @@ pub fn pathbuf_to_rust_path(path: PathBuf) -> String {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum InsertPoint {
|
pub enum InsertPoint {
|
||||||
Start,
|
Start,
|
||||||
|
Replace(String),
|
||||||
End,
|
End,
|
||||||
}
|
}
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -47,20 +49,44 @@ impl FileManager {
|
|||||||
files: HashMap::new(),
|
files: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn insert_file(
|
pub fn insert(
|
||||||
&mut self,
|
&mut self,
|
||||||
file: PathBuf,
|
file: &PathBuf,
|
||||||
content: String,
|
content: &str,
|
||||||
insert_point: Option<InsertPoint>,
|
insert_point: Option<InsertPoint>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if let Some(file) = self.files.get_mut(&file) {
|
if let Some(file) = self.files.get_mut(file) {
|
||||||
match insert_point {
|
match insert_point {
|
||||||
Some(InsertPoint::Start) => file.content.insert_str(0, &content),
|
Some(InsertPoint::Start) => file.content.insert_str(0, content),
|
||||||
Some(InsertPoint::End) => file.content.push_str(&content),
|
Some(InsertPoint::End) => file.content.push_str(content),
|
||||||
None => file.content.push_str(&content),
|
None => file.content.push_str(content),
|
||||||
|
Some(InsertPoint::Replace(replace)) => {
|
||||||
|
let content = file.content.replace(&replace, content);
|
||||||
|
file.content = content;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.files.insert(file.clone(), FileContent { content });
|
self.files.insert(
|
||||||
|
file.clone(),
|
||||||
|
FileContent {
|
||||||
|
content: content.to_string(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn get(&self, file: &PathBuf) -> Option<&FileContent> {
|
||||||
|
self.files.get(file)
|
||||||
|
}
|
||||||
|
pub async fn write_files(&self) -> Result<()> {
|
||||||
|
for (file, content) in &self.files {
|
||||||
|
tracing::debug!(?file, "Writing file");
|
||||||
|
let parent = file.parent().unwrap();
|
||||||
|
if !parent.exists() {
|
||||||
|
tokio::fs::create_dir_all(parent).await?;
|
||||||
|
}
|
||||||
|
let mut opened_file = File::create(file).await?;
|
||||||
|
opened_file.write_all(content.content.as_bytes()).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -68,7 +94,7 @@ impl FileManager {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::generator::file::pathbuf_to_rust_path;
|
use crate::generator::file::{pathbuf_to_rust_path, FileManager, InsertPoint};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pathbuf_to_rust_path() {
|
fn test_pathbuf_to_rust_path() {
|
||||||
@@ -90,4 +116,49 @@ mod test {
|
|||||||
let rust_path = pathbuf_to_rust_path(path);
|
let rust_path = pathbuf_to_rust_path(path);
|
||||||
assert_eq!(rust_path, "");
|
assert_eq!(rust_path, "");
|
||||||
}
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_fildmanager_insert() {
|
||||||
|
let mut file_manager = FileManager::new();
|
||||||
|
let file_path = PathBuf::from("test.rs");
|
||||||
|
file_manager.insert(&file_path, "test", None).unwrap();
|
||||||
|
file_manager.insert(&file_path, "test1", None).unwrap();
|
||||||
|
assert_eq!(file_manager.get(&file_path).unwrap().content, "testtest1");
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_fildmanager_insert_start() {
|
||||||
|
let mut file_manager = FileManager::new();
|
||||||
|
let file_path = PathBuf::from("test.rs");
|
||||||
|
file_manager.insert(&file_path, "test", None).unwrap();
|
||||||
|
file_manager
|
||||||
|
.insert(&file_path, "teststart", Some(InsertPoint::Start))
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
file_manager.get(&file_path).unwrap().content,
|
||||||
|
"teststarttest"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_fildmanager_insert_end() {
|
||||||
|
let mut file_manager = FileManager::new();
|
||||||
|
let file_path = PathBuf::from("test.rs");
|
||||||
|
file_manager.insert(&file_path, "test", None).unwrap();
|
||||||
|
file_manager
|
||||||
|
.insert(&file_path, "testend", Some(InsertPoint::End))
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(file_manager.get(&file_path).unwrap().content, "testtestend");
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_fildmanager_insert_replace() {
|
||||||
|
let mut file_manager = FileManager::new();
|
||||||
|
let file_path = PathBuf::from("test.rs");
|
||||||
|
file_manager.insert(&file_path, "test", None).unwrap();
|
||||||
|
file_manager
|
||||||
|
.insert(
|
||||||
|
&file_path,
|
||||||
|
"testreplace",
|
||||||
|
Some(InsertPoint::Replace("test".to_string())),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(file_manager.get(&file_path).unwrap().content, "testreplace");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
pub mod discover;
|
|
||||||
pub mod file;
|
pub mod file;
|
||||||
pub mod modules;
|
pub mod modules;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
@@ -15,6 +14,11 @@ pub async fn generate(database_url: &str, root_config: DocumentMut) -> Result<()
|
|||||||
.insert(DatabaseUrl(database_url.to_owned()));
|
.insert(DatabaseUrl(database_url.to_owned()));
|
||||||
module_manager.validate().await?;
|
module_manager.validate().await?;
|
||||||
module_manager.execute().await?;
|
module_manager.execute().await?;
|
||||||
|
module_manager
|
||||||
|
.get_context_mut()
|
||||||
|
.get_file_manager()
|
||||||
|
.write_files()
|
||||||
|
.await?;
|
||||||
|
|
||||||
// let db_filter = config.sea_orm.entity.tables.get_filter();
|
// let db_filter = config.sea_orm.entity.tables.get_filter();
|
||||||
// let (table_stmts, db_type) =
|
// let (table_stmts, db_type) =
|
||||||
|
|||||||
43
src/generator/modules/annotate/mod.rs
Normal file
43
src/generator/modules/annotate/mod.rs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
use super::{models::ModelsConfig, Module, ModulesContext};
|
||||||
|
use color_eyre::Result;
|
||||||
|
use serde::Deserialize;
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct AnnotateConfig {
|
||||||
|
pub enable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AnnotateConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { enable: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct AnnotateModule {
|
||||||
|
pub models: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Module for AnnotateModule {
|
||||||
|
fn init(&mut self, ctx: &mut ModulesContext) -> Result<()> {
|
||||||
|
ctx.get_config_auto::<AnnotateConfig>("modules.annotate")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn validate(&mut self, ctx: &mut ModulesContext) -> Result<bool> {
|
||||||
|
let map = ctx.get_anymap();
|
||||||
|
|
||||||
|
if let (Some(config), Some(models_config)) =
|
||||||
|
(map.get::<AnnotateConfig>(), map.get::<ModelsConfig>())
|
||||||
|
{
|
||||||
|
Ok(config.enable)
|
||||||
|
} else {
|
||||||
|
// One or both keys are missing
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async fn execute(&mut self, ctx: &mut ModulesContext) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ use color_eyre::{eyre::ContextCompat, Result};
|
|||||||
use heck::ToUpperCamelCase;
|
use heck::ToUpperCamelCase;
|
||||||
use sea_schema::sea_query::{ColumnDef, ColumnSpec, ColumnType, IndexCreateStatement};
|
use sea_schema::sea_query::{ColumnDef, ColumnSpec, ColumnType, IndexCreateStatement};
|
||||||
|
|
||||||
use crate::config::sea_orm_config::DateTimeCrate;
|
use crate::generator::modules::sea_orm::config::DateTimeCrate;
|
||||||
|
|
||||||
use super::db::DbType;
|
use super::db::DbType;
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ use color_eyre::eyre::{eyre, ContextCompat, Report, Result};
|
|||||||
use sea_schema::sea_query::TableCreateStatement;
|
use sea_schema::sea_query::TableCreateStatement;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::config::db::DbConfig;
|
|
||||||
|
|
||||||
use super::DiscoveryConfig;
|
use super::DiscoveryConfig;
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum DbType {
|
pub enum DbType {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use color_eyre::Result;
|
|||||||
use db::DbType;
|
use db::DbType;
|
||||||
use sea_schema::sea_query::TableCreateStatement;
|
use sea_schema::sea_query::TableCreateStatement;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_inline_default::serde_inline_default;
|
|
||||||
use table::Table;
|
use table::Table;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
|||||||
@@ -1,25 +1,18 @@
|
|||||||
use std::{
|
use std::fmt::Debug;
|
||||||
fmt::Debug,
|
|
||||||
sync::{Arc, MutexGuard},
|
|
||||||
};
|
|
||||||
|
|
||||||
use anymap::{
|
use annotate::AnnotateModule;
|
||||||
any::{Any, CloneAny},
|
use anymap::{any::CloneAny, Map};
|
||||||
Map,
|
|
||||||
};
|
|
||||||
use color_eyre::{eyre::eyre, Result};
|
use color_eyre::{eyre::eyre, Result};
|
||||||
use discovery::DiscoveryModule;
|
use discovery::DiscoveryModule;
|
||||||
use sea_orm::{SeaOrmConfig, SeaOrmModule};
|
use models::ModelsModule;
|
||||||
// use models::ModelsModule;
|
use sea_orm::SeaOrmModule;
|
||||||
use serde::{de::IntoDeserializer, Deserialize};
|
use serde::{de::IntoDeserializer, Deserialize};
|
||||||
use std::sync::Mutex;
|
|
||||||
use templates::TemplateModule;
|
use templates::TemplateModule;
|
||||||
use toml_edit::{de::ValueDeserializer, DocumentMut, Item, Value};
|
use toml_edit::{DocumentMut, Item};
|
||||||
// use models::table::Table;
|
|
||||||
//
|
|
||||||
// use super::discover::DbType;
|
|
||||||
|
|
||||||
|
use super::file::FileManager;
|
||||||
type AnyCloneMap = Map<dyn CloneAny + Send>;
|
type AnyCloneMap = Map<dyn CloneAny + Send>;
|
||||||
|
pub mod annotate;
|
||||||
pub mod discovery;
|
pub mod discovery;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
pub mod sea_orm;
|
pub mod sea_orm;
|
||||||
@@ -28,12 +21,14 @@ pub mod templates;
|
|||||||
pub struct ModulesContext {
|
pub struct ModulesContext {
|
||||||
pub anymap: AnyCloneMap,
|
pub anymap: AnyCloneMap,
|
||||||
pub root_config: DocumentMut,
|
pub root_config: DocumentMut,
|
||||||
|
pub file_manager: FileManager,
|
||||||
}
|
}
|
||||||
impl ModulesContext {
|
impl ModulesContext {
|
||||||
pub fn new(root_config: DocumentMut) -> Self {
|
pub fn new(root_config: DocumentMut) -> Self {
|
||||||
Self {
|
Self {
|
||||||
anymap: AnyCloneMap::new(),
|
anymap: AnyCloneMap::new(),
|
||||||
root_config,
|
root_config,
|
||||||
|
file_manager: FileManager::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_config_raw(&self, path: &str) -> Result<&Item> {
|
pub fn get_config_raw(&self, path: &str) -> Result<&Item> {
|
||||||
@@ -44,12 +39,12 @@ impl ModulesContext {
|
|||||||
if let Some(v) = item.get(i) {
|
if let Some(v) = item.get(i) {
|
||||||
*item = v;
|
*item = v;
|
||||||
} else {
|
} else {
|
||||||
return Err(eyre!("Config not found"));
|
return Err(eyre!("Config not found \"{i}\""));
|
||||||
}
|
}
|
||||||
} else if let Some(v) = self.root_config.get(i) {
|
} else if let Some(v) = self.root_config.get(i) {
|
||||||
item = Some(v);
|
item = Some(v);
|
||||||
} else {
|
} else {
|
||||||
return Err(eyre!("Config not found"));
|
return Err(eyre!("Config not found \"{i}\""));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(v) = item {
|
if let Some(v) = item {
|
||||||
@@ -58,8 +53,10 @@ impl ModulesContext {
|
|||||||
Err(eyre!("Config not found"))
|
Err(eyre!("Config not found"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_config<'a, V: Deserialize<'a> + Debug>(&self, path: &str) -> Result<V> {
|
pub fn get_config<'a, V: Deserialize<'a> + Debug>(&self, path: &str) -> Result<Option<V>> {
|
||||||
let item = self.get_config_raw(path)?;
|
let Ok(item) = self.get_config_raw(path) else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
let value = item
|
let value = item
|
||||||
.clone()
|
.clone()
|
||||||
.into_value()
|
.into_value()
|
||||||
@@ -67,26 +64,28 @@ impl ModulesContext {
|
|||||||
let deserializer = value.into_deserializer();
|
let deserializer = value.into_deserializer();
|
||||||
let config = V::deserialize(deserializer)?;
|
let config = V::deserialize(deserializer)?;
|
||||||
tracing::debug!(?config, "{}", path);
|
tracing::debug!(?config, "{}", path);
|
||||||
Ok(config)
|
Ok(Some(config))
|
||||||
}
|
}
|
||||||
pub fn get_config_auto<'a, V: Deserialize<'a> + Clone + Send + Debug + 'static>(
|
pub fn get_config_auto<'a, V: Deserialize<'a> + Clone + Send + Debug + Default + 'static>(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: &str,
|
path: &str,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let value: V = self.get_config::<V>(path)?;
|
let value: Option<V> = self.get_config::<V>(path)?;
|
||||||
self.get_anymap_mut().insert(value);
|
if value.is_none() {
|
||||||
|
tracing::warn!(?path, "Config not found, using default");
|
||||||
|
}
|
||||||
|
self.get_anymap_mut().insert(value.unwrap_or_default());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
// pub fn get_anymap(&self) -> MutexGuard<Map> {
|
|
||||||
// let v = self.anymap.lock().unwrap();
|
|
||||||
// v
|
|
||||||
// }
|
|
||||||
pub fn get_anymap(&self) -> &AnyCloneMap {
|
pub fn get_anymap(&self) -> &AnyCloneMap {
|
||||||
&self.anymap
|
&self.anymap
|
||||||
}
|
}
|
||||||
pub fn get_anymap_mut(&mut self) -> &mut AnyCloneMap {
|
pub fn get_anymap_mut(&mut self) -> &mut AnyCloneMap {
|
||||||
&mut self.anymap
|
&mut self.anymap
|
||||||
}
|
}
|
||||||
|
pub fn get_file_manager(&mut self) -> &mut FileManager {
|
||||||
|
&mut self.file_manager
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait Module: Debug {
|
pub trait Module: Debug {
|
||||||
@@ -105,7 +104,9 @@ impl ModuleManager {
|
|||||||
let modules: Vec<Box<dyn Module>> = vec![
|
let modules: Vec<Box<dyn Module>> = vec![
|
||||||
Box::new(TemplateModule),
|
Box::new(TemplateModule),
|
||||||
Box::new(DiscoveryModule),
|
Box::new(DiscoveryModule),
|
||||||
Box::new(SeaOrmModule), //Box::new(ModelsModule)
|
Box::new(SeaOrmModule),
|
||||||
|
Box::new(ModelsModule),
|
||||||
|
Box::new(AnnotateModule::default()),
|
||||||
];
|
];
|
||||||
Self {
|
Self {
|
||||||
modules,
|
modules,
|
||||||
|
|||||||
@@ -1,27 +1,167 @@
|
|||||||
use super::{Module, ModulesContext};
|
use std::path::PathBuf;
|
||||||
use color_eyre::Result;
|
|
||||||
use serde::Deserialize;
|
use crate::generator::file::pathbuf_to_rust_path;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
discovery::DiscoveredSchema, sea_orm::SeaOrmConfig, templates::TemplateConfig, Module,
|
||||||
|
ModulesContext,
|
||||||
|
};
|
||||||
|
use color_eyre::{
|
||||||
|
eyre::{eyre, Context, ContextCompat},
|
||||||
|
Result,
|
||||||
|
};
|
||||||
|
use handlebars::Handlebars;
|
||||||
|
use heck::ToPascalCase;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct ModelsConfig {
|
pub struct ModelsConfig {
|
||||||
pub enable: bool,
|
pub enable: bool,
|
||||||
pub database_schema: String,
|
pub path: Option<PathBuf>,
|
||||||
pub max_connections: u32,
|
pub prelude: bool,
|
||||||
pub acquire_timeout: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[derive(Debug)]
|
impl Default for ModelsConfig {
|
||||||
// pub struct ModelsModule;
|
fn default() -> Self {
|
||||||
//
|
Self {
|
||||||
// #[async_trait::async_trait]
|
enable: false,
|
||||||
// impl Module for ModelsModule {
|
path: None,
|
||||||
// fn init(&self, ctx: &mut ModulesContext) -> Result<()> {
|
prelude: true,
|
||||||
// Ok(())
|
}
|
||||||
// }
|
}
|
||||||
//
|
}
|
||||||
// async fn validate(&self, ctx: &mut ModulesContext) -> Result<bool> {
|
|
||||||
// Ok(false)
|
#[derive(Debug, Clone, Serialize)]
|
||||||
// }
|
pub struct ModelTemplateContext {
|
||||||
// }
|
entities_path: String,
|
||||||
|
model_path: String,
|
||||||
|
model_name: String,
|
||||||
|
entity_name: String,
|
||||||
|
active_model_name: String,
|
||||||
|
prelude_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModelTemplateContext {
|
||||||
|
pub fn new(entities_path: String, model_name: String, prelude_path: String) -> Self {
|
||||||
|
let model_path = model_name.clone();
|
||||||
|
let active_model_name = format!("{}ActiveModel", model_name).to_pascal_case();
|
||||||
|
let model_name = format!("{}Model", model_name).to_pascal_case();
|
||||||
|
let entity_name = model_path.clone().to_pascal_case();
|
||||||
|
Self {
|
||||||
|
entities_path,
|
||||||
|
model_path,
|
||||||
|
model_name,
|
||||||
|
active_model_name,
|
||||||
|
prelude_path,
|
||||||
|
entity_name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ModelsModule;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Module for ModelsModule {
|
||||||
|
fn init(&mut self, ctx: &mut ModulesContext) -> Result<()> {
|
||||||
|
ctx.get_config_auto::<ModelsConfig>("modules.model")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn validate(&mut self, ctx: &mut ModulesContext) -> Result<bool> {
|
||||||
|
let map = ctx.get_anymap();
|
||||||
|
|
||||||
|
if let (Some(config), Some(template_config), Some(sea_orm_config)) = (
|
||||||
|
map.get::<ModelsConfig>(),
|
||||||
|
map.get::<TemplateConfig>(),
|
||||||
|
map.get::<SeaOrmConfig>(),
|
||||||
|
) {
|
||||||
|
if config.enable && !template_config.enable {
|
||||||
|
return Err(eyre!(
|
||||||
|
"\"modules.template.enable\" must be enabled to use \"modules.model.enable\""
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if config.enable && !sea_orm_config.enable {
|
||||||
|
return Err(eyre!(
|
||||||
|
"\"modules.sea_orm.enable\" must be enabled to use \"modules.model.enable\""
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if config.enable && config.path.is_none() {
|
||||||
|
return Err(eyre!(
|
||||||
|
"\"modules.model.path\" must be set to use \"modules.model.enable\""
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(config.enable && template_config.enable)
|
||||||
|
} else {
|
||||||
|
// One or both keys are missing
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async fn execute(&mut self, ctx: &mut ModulesContext) -> Result<()> {
|
||||||
|
let mut files: Vec<(PathBuf, String)> = Vec::new();
|
||||||
|
let map = ctx.get_anymap();
|
||||||
|
|
||||||
|
if let (Some(config), Some(templates), Some(sea_orm_config), Some(schema)) = (
|
||||||
|
map.get::<ModelsConfig>(),
|
||||||
|
map.get::<Handlebars<'static>>(),
|
||||||
|
map.get::<SeaOrmConfig>(),
|
||||||
|
map.get::<DiscoveredSchema>(),
|
||||||
|
) {
|
||||||
|
let models_path = config.path.clone().unwrap();
|
||||||
|
tracing::info!(?models_path, "Models path");
|
||||||
|
let entities_path = sea_orm_config.path.clone().unwrap();
|
||||||
|
let mod_path = models_path.join("mod.rs");
|
||||||
|
if config.prelude {
|
||||||
|
files.push((mod_path.clone(), "pub mod prelude;".to_string()));
|
||||||
|
}
|
||||||
|
for table in &schema.tables {
|
||||||
|
tracing::debug!(?table, "Generating model for table");
|
||||||
|
let path = models_path.join(format!("{}.rs", table.name));
|
||||||
|
|
||||||
|
let relative_entities_path = pathdiff::diff_paths(&entities_path, &path)
|
||||||
|
.context("Failed to calculate relative path")?;
|
||||||
|
let relative_entities_rust_path = pathbuf_to_rust_path(relative_entities_path);
|
||||||
|
let context = ModelTemplateContext::new(
|
||||||
|
relative_entities_rust_path.clone(),
|
||||||
|
table.name.clone(),
|
||||||
|
"super::prelude".to_string(),
|
||||||
|
);
|
||||||
|
if config.prelude {
|
||||||
|
let prelude_part = templates
|
||||||
|
.render("model_prelude_part", &context)
|
||||||
|
.context("Failed to render model prelude part")?;
|
||||||
|
files.push((models_path.join("prelude.rs"), prelude_part.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
files.push((mod_path.clone(), format!("pub mod {};", table.name)));
|
||||||
|
if path.exists() {
|
||||||
|
tracing::debug!(?path, "Model file already exists");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.prelude {
|
||||||
|
let content = templates
|
||||||
|
.render("model_prelude", &context)
|
||||||
|
.context("Failed to render model prelude")?;
|
||||||
|
files.push((path.clone(), content.clone()));
|
||||||
|
} else {
|
||||||
|
let content = templates
|
||||||
|
.render("model", &context)
|
||||||
|
.context("Failed to render model")?;
|
||||||
|
files.push((path.clone(), content.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// One or both keys are missing
|
||||||
|
}
|
||||||
|
tracing::info!(?files, "Generated model files");
|
||||||
|
let file_manager = ctx.get_file_manager();
|
||||||
|
for (output_path, content) in files {
|
||||||
|
file_manager.insert(&output_path, &content, None)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// use crate::{
|
// use crate::{
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
use serde_yaml::Value;
|
use toml::Value;
|
||||||
|
|
||||||
use sea_orm_codegen::{DateTimeCrate as CodegenDateTimeCrate, WithPrelude, WithSerde};
|
use sea_orm_codegen::{DateTimeCrate as CodegenDateTimeCrate, WithPrelude, WithSerde};
|
||||||
|
|
||||||
@@ -43,8 +43,8 @@ impl<'de> Deserialize<'de> for SerdeEnable {
|
|||||||
match value {
|
match value {
|
||||||
Value::String(s) if s == "serialize" => Ok(SerdeEnable::Serialize),
|
Value::String(s) if s == "serialize" => Ok(SerdeEnable::Serialize),
|
||||||
Value::String(s) if s == "deserialize" => Ok(SerdeEnable::Deserialize),
|
Value::String(s) if s == "deserialize" => Ok(SerdeEnable::Deserialize),
|
||||||
Value::Bool(true) => Ok(SerdeEnable::Both),
|
Value::Boolean(true) => Ok(SerdeEnable::Both),
|
||||||
Value::Bool(false) => Ok(SerdeEnable::None),
|
Value::Boolean(false) => Ok(SerdeEnable::None),
|
||||||
_ => Err(serde::de::Error::custom(
|
_ => Err(serde::de::Error::custom(
|
||||||
"expected 'serialize', 'deserialize', 'true' or 'false'",
|
"expected 'serialize', 'deserialize', 'true' or 'false'",
|
||||||
)),
|
)),
|
||||||
@@ -72,8 +72,8 @@ impl<'de> Deserialize<'de> for Prelude {
|
|||||||
let value = Value::deserialize(deserializer)?;
|
let value = Value::deserialize(deserializer)?;
|
||||||
|
|
||||||
match value {
|
match value {
|
||||||
Value::Bool(true) => Ok(Prelude::Enabled),
|
Value::Boolean(true) => Ok(Prelude::Enabled),
|
||||||
Value::Bool(false) => Ok(Prelude::Disabled),
|
Value::Boolean(false) => Ok(Prelude::Disabled),
|
||||||
Value::String(s) if s == "allow_unused_imports" => Ok(Prelude::AllowUnusedImports),
|
Value::String(s) if s == "allow_unused_imports" => Ok(Prelude::AllowUnusedImports),
|
||||||
_ => Err(serde::de::Error::custom(
|
_ => Err(serde::de::Error::custom(
|
||||||
"expected 'true', 'false', or 'allow_unused_imports'",
|
"expected 'true', 'false', or 'allow_unused_imports'",
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ impl Module for SeaOrmModule {
|
|||||||
}
|
}
|
||||||
async fn execute(&mut self, ctx: &mut ModulesContext) -> Result<()> {
|
async fn execute(&mut self, ctx: &mut ModulesContext) -> Result<()> {
|
||||||
let map = ctx.get_anymap();
|
let map = ctx.get_anymap();
|
||||||
|
let mut outputs = vec![];
|
||||||
if let (Some(statements), Some(config), Some(discovery_config)) = (
|
if let (Some(statements), Some(config), Some(discovery_config)) = (
|
||||||
map.get::<RawDiscoveredStatements>(),
|
map.get::<RawDiscoveredStatements>(),
|
||||||
map.get::<SeaOrmConfig>(),
|
map.get::<SeaOrmConfig>(),
|
||||||
@@ -86,18 +87,20 @@ impl Module for SeaOrmModule {
|
|||||||
config.entity.extra_derives.eenum.clone(),
|
config.entity.extra_derives.eenum.clone(),
|
||||||
config.entity.extra_attributes.eenum.clone(),
|
config.entity.extra_attributes.eenum.clone(),
|
||||||
false,
|
false,
|
||||||
true,
|
false,
|
||||||
);
|
);
|
||||||
let output = EntityTransformer::transform(statements.statements.clone())?
|
let output = EntityTransformer::transform(statements.statements.clone())?
|
||||||
.generate(&writer_context);
|
.generate(&writer_context);
|
||||||
for file in output.files {
|
outputs.extend(output.files.into_iter().map(|file| {
|
||||||
let file_path = config.path.clone().unwrap_or_default();
|
let file_path = config.path.clone().unwrap_or_default();
|
||||||
let file_path = file_path.join(file.name);
|
let file_path = file_path.join(file.name);
|
||||||
tracing::info!(?file_path, "Generating file");
|
(file_path, file.content)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
// let mut file_generator = crate::generator::file::FileGenerator::new(file_path);
|
let file_manager = ctx.get_file_manager();
|
||||||
// file_generator.write(file.content)?;
|
for (output_path, content) in outputs {
|
||||||
}
|
file_manager.insert(&output_path, &content, None)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,16 +1,84 @@
|
|||||||
use crate::generator::DatabaseUrl;
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
use super::{Module, ModulesContext};
|
use super::{Module, ModulesContext};
|
||||||
use color_eyre::Result;
|
use color_eyre::{
|
||||||
|
eyre::{eyre, ContextCompat},
|
||||||
|
Result,
|
||||||
|
};
|
||||||
use handlebars::Handlebars;
|
use handlebars::Handlebars;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_inline_default::serde_inline_default;
|
|
||||||
|
|
||||||
#[serde_inline_default]
|
use include_dir::{include_dir, Dir, DirEntry};
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
|
static TEMPLATE_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/templates");
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct TemplateConfig {
|
pub struct TemplateConfig {
|
||||||
#[serde_inline_default(false)]
|
|
||||||
pub enable: bool,
|
pub enable: bool,
|
||||||
|
pub path: Option<PathBuf>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub tables: HashMap<String, MapString>,
|
||||||
|
}
|
||||||
|
impl TemplateConfig {
|
||||||
|
pub fn to_paths(&self) -> Vec<(String, PathBuf)> {
|
||||||
|
let mut paths = Vec::new();
|
||||||
|
for (key, value) in &self.tables {
|
||||||
|
let map_string = value.clone();
|
||||||
|
paths.extend(map_string.into_paths(key.clone()));
|
||||||
|
}
|
||||||
|
let root = self.path.clone().unwrap_or_default();
|
||||||
|
|
||||||
|
paths
|
||||||
|
.into_iter()
|
||||||
|
.map(|(key, path)| {
|
||||||
|
let new_path = root.clone().join(path);
|
||||||
|
(key, new_path)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum MapString {
|
||||||
|
Map(HashMap<String, MapString>),
|
||||||
|
PathBuf(PathBuf),
|
||||||
|
}
|
||||||
|
impl MapString {
|
||||||
|
pub fn into_paths(self, prefix: String) -> Vec<(String, PathBuf)> {
|
||||||
|
fn write_path(prefix: String, string: MapString) -> Vec<(String, PathBuf)> {
|
||||||
|
let mut strings = Vec::new();
|
||||||
|
match string {
|
||||||
|
MapString::Map(inner) => {
|
||||||
|
for (key, value) in inner {
|
||||||
|
let new_prefix = if prefix.is_empty() {
|
||||||
|
key
|
||||||
|
} else {
|
||||||
|
format!("{}.{}", prefix, key)
|
||||||
|
};
|
||||||
|
strings.extend(write_path(new_prefix, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MapString::PathBuf(pathbuf) => {
|
||||||
|
strings.push((prefix, pathbuf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strings.sort();
|
||||||
|
strings
|
||||||
|
}
|
||||||
|
write_path(prefix, self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TemplateConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
enable: true,
|
||||||
|
tables: HashMap::new(),
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TemplateModule;
|
pub struct TemplateModule;
|
||||||
@@ -18,22 +86,118 @@ pub struct TemplateModule;
|
|||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Module for TemplateModule {
|
impl Module for TemplateModule {
|
||||||
fn init(&mut self, ctx: &mut ModulesContext) -> Result<()> {
|
fn init(&mut self, ctx: &mut ModulesContext) -> Result<()> {
|
||||||
let registry: Handlebars<'static> = Handlebars::new();
|
ctx.get_config_auto::<TemplateConfig>("modules.template")?;
|
||||||
ctx.get_anymap_mut().insert(registry);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
async fn validate(&mut self, ctx: &mut ModulesContext) -> Result<bool> {
|
async fn validate(&mut self, ctx: &mut ModulesContext) -> Result<bool> {
|
||||||
// let map = ctx.get_anymap();
|
let map = ctx.get_anymap_mut();
|
||||||
//
|
//
|
||||||
// if let (Some(config), Some(_)) = (map.get::<DiscoveryConfig>(), map.get::<DatabaseUrl>()) {
|
if let Some(config) = map.get::<TemplateConfig>() {
|
||||||
// Ok(config.enable)
|
if config.enable {
|
||||||
// } else {
|
for templates in config.to_paths() {
|
||||||
// // One or both keys are missing
|
let path = templates.1;
|
||||||
// Ok(false)
|
if !path.exists() {
|
||||||
// }
|
return Err(eyre!("Template path does not exist: {}", path.display()));
|
||||||
Ok(true)
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(config.enable)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
async fn execute(&mut self, ctx: &mut ModulesContext) -> Result<()> {
|
async fn execute(&mut self, ctx: &mut ModulesContext) -> Result<()> {
|
||||||
|
let mut registry: Handlebars<'static> = Handlebars::new();
|
||||||
|
if let Some(config) = ctx.get_anymap().get::<TemplateConfig>() {
|
||||||
|
for (templates, path) in config.to_paths() {
|
||||||
|
tracing::debug!(?templates, ?path, "Registering template");
|
||||||
|
let content = fs::read_to_string(path).await?;
|
||||||
|
registry
|
||||||
|
.register_template_string(templates.as_str(), content)
|
||||||
|
.map_err(|_| eyre!("Failed to register template"))?;
|
||||||
|
}
|
||||||
|
Self::register_default_templates(TEMPLATE_DIR.entries(), &mut registry).await?;
|
||||||
|
}
|
||||||
|
ctx.get_anymap_mut().insert(registry);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TemplateModule {
|
||||||
|
async fn register_default_templates<'a>(
|
||||||
|
entries: &[DirEntry<'a>],
|
||||||
|
handlebars: &mut Handlebars<'a>,
|
||||||
|
) -> Result<()> {
|
||||||
|
for entry in entries.iter().filter(|file| {
|
||||||
|
file.path()
|
||||||
|
.extension()
|
||||||
|
.is_some_and(|f| f.to_str().is_some_and(|f| f == "hbs"))
|
||||||
|
|| file.as_dir().is_some()
|
||||||
|
}) {
|
||||||
|
match entry {
|
||||||
|
DirEntry::File(file) => {
|
||||||
|
let path = file.path().with_extension("");
|
||||||
|
let name = path
|
||||||
|
.to_str()
|
||||||
|
.context("Failed to convert path to str")?
|
||||||
|
.replace("/", ".");
|
||||||
|
let content = file
|
||||||
|
.contents_utf8()
|
||||||
|
.context(format!("Template {} failed to parse", name))?;
|
||||||
|
|
||||||
|
tracing::debug!(?name, "Registering template");
|
||||||
|
if !handlebars.has_template(&name) {
|
||||||
|
handlebars.register_template_string(&name, content)?;
|
||||||
|
} else {
|
||||||
|
tracing::debug!(?name, "Template already registered, skipping");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DirEntry::Dir(dir) => {
|
||||||
|
Box::pin(Self::register_default_templates(dir.entries(), handlebars)).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
// #[test]
|
||||||
|
// fn test_map_string() {
|
||||||
|
// use super::MapString;
|
||||||
|
// let map = MapString::Map(
|
||||||
|
// vec![
|
||||||
|
// ("a".to_string(), MapString::PathBuf("b".to_string())),
|
||||||
|
// ("c".to_string(), MapString::PathBuf("d".to_string())),
|
||||||
|
// (
|
||||||
|
// "e".to_string(),
|
||||||
|
// MapString::Map(
|
||||||
|
// vec![("f".to_string(), MapString::String("g".to_string()))]
|
||||||
|
// .into_iter()
|
||||||
|
// .collect(),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ]
|
||||||
|
// .into_iter()
|
||||||
|
// .collect(),
|
||||||
|
// );
|
||||||
|
// let paths = map.into_paths("".to_string());
|
||||||
|
// assert_eq!(
|
||||||
|
// paths,
|
||||||
|
// vec![
|
||||||
|
// ("a".to_string(), "b".to_string()),
|
||||||
|
// ("c".to_string(), "d".to_string()),
|
||||||
|
// ("e.f".to_string(), "g".to_string())
|
||||||
|
// ]
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// #[test]
|
||||||
|
// fn test_map_string2() {
|
||||||
|
// use super::MapString;
|
||||||
|
// let map = MapString::String("a".to_string());
|
||||||
|
// let paths = map.to_paths();
|
||||||
|
// assert_eq!(paths, vec!["a"]);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
mod config;
|
// mod config;
|
||||||
mod generator;
|
mod generator;
|
||||||
mod templates;
|
mod templates;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use color_eyre::{eyre::eyre, Result};
|
use color_eyre::{eyre::eyre, Result};
|
||||||
use config::Config;
|
|
||||||
use figment::{
|
|
||||||
providers::{Format, Serialized, Yaml},
|
|
||||||
Figment,
|
|
||||||
};
|
|
||||||
use handlebars::Handlebars;
|
use handlebars::Handlebars;
|
||||||
use tokio::{fs, io::AsyncWriteExt, process::Command};
|
use tokio::{fs, io::AsyncWriteExt, process::Command};
|
||||||
use toml_edit::DocumentMut;
|
use toml_edit::DocumentMut;
|
||||||
@@ -44,7 +39,7 @@ async fn main() -> Result<()> {
|
|||||||
let config = fs::read_to_string(args.config).await?;
|
let config = fs::read_to_string(args.config).await?;
|
||||||
let root_config = config.parse::<DocumentMut>()?;
|
let root_config = config.parse::<DocumentMut>()?;
|
||||||
|
|
||||||
let outputs = generator::generate(&args.database_url, root_config).await?;
|
generator::generate(&args.database_url, root_config).await?;
|
||||||
//
|
//
|
||||||
// // tracing::info!(?outputs, "Generated files");
|
// // tracing::info!(?outputs, "Generated files");
|
||||||
// for output in outputs.iter() {
|
// for output in outputs.iter() {
|
||||||
|
|||||||
@@ -1,67 +1,28 @@
|
|||||||
use crate::config::Config;
|
|
||||||
use color_eyre::eyre::{ContextCompat, Result};
|
use color_eyre::eyre::{ContextCompat, Result};
|
||||||
use handlebars::Handlebars;
|
use handlebars::Handlebars;
|
||||||
use include_dir::{include_dir, Dir, DirEntry};
|
|
||||||
use serde_yaml::Value;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
|
||||||
static TEMPLATE_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/templates");
|
// static TEMPLATE_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/templates");
|
||||||
|
|
||||||
async fn handle_direntries<'a>(
|
// pub async fn register_templates(handlebars: &mut Handlebars<'_>, config: &Config) -> Result<()> {
|
||||||
entries: &[DirEntry<'a>],
|
// // if let Some(templates) = &config.templates {
|
||||||
handlebars: &mut Handlebars<'a>,
|
// // for (name, value) in templates.iter() {
|
||||||
) -> Result<()> {
|
// // let Value::String(name) = name else {
|
||||||
for entry in entries.iter().filter(|file| {
|
// // return Err(color_eyre::eyre::eyre!("Invalid template name"));
|
||||||
file.path()
|
// // };
|
||||||
.extension()
|
// // let Value::String(path) = value else {
|
||||||
.is_some_and(|f| f.to_str().is_some_and(|f| f == "hbs"))
|
// // return Err(color_eyre::eyre::eyre!("Invalid template value"));
|
||||||
|| file.as_dir().is_some()
|
// // };
|
||||||
}) {
|
// // let mut path = PathBuf::from(path);
|
||||||
match entry {
|
// // if let Some(templates_dir) = &config.templates_dir {
|
||||||
DirEntry::File(file) => {
|
// // path = templates_dir.join(path);
|
||||||
let path = file.path().with_extension("");
|
// // }
|
||||||
let name = path
|
// // tracing::info!(?name, ?path, "Registering template");
|
||||||
.to_str()
|
// // let content = fs::read_to_string(path).await?;
|
||||||
.context("Failed to convert path to str")?
|
// // handlebars.register_template_string(name, content)?;
|
||||||
.replace("/", ".");
|
// // }
|
||||||
let content = file
|
// // }
|
||||||
.contents_utf8()
|
// handle_direntries(TEMPLATE_DIR.entries(), handlebars).await?;
|
||||||
.context(format!("Template {} failed to parse", name))?;
|
// Ok(())
|
||||||
|
// }
|
||||||
tracing::debug!(?name, "Registering template");
|
|
||||||
if !handlebars.has_template(&name) {
|
|
||||||
handlebars.register_template_string(&name, content)?;
|
|
||||||
} else {
|
|
||||||
tracing::debug!(?name, "Template already registered, skipping");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DirEntry::Dir(dir) => {
|
|
||||||
Box::pin(handle_direntries(dir.entries(), handlebars)).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub async fn register_templates(handlebars: &mut Handlebars<'_>, config: &Config) -> Result<()> {
|
|
||||||
if let Some(templates) = &config.templates {
|
|
||||||
for (name, value) in templates.iter() {
|
|
||||||
let Value::String(name) = name else {
|
|
||||||
return Err(color_eyre::eyre::eyre!("Invalid template name"));
|
|
||||||
};
|
|
||||||
let Value::String(path) = value else {
|
|
||||||
return Err(color_eyre::eyre::eyre!("Invalid template value"));
|
|
||||||
};
|
|
||||||
let mut path = PathBuf::from(path);
|
|
||||||
if let Some(templates_dir) = &config.templates_dir {
|
|
||||||
path = templates_dir.join(path);
|
|
||||||
}
|
|
||||||
tracing::info!(?name, ?path, "Registering template");
|
|
||||||
let content = fs::read_to_string(path).await?;
|
|
||||||
handlebars.register_template_string(name, content)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handle_direntries(TEMPLATE_DIR.entries(), handlebars).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
1
templates/model_prelude_part.hbs
Normal file
1
templates/model_prelude_part.hbs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub use {{entities_path}}::{{model_name}}::{ActiveModel as {{active_model_name}}, Model as {{model_name}}, Entity as {{entity_name}}};
|
||||||
3
tests/src/models/_entities/mod.rs
Normal file
3
tests/src/models/_entities/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.8
|
||||||
|
|
||||||
|
pub mod user ;
|
||||||
9
tests/src/models/_entities/user.rs
Normal file
9
tests/src/models/_entities/user.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.8
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
use sea_orm :: entity :: prelude :: * ;
|
||||||
|
|
||||||
|
# [derive (Clone , Debug , PartialEq , DeriveEntityModel , Eq)] # [sea_orm (table_name = "user")] pub struct Model { # [sea_orm (primary_key)] pub id : i32 , # [sea_orm (unique)] pub username : String , # [sea_orm (unique)] pub email : String , pub password : String , # [sea_orm (unique)] pub test : String , }
|
||||||
|
|
||||||
|
# [derive (Copy , Clone , Debug , EnumIter , DeriveRelation)] pub enum Relation { }
|
||||||
1
tests/src/models/mod.rs
Normal file
1
tests/src/models/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod prelude;pub mod user;
|
||||||
1
tests/src/models/prelude.rs
Normal file
1
tests/src/models/prelude.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub use super::_entities::UserModel::{ActiveModel as UserActiveModel, Model as UserModel, Entity as User};
|
||||||
9
tests/src/models/user.rs
Normal file
9
tests/src/models/user.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use super::prelude::*;
|
||||||
|
use sea_orm::ActiveModelBehavior;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl ActiveModelBehavior for UserActiveModel {}
|
||||||
|
|
||||||
|
impl UserModel {}
|
||||||
|
|
||||||
|
impl UserActiveModel {}
|
||||||
Reference in New Issue
Block a user