style: apply cargo fmt
This commit is contained in:
parent
534ed83419
commit
d205d722aa
17 changed files with 390 additions and 315 deletions
|
|
@ -1,11 +1,12 @@
|
|||
use anyhow::Context;
|
||||
use std::str::FromStr;
|
||||
use std::path::PathBuf;
|
||||
use anyhow::Result;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use fully_pub::fully_pub;
|
||||
use sqlx::{
|
||||
Pool, Sqlite, sqlite::{SqliteConnectOptions, SqlitePoolOptions},
|
||||
sqlite::{SqliteConnectOptions, SqlitePoolOptions},
|
||||
Pool, Sqlite,
|
||||
};
|
||||
|
||||
/// database storage interface
|
||||
|
|
@ -13,7 +14,6 @@ use sqlx::{
|
|||
#[derive(Clone, Debug)]
|
||||
struct Database(Pool<Sqlite>);
|
||||
|
||||
|
||||
/// Initialize database
|
||||
pub async fn provide_database(sqlite_db_path: &str) -> Result<Database> {
|
||||
let path = PathBuf::from(sqlite_db_path);
|
||||
|
|
@ -37,5 +37,3 @@ pub async fn provide_database(sqlite_db_path: &str) -> Result<Database> {
|
|||
|
||||
Ok(Database(pool))
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
pub mod repositories;
|
||||
pub mod db;
|
||||
pub mod models;
|
||||
pub mod repositories;
|
||||
|
|
|
|||
|
|
@ -4,12 +4,15 @@ use chrono::Utc;
|
|||
use sqlx::types::Json;
|
||||
use sqlxgentools_misc::ForeignRef;
|
||||
|
||||
use crate::{db::provide_database, models::user::{User, UserToken}, repositories::user_token_repository::UserTokenRepository};
|
||||
use crate::{
|
||||
db::provide_database,
|
||||
models::user::{User, UserToken},
|
||||
repositories::user_token_repository::UserTokenRepository,
|
||||
};
|
||||
|
||||
pub mod db;
|
||||
pub mod models;
|
||||
pub mod repositories;
|
||||
pub mod db;
|
||||
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
|
|
@ -24,7 +27,7 @@ async fn main() -> Result<()> {
|
|||
last_login_at: None,
|
||||
status: models::user::UserStatus::Invited,
|
||||
groups: Json(vec![]),
|
||||
avatar_bytes: None
|
||||
avatar_bytes: None,
|
||||
},
|
||||
User {
|
||||
id: "idu2".into(),
|
||||
|
|
@ -34,7 +37,7 @@ async fn main() -> Result<()> {
|
|||
last_login_at: None,
|
||||
status: models::user::UserStatus::Invited,
|
||||
groups: Json(vec![]),
|
||||
avatar_bytes: None
|
||||
avatar_bytes: None,
|
||||
},
|
||||
User {
|
||||
id: "idu3".into(),
|
||||
|
|
@ -44,8 +47,8 @@ async fn main() -> Result<()> {
|
|||
last_login_at: None,
|
||||
status: models::user::UserStatus::Invited,
|
||||
groups: Json(vec![]),
|
||||
avatar_bytes: None
|
||||
}
|
||||
avatar_bytes: None,
|
||||
},
|
||||
];
|
||||
let user_token = UserToken {
|
||||
id: "idtoken1".into(),
|
||||
|
|
@ -53,43 +56,44 @@ async fn main() -> Result<()> {
|
|||
last_use_time: None,
|
||||
creation_time: Utc::now(),
|
||||
expiration_time: Utc::now(),
|
||||
user_id: ForeignRef::new(&users.get(0).unwrap())
|
||||
user_id: ForeignRef::new(&users.get(0).unwrap()),
|
||||
};
|
||||
|
||||
let db = provide_database("tmp/db.db").await?;
|
||||
|
||||
let user_token_repo = UserTokenRepository::new(db);
|
||||
user_token_repo.insert_many(&vec![
|
||||
UserToken {
|
||||
id: "idtoken2".into(),
|
||||
secret: "4LP5A3F3XBV5NM8VXRGZG3QDXO9PNAC0".into(),
|
||||
last_use_time: None,
|
||||
creation_time: Utc::now(),
|
||||
expiration_time: Utc::now(),
|
||||
user_id: ForeignRef::new(&users.get(0).unwrap())
|
||||
},
|
||||
UserToken {
|
||||
id: "idtoken3".into(),
|
||||
secret: "CBHR6G41KSEMR1AI".into(),
|
||||
last_use_time: None,
|
||||
creation_time: Utc::now(),
|
||||
expiration_time: Utc::now(),
|
||||
user_id: ForeignRef::new(&users.get(1).unwrap())
|
||||
},
|
||||
UserToken {
|
||||
id: "idtoken4".into(),
|
||||
secret: "CBHR6G41KSEMR1AI".into(),
|
||||
last_use_time: None,
|
||||
creation_time: Utc::now(),
|
||||
expiration_time: Utc::now(),
|
||||
user_id: ForeignRef::new(&users.get(1).unwrap())
|
||||
}
|
||||
]).await?;
|
||||
let user_tokens = user_token_repo.get_many_user_tokens_by_usersss(
|
||||
vec!["idu2".into()]
|
||||
).await?;
|
||||
user_token_repo
|
||||
.insert_many(&vec![
|
||||
UserToken {
|
||||
id: "idtoken2".into(),
|
||||
secret: "4LP5A3F3XBV5NM8VXRGZG3QDXO9PNAC0".into(),
|
||||
last_use_time: None,
|
||||
creation_time: Utc::now(),
|
||||
expiration_time: Utc::now(),
|
||||
user_id: ForeignRef::new(&users.get(0).unwrap()),
|
||||
},
|
||||
UserToken {
|
||||
id: "idtoken3".into(),
|
||||
secret: "CBHR6G41KSEMR1AI".into(),
|
||||
last_use_time: None,
|
||||
creation_time: Utc::now(),
|
||||
expiration_time: Utc::now(),
|
||||
user_id: ForeignRef::new(&users.get(1).unwrap()),
|
||||
},
|
||||
UserToken {
|
||||
id: "idtoken4".into(),
|
||||
secret: "CBHR6G41KSEMR1AI".into(),
|
||||
last_use_time: None,
|
||||
creation_time: Utc::now(),
|
||||
expiration_time: Utc::now(),
|
||||
user_id: ForeignRef::new(&users.get(1).unwrap()),
|
||||
},
|
||||
])
|
||||
.await?;
|
||||
let user_tokens = user_token_repo
|
||||
.get_many_user_tokens_by_usersss(vec!["idu2".into()])
|
||||
.await?;
|
||||
dbg!(&user_tokens);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use sqlx::types::Json;
|
||||
use fully_pub::fully_pub;
|
||||
use sqlx::types::Json;
|
||||
|
||||
use sqlxgentools_attrs::{SqlGeneratorDerive, SqlGeneratorModelWithId, sql_generator_model};
|
||||
use sqlxgentools_attrs::{sql_generator_model, SqlGeneratorDerive, SqlGeneratorModelWithId};
|
||||
use sqlxgentools_misc::{DatabaseLine, ForeignRef};
|
||||
|
||||
#[derive(sqlx::Type, Clone, Debug, PartialEq)]
|
||||
|
|
@ -11,37 +11,36 @@ enum UserStatus {
|
|||
Disabled,
|
||||
Invited,
|
||||
Active,
|
||||
Archived
|
||||
Archived,
|
||||
}
|
||||
|
||||
#[derive(SqlGeneratorDerive, SqlGeneratorModelWithId, sqlx::FromRow, Debug, Clone)]
|
||||
#[sql_generator_model(table_name="usersss")]
|
||||
#[sql_generator_model(table_name = "usersss")]
|
||||
#[fully_pub]
|
||||
struct User {
|
||||
#[sql_generator_field(is_primary=true)]
|
||||
#[sql_generator_field(is_primary = true)]
|
||||
id: String,
|
||||
#[sql_generator_field(is_unique=true)]
|
||||
#[sql_generator_field(is_unique = true)]
|
||||
handle: String,
|
||||
full_name: Option<String>,
|
||||
prefered_color: Option<i64>,
|
||||
last_login_at: Option<DateTime<Utc>>,
|
||||
status: UserStatus,
|
||||
groups: Json<Vec<String>>,
|
||||
avatar_bytes: Option<Vec<u8>>
|
||||
avatar_bytes: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(SqlGeneratorDerive, SqlGeneratorModelWithId, sqlx::FromRow, Debug, Clone)]
|
||||
#[sql_generator_model(table_name="user_tokens")]
|
||||
#[sql_generator_model(table_name = "user_tokens")]
|
||||
#[fully_pub]
|
||||
struct UserToken {
|
||||
#[sql_generator_field(is_primary=true)]
|
||||
#[sql_generator_field(is_primary = true)]
|
||||
id: String,
|
||||
secret: String,
|
||||
last_use_time: Option<DateTime<Utc>>,
|
||||
creation_time: DateTime<Utc>,
|
||||
expiration_time: DateTime<Utc>,
|
||||
#[sql_generator_field(reverse_relation_name="user_tokens")] // to generate get_user_tokens_of_user(&user_id)
|
||||
user_id: ForeignRef<User>
|
||||
#[sql_generator_field(reverse_relation_name = "user_tokens")]
|
||||
// to generate get_user_tokens_of_user(&user_id)
|
||||
user_id: ForeignRef<User>,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::models::user::User;
|
||||
use crate::db::Database;
|
||||
use crate::models::user::User;
|
||||
pub struct UserRepository {
|
||||
db: Database,
|
||||
}
|
||||
|
|
@ -8,7 +8,9 @@ impl UserRepository {
|
|||
UserRepository { db }
|
||||
}
|
||||
pub async fn get_all(&self) -> Result<Vec<User>, sqlx::Error> {
|
||||
sqlx::query_as::<_, User>("SELECT * FROM usersss").fetch_all(&self.db.0).await
|
||||
sqlx::query_as::<_, User>("SELECT * FROM usersss")
|
||||
.fetch_all(&self.db.0)
|
||||
.await
|
||||
}
|
||||
pub async fn get_by_id(&self, item_id: &str) -> Result<User, sqlx::Error> {
|
||||
sqlx::query_as::<_, User>("SELECT * FROM usersss WHERE id = $1")
|
||||
|
|
@ -16,10 +18,7 @@ impl UserRepository {
|
|||
.fetch_one(&self.db.0)
|
||||
.await
|
||||
}
|
||||
pub async fn get_many_by_id(
|
||||
&self,
|
||||
items_ids: &[&str],
|
||||
) -> Result<Vec<User>, sqlx::Error> {
|
||||
pub async fn get_many_by_id(&self, items_ids: &[&str]) -> Result<Vec<User>, sqlx::Error> {
|
||||
if items_ids.is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
|
@ -27,9 +26,7 @@ impl UserRepository {
|
|||
.map(|i| format!("${}", i))
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
let query_sql = format!(
|
||||
"SELECT * FROM usersss WHERE id IN ({})", placeholder_params
|
||||
);
|
||||
let query_sql = format!("SELECT * FROM usersss WHERE id IN ({})", placeholder_params);
|
||||
let mut query = sqlx::query_as::<_, User>(&query_sql);
|
||||
for id in items_ids {
|
||||
query = query.bind(id);
|
||||
|
|
@ -59,8 +56,11 @@ impl UserRepository {
|
|||
.map(|c| c.to_vec())
|
||||
.map(|x| {
|
||||
format!(
|
||||
"({})", x.iter().map(| i | format!("${}", i)).collect:: < Vec <
|
||||
String >> ().join(", ")
|
||||
"({})",
|
||||
x.iter()
|
||||
.map(|i| format!("${}", i))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
|
|
@ -84,11 +84,7 @@ impl UserRepository {
|
|||
query.execute(&self.db.0).await?;
|
||||
Ok(())
|
||||
}
|
||||
pub async fn update_by_id(
|
||||
&self,
|
||||
item_id: &str,
|
||||
entity: &User,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
pub async fn update_by_id(&self, item_id: &str, entity: &User) -> Result<(), sqlx::Error> {
|
||||
sqlx::query(
|
||||
"UPDATE usersss SET id = $2, handle = $3, full_name = $4, prefered_color = $5, last_login_at = $6, status = $7, groups = $8, avatar_bytes = $9 WHERE id = $1",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::models::user::UserToken;
|
||||
use crate::db::Database;
|
||||
use crate::models::user::UserToken;
|
||||
pub struct UserTokenRepository {
|
||||
db: Database,
|
||||
}
|
||||
|
|
@ -18,10 +18,7 @@ impl UserTokenRepository {
|
|||
.fetch_one(&self.db.0)
|
||||
.await
|
||||
}
|
||||
pub async fn get_many_by_id(
|
||||
&self,
|
||||
items_ids: &[&str],
|
||||
) -> Result<Vec<UserToken>, sqlx::Error> {
|
||||
pub async fn get_many_by_id(&self, items_ids: &[&str]) -> Result<Vec<UserToken>, sqlx::Error> {
|
||||
if items_ids.is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
|
@ -30,7 +27,8 @@ impl UserTokenRepository {
|
|||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
let query_sql = format!(
|
||||
"SELECT * FROM user_tokens WHERE id IN ({})", placeholder_params
|
||||
"SELECT * FROM user_tokens WHERE id IN ({})",
|
||||
placeholder_params
|
||||
);
|
||||
let mut query = sqlx::query_as::<_, UserToken>(&query_sql);
|
||||
for id in items_ids {
|
||||
|
|
@ -52,18 +50,18 @@ impl UserTokenRepository {
|
|||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
pub async fn insert_many(
|
||||
&self,
|
||||
entities: &Vec<UserToken>,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
pub async fn insert_many(&self, entities: &Vec<UserToken>) -> Result<(), sqlx::Error> {
|
||||
let values_templates: String = (1..(6usize * entities.len() + 1))
|
||||
.collect::<Vec<usize>>()
|
||||
.chunks(6usize)
|
||||
.map(|c| c.to_vec())
|
||||
.map(|x| {
|
||||
format!(
|
||||
"({})", x.iter().map(| i | format!("${}", i)).collect:: < Vec <
|
||||
String >> ().join(", ")
|
||||
"({})",
|
||||
x.iter()
|
||||
.map(|i| format!("${}", i))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
|
|
@ -85,11 +83,7 @@ impl UserTokenRepository {
|
|||
query.execute(&self.db.0).await?;
|
||||
Ok(())
|
||||
}
|
||||
pub async fn update_by_id(
|
||||
&self,
|
||||
item_id: &str,
|
||||
entity: &UserToken,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
pub async fn update_by_id(&self, item_id: &str, entity: &UserToken) -> Result<(), sqlx::Error> {
|
||||
sqlx::query(
|
||||
"UPDATE user_tokens SET id = $2, secret = $3, last_use_time = $4, creation_time = $5, expiration_time = $6, user_id = $7 WHERE id = $1",
|
||||
)
|
||||
|
|
@ -132,7 +126,8 @@ impl UserTokenRepository {
|
|||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
let query_tmpl = format!(
|
||||
"SELECT * FROM user_tokens WHERE user_id IN ({})", placeholder_params
|
||||
"SELECT * FROM user_tokens WHERE user_id IN ({})",
|
||||
placeholder_params
|
||||
);
|
||||
let mut query = sqlx::query_as::<_, UserToken>(&query_tmpl);
|
||||
for id in items_ids {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@
|
|||
use std::assert_matches::assert_matches;
|
||||
|
||||
use chrono::Utc;
|
||||
use sandbox::{models::user::{User, UserStatus}, repositories::user_repository::UserRepository};
|
||||
use sandbox::{
|
||||
models::user::{User, UserStatus},
|
||||
repositories::user_repository::UserRepository,
|
||||
};
|
||||
use sqlx::{types::Json, Pool, Sqlite};
|
||||
|
||||
#[sqlx::test(fixtures("../src/migrations/all.sql"))]
|
||||
|
|
@ -22,43 +25,40 @@ async fn test_user_repository_create_read_update_delete(pool: Pool<Sqlite>) -> s
|
|||
last_login_at: Some(Utc::now()),
|
||||
status: UserStatus::Invited,
|
||||
groups: Json(vec!["artists".into()]),
|
||||
avatar_bytes: vec![0x00]
|
||||
avatar_bytes: vec![0x00],
|
||||
};
|
||||
assert_matches!(user_repo.insert(&new_user).await, Ok(()));
|
||||
assert_matches!(
|
||||
user_repo.insert(&new_user).await,
|
||||
Ok(())
|
||||
);
|
||||
assert_matches!(
|
||||
user_repo.get_by_id("ffffffff-0000-4000-0000-0000000000c9".into()).await,
|
||||
user_repo
|
||||
.get_by_id("ffffffff-0000-4000-0000-0000000000c9".into())
|
||||
.await,
|
||||
Ok(User { .. })
|
||||
);
|
||||
assert_matches!(
|
||||
user_repo.get_by_id("ffffffff-0000-4040-0000-000000000000".into()).await,
|
||||
user_repo
|
||||
.get_by_id("ffffffff-0000-4040-0000-000000000000".into())
|
||||
.await,
|
||||
Err(sqlx::Error::RowNotFound)
|
||||
);
|
||||
|
||||
// Insert Many
|
||||
let bunch_of_users: Vec<User> = (0..10).map(|pid| User {
|
||||
id: format!("ffffffff-0000-4000-0010-{:0>8}", pid),
|
||||
handle: format!("user num {}", pid),
|
||||
full_name: None,
|
||||
prefered_color: None,
|
||||
last_login_at: None,
|
||||
status: UserStatus::Invited,
|
||||
groups: Json(vec![]),
|
||||
avatar_bytes: vec![]
|
||||
}).collect();
|
||||
assert_matches!(
|
||||
user_repo.insert_many(&bunch_of_users).await,
|
||||
Ok(())
|
||||
);
|
||||
let bunch_of_users: Vec<User> = (0..10)
|
||||
.map(|pid| User {
|
||||
id: format!("ffffffff-0000-4000-0010-{:0>8}", pid),
|
||||
handle: format!("user num {}", pid),
|
||||
full_name: None,
|
||||
prefered_color: None,
|
||||
last_login_at: None,
|
||||
status: UserStatus::Invited,
|
||||
groups: Json(vec![]),
|
||||
avatar_bytes: vec![],
|
||||
})
|
||||
.collect();
|
||||
assert_matches!(user_repo.insert_many(&bunch_of_users).await, Ok(()));
|
||||
|
||||
// Read many all
|
||||
let read_all_res = user_repo.get_all().await;
|
||||
assert_matches!(
|
||||
read_all_res,
|
||||
Ok(..)
|
||||
);
|
||||
assert_matches!(read_all_res, Ok(..));
|
||||
let all_users = read_all_res.unwrap();
|
||||
assert_eq!(all_users.len(), 11);
|
||||
|
||||
|
|
@ -69,16 +69,18 @@ async fn test_user_repository_create_read_update_delete(pool: Pool<Sqlite>) -> s
|
|||
user_repo.update_by_id(&new_user.id, &updated_user).await,
|
||||
Ok(())
|
||||
);
|
||||
let user_from_db = user_repo.get_by_id("ffffffff-0000-4000-0000-0000000000c9".into()).await.unwrap();
|
||||
let user_from_db = user_repo
|
||||
.get_by_id("ffffffff-0000-4000-0000-0000000000c9".into())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(user_from_db.status, UserStatus::Disabled);
|
||||
|
||||
// Delete
|
||||
assert_matches!(user_repo.delete_by_id(&new_user.id).await, Ok(()));
|
||||
assert_matches!(
|
||||
user_repo.delete_by_id(&new_user.id).await,
|
||||
Ok(())
|
||||
);
|
||||
assert_matches!(
|
||||
user_repo.get_by_id("ffffffff-0000-4000-0000-0000000000c9".into()).await,
|
||||
user_repo
|
||||
.get_by_id("ffffffff-0000-4000-0000-0000000000c9".into())
|
||||
.await,
|
||||
Err(sqlx::Error::RowNotFound)
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{DeriveInput, Fields, parse_macro_input};
|
||||
use syn::{parse_macro_input, DeriveInput, Fields};
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn sql_generator_model(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
|
|
@ -38,4 +38,3 @@ pub fn derive_sql_generator_model_with_id(input: TokenStream) -> TokenStream {
|
|||
// If `id` field is not found, return an error
|
||||
panic!("Expected struct with a named field `id` of type String")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
use anyhow::{Result, anyhow};
|
||||
use anyhow::{anyhow, Result};
|
||||
|
||||
use crate::models::{Field, Model};
|
||||
|
||||
|
||||
// Implementations
|
||||
impl Field {
|
||||
/// return sqlite type
|
||||
|
|
@ -21,7 +20,7 @@ impl Field {
|
|||
"DateTime" => Some("DATETIME".into()),
|
||||
"Json" => Some("TEXT".into()),
|
||||
"Vec<u8>" => Some("BLOB".into()),
|
||||
_ => Some("TEXT".into())
|
||||
_ => Some("TEXT".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -35,8 +34,10 @@ pub fn generate_create_table_sql(models: &[Model]) -> Result<String> {
|
|||
let mut fields_sql: Vec<String> = vec![];
|
||||
for field in model.fields.iter() {
|
||||
let mut additions: String = "".into();
|
||||
let sql_type = field.sql_type()
|
||||
.ok_or(anyhow!(format!("Could not find SQL type for field {}", field.name)))?;
|
||||
let sql_type = field.sql_type().ok_or(anyhow!(format!(
|
||||
"Could not find SQL type for field {}",
|
||||
field.name
|
||||
)))?;
|
||||
if !field.is_nullable {
|
||||
additions.push_str(" NOT NULL");
|
||||
}
|
||||
|
|
@ -46,20 +47,15 @@ pub fn generate_create_table_sql(models: &[Model]) -> Result<String> {
|
|||
if field.is_primary {
|
||||
additions.push_str(" PRIMARY KEY");
|
||||
}
|
||||
fields_sql.push(
|
||||
format!("\t{: <#18}\t{}{}", field.name, sql_type, additions)
|
||||
);
|
||||
fields_sql.push(format!("\t{: <#18}\t{}{}", field.name, sql_type, additions));
|
||||
}
|
||||
|
||||
sql_code.push_str(
|
||||
&format!(
|
||||
"CREATE TABLE {} (\n{}\n);\n",
|
||||
model.table_name,
|
||||
fields_sql.join(",\n")
|
||||
)
|
||||
);
|
||||
sql_code.push_str(&format!(
|
||||
"CREATE TABLE {} (\n{}\n);\n",
|
||||
model.table_name,
|
||||
fields_sql.join(",\n")
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
Ok(sql_code)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,13 +8,12 @@ pub mod repositories;
|
|||
#[fully_pub]
|
||||
enum SourceNode {
|
||||
File(String),
|
||||
Directory(Vec<SourceNodeContainer>)
|
||||
Directory(Vec<SourceNodeContainer>),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[fully_pub]
|
||||
struct SourceNodeContainer {
|
||||
name: String,
|
||||
inner: SourceNode
|
||||
inner: SourceNode,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
use anyhow::Result;
|
||||
use proc_macro2::{TokenStream, Ident};
|
||||
use heck::ToSnakeCase;
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
use syn::File;
|
||||
use heck::ToSnakeCase;
|
||||
|
||||
use crate::{generators::repositories::relations::gen_get_many_of_related_entity_method, models::{Field, FieldForeignMode, Model}};
|
||||
use crate::generators::{SourceNode, SourceNodeContainer};
|
||||
|
||||
use crate::{
|
||||
generators::repositories::relations::gen_get_many_of_related_entity_method,
|
||||
models::{Field, FieldForeignMode, Model},
|
||||
};
|
||||
|
||||
fn gen_get_all_method(model: &Model) -> TokenStream {
|
||||
let resource_ident = format_ident!("{}", &model.name);
|
||||
|
|
@ -23,7 +25,10 @@ fn gen_get_all_method(model: &Model) -> TokenStream {
|
|||
|
||||
fn gen_get_by_field_method(model: &Model, query_field: &Field) -> TokenStream {
|
||||
let resource_ident = format_ident!("{}", &model.name);
|
||||
let select_query = format!("SELECT * FROM {} WHERE {} = $1", model.table_name, query_field.name);
|
||||
let select_query = format!(
|
||||
"SELECT * FROM {} WHERE {} = $1",
|
||||
model.table_name, query_field.name
|
||||
);
|
||||
|
||||
let func_name_ident = format_ident!("get_by_{}", query_field.name);
|
||||
|
||||
|
|
@ -40,7 +45,10 @@ fn gen_get_by_field_method(model: &Model, query_field: &Field) -> TokenStream {
|
|||
|
||||
fn gen_get_many_by_field_method(model: &Model, query_field: &Field) -> TokenStream {
|
||||
let resource_ident = format_ident!("{}", &model.name);
|
||||
let select_query_tmpl = format!("SELECT * FROM {} WHERE {} IN ({{}})", model.table_name, query_field.name);
|
||||
let select_query_tmpl = format!(
|
||||
"SELECT * FROM {} WHERE {} IN ({{}})",
|
||||
model.table_name, query_field.name
|
||||
);
|
||||
|
||||
let func_name_ident = format_ident!("get_many_by_{}", query_field.name);
|
||||
|
||||
|
|
@ -66,21 +74,41 @@ fn gen_get_many_by_field_method(model: &Model, query_field: &Field) -> TokenStre
|
|||
}
|
||||
|
||||
fn get_mutation_fields(model: &Model) -> (Vec<&Field>, Vec<&Field>) {
|
||||
let normal_field_names: Vec<&Field> = model.fields.iter()
|
||||
.filter(|f| match f.foreign_mode { FieldForeignMode::NotRef => true, FieldForeignMode::ForeignRef(_) => false })
|
||||
let normal_field_names: Vec<&Field> = model
|
||||
.fields
|
||||
.iter()
|
||||
.filter(|f| match f.foreign_mode {
|
||||
FieldForeignMode::NotRef => true,
|
||||
FieldForeignMode::ForeignRef(_) => false,
|
||||
})
|
||||
.collect();
|
||||
let foreign_keys_field_names: Vec<&Field> = model.fields.iter()
|
||||
.filter(|f| match f.foreign_mode { FieldForeignMode::NotRef => false, FieldForeignMode::ForeignRef(_) => true })
|
||||
let foreign_keys_field_names: Vec<&Field> = model
|
||||
.fields
|
||||
.iter()
|
||||
.filter(|f| match f.foreign_mode {
|
||||
FieldForeignMode::NotRef => false,
|
||||
FieldForeignMode::ForeignRef(_) => true,
|
||||
})
|
||||
.collect();
|
||||
(normal_field_names, foreign_keys_field_names)
|
||||
}
|
||||
|
||||
fn get_mutation_fields_ident(model: &Model) -> (Vec<&Field>, Vec<&Field>) {
|
||||
let normal_field_names: Vec<&Field> = model.fields.iter()
|
||||
.filter(|f| match f.foreign_mode { FieldForeignMode::NotRef => true, FieldForeignMode::ForeignRef(_) => false })
|
||||
let normal_field_names: Vec<&Field> = model
|
||||
.fields
|
||||
.iter()
|
||||
.filter(|f| match f.foreign_mode {
|
||||
FieldForeignMode::NotRef => true,
|
||||
FieldForeignMode::ForeignRef(_) => false,
|
||||
})
|
||||
.collect();
|
||||
let foreign_keys_field_names: Vec<&Field> = model.fields.iter()
|
||||
.filter(|f| match f.foreign_mode { FieldForeignMode::NotRef => false, FieldForeignMode::ForeignRef(_) => true })
|
||||
let foreign_keys_field_names: Vec<&Field> = model
|
||||
.fields
|
||||
.iter()
|
||||
.filter(|f| match f.foreign_mode {
|
||||
FieldForeignMode::NotRef => false,
|
||||
FieldForeignMode::ForeignRef(_) => true,
|
||||
})
|
||||
.collect();
|
||||
(normal_field_names, foreign_keys_field_names)
|
||||
}
|
||||
|
|
@ -88,26 +116,31 @@ fn get_mutation_fields_ident(model: &Model) -> (Vec<&Field>, Vec<&Field>) {
|
|||
fn gen_insert_method(model: &Model) -> TokenStream {
|
||||
let resource_ident = format_ident!("{}", &model.name);
|
||||
|
||||
let value_templates = (1..(model.fields.len()+1))
|
||||
let value_templates = (1..(model.fields.len() + 1))
|
||||
.map(|i| format!("${}", i))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
let (normal_fields, foreign_keys_fields) = get_mutation_fields(model);
|
||||
let (normal_field_idents, foreign_keys_field_idents) = (
|
||||
normal_fields.iter().map(|f| format_ident!("{}", &f.name)).collect::<Vec<Ident>>(),
|
||||
foreign_keys_fields.iter().map(|f| format_ident!("{}", &f.name)).collect::<Vec<Ident>>()
|
||||
normal_fields
|
||||
.iter()
|
||||
.map(|f| format_ident!("{}", &f.name))
|
||||
.collect::<Vec<Ident>>(),
|
||||
foreign_keys_fields
|
||||
.iter()
|
||||
.map(|f| format_ident!("{}", &f.name))
|
||||
.collect::<Vec<Ident>>(),
|
||||
);
|
||||
|
||||
let sql_columns = [normal_fields, foreign_keys_fields].concat()
|
||||
let sql_columns = [normal_fields, foreign_keys_fields]
|
||||
.concat()
|
||||
.iter()
|
||||
.map(|f| f.name.clone())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
let insert_query = format!(
|
||||
"INSERT INTO {} ({}) VALUES ({})",
|
||||
model.table_name,
|
||||
sql_columns,
|
||||
value_templates
|
||||
model.table_name, sql_columns, value_templates
|
||||
);
|
||||
// foreign keys must be inserted first, we sort the columns so that foreign keys are first
|
||||
|
||||
|
|
@ -126,19 +159,26 @@ fn gen_insert_method(model: &Model) -> TokenStream {
|
|||
|
||||
fn gen_insert_many_method(model: &Model) -> TokenStream {
|
||||
let resource_ident = format_ident!("{}", &model.name);
|
||||
let sql_columns = model.fields.iter()
|
||||
let sql_columns = model
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f| f.name.clone())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
let base_insert_query = format!(
|
||||
"INSERT INTO {} ({}) VALUES {{}} ON CONFLICT DO NOTHING",
|
||||
model.table_name,
|
||||
sql_columns
|
||||
model.table_name, sql_columns
|
||||
);
|
||||
let (normal_fields, foreign_keys_fields) = get_mutation_fields(model);
|
||||
let (normal_field_idents, foreign_keys_field_idents) = (
|
||||
normal_fields.iter().map(|f| format_ident!("{}", &f.name)).collect::<Vec<Ident>>(),
|
||||
foreign_keys_fields.iter().map(|f| format_ident!("{}", &f.name)).collect::<Vec<Ident>>()
|
||||
normal_fields
|
||||
.iter()
|
||||
.map(|f| format_ident!("{}", &f.name))
|
||||
.collect::<Vec<Ident>>(),
|
||||
foreign_keys_fields
|
||||
.iter()
|
||||
.map(|f| format_ident!("{}", &f.name))
|
||||
.collect::<Vec<Ident>>(),
|
||||
);
|
||||
let fields_count = model.fields.len();
|
||||
|
||||
|
|
@ -174,32 +214,39 @@ fn gen_insert_many_method(model: &Model) -> TokenStream {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
fn gen_update_by_id_method(model: &Model) -> TokenStream {
|
||||
let resource_ident = format_ident!("{}", &model.name);
|
||||
let primary_key = &model.fields.iter()
|
||||
let primary_key = &model
|
||||
.fields
|
||||
.iter()
|
||||
.find(|f| f.is_primary)
|
||||
.expect("A model must have at least one primary key")
|
||||
.name;
|
||||
let (normal_fields, foreign_keys_fields) = get_mutation_fields(model);
|
||||
let (normal_field_idents, foreign_keys_field_idents) = (
|
||||
normal_fields.iter().map(|f| format_ident!("{}", &f.name)).collect::<Vec<Ident>>(),
|
||||
foreign_keys_fields.iter().map(|f| format_ident!("{}", &f.name)).collect::<Vec<Ident>>()
|
||||
normal_fields
|
||||
.iter()
|
||||
.map(|f| format_ident!("{}", &f.name))
|
||||
.collect::<Vec<Ident>>(),
|
||||
foreign_keys_fields
|
||||
.iter()
|
||||
.map(|f| format_ident!("{}", &f.name))
|
||||
.collect::<Vec<Ident>>(),
|
||||
);
|
||||
let sql_columns = [normal_fields, foreign_keys_fields].concat()
|
||||
let sql_columns = [normal_fields, foreign_keys_fields]
|
||||
.concat()
|
||||
.iter()
|
||||
.map(|f| f.name.clone())
|
||||
.collect::<Vec<String>>();
|
||||
let set_statements = sql_columns.iter()
|
||||
let set_statements = sql_columns
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, column_name)| format!("{} = ${}", column_name, i+2))
|
||||
.map(|(i, column_name)| format!("{} = ${}", column_name, i + 2))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
let update_query = format!(
|
||||
"UPDATE {} SET {} WHERE {} = $1",
|
||||
model.table_name,
|
||||
set_statements,
|
||||
primary_key
|
||||
model.table_name, set_statements, primary_key
|
||||
);
|
||||
let func_name_ident = format_ident!("update_by_{}", primary_key);
|
||||
|
||||
|
|
@ -218,7 +265,9 @@ fn gen_update_by_id_method(model: &Model) -> TokenStream {
|
|||
}
|
||||
|
||||
fn gen_delete_by_id_method(model: &Model) -> TokenStream {
|
||||
let primary_key = &model.fields.iter()
|
||||
let primary_key = &model
|
||||
.fields
|
||||
.iter()
|
||||
.find(|f| f.is_primary)
|
||||
.expect("A model must have at least one primary key")
|
||||
.name;
|
||||
|
|
@ -226,8 +275,7 @@ fn gen_delete_by_id_method(model: &Model) -> TokenStream {
|
|||
let func_name_ident = format_ident!("delete_by_{}", primary_key);
|
||||
let query = format!(
|
||||
"DELETE FROM {} WHERE {} = $1",
|
||||
model.table_name,
|
||||
primary_key
|
||||
model.table_name, primary_key
|
||||
);
|
||||
|
||||
quote! {
|
||||
|
|
@ -243,7 +291,9 @@ fn gen_delete_by_id_method(model: &Model) -> TokenStream {
|
|||
}
|
||||
|
||||
fn gen_delete_many_by_id_method(model: &Model) -> TokenStream {
|
||||
let primary_key = &model.fields.iter()
|
||||
let primary_key = &model
|
||||
.fields
|
||||
.iter()
|
||||
.find(|f| f.is_primary)
|
||||
.expect("A model must have at least one primary key")
|
||||
.name;
|
||||
|
|
@ -251,8 +301,7 @@ fn gen_delete_many_by_id_method(model: &Model) -> TokenStream {
|
|||
let func_name_ident = format_ident!("delete_many_by_{}", primary_key);
|
||||
let delete_query_tmpl = format!(
|
||||
"DELETE FROM {} WHERE {} IN ({{}})",
|
||||
model.table_name,
|
||||
primary_key
|
||||
model.table_name, primary_key
|
||||
);
|
||||
|
||||
quote! {
|
||||
|
|
@ -278,8 +327,10 @@ fn gen_delete_many_by_id_method(model: &Model) -> TokenStream {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn generate_repository_file(all_models: &[Model], model: &Model) -> Result<SourceNodeContainer> {
|
||||
pub fn generate_repository_file(
|
||||
all_models: &[Model],
|
||||
model: &Model,
|
||||
) -> Result<SourceNodeContainer> {
|
||||
let resource_name = model.name.clone();
|
||||
|
||||
let resource_module_ident = format_ident!("{}", &model.module_path.first().unwrap());
|
||||
|
|
@ -290,15 +341,19 @@ pub fn generate_repository_file(all_models: &[Model], model: &Model) -> Result<S
|
|||
let get_all_method_code = gen_get_all_method(model);
|
||||
let get_by_id_method_code = gen_get_by_field_method(
|
||||
model,
|
||||
model.fields.iter()
|
||||
model
|
||||
.fields
|
||||
.iter()
|
||||
.find(|f| f.is_primary == true)
|
||||
.expect("Expected at least one primary key on the model.")
|
||||
.expect("Expected at least one primary key on the model."),
|
||||
);
|
||||
let get_many_by_id_method_code = gen_get_many_by_field_method(
|
||||
model,
|
||||
model.fields.iter()
|
||||
model
|
||||
.fields
|
||||
.iter()
|
||||
.find(|f| f.is_primary == true)
|
||||
.expect("Expected at least one primary key on the model.")
|
||||
.expect("Expected at least one primary key on the model."),
|
||||
);
|
||||
let insert_method_code = gen_insert_method(model);
|
||||
let insert_many_method_code = gen_insert_many_method(model);
|
||||
|
|
@ -306,40 +361,37 @@ pub fn generate_repository_file(all_models: &[Model], model: &Model) -> Result<S
|
|||
let delete_by_id_method_code = gen_delete_by_id_method(model);
|
||||
let delete_many_by_id_method_code = gen_delete_many_by_id_method(model);
|
||||
|
||||
let query_by_field_methods: Vec<TokenStream> = model
|
||||
.fields
|
||||
.iter()
|
||||
.filter(|f| f.is_query_entrypoint)
|
||||
.map(|field| gen_get_by_field_method(model, &field))
|
||||
.collect();
|
||||
let query_many_by_field_methods: Vec<TokenStream> = model
|
||||
.fields
|
||||
.iter()
|
||||
.filter(|f| f.is_query_entrypoint)
|
||||
.map(|field| gen_get_many_by_field_method(model, &field))
|
||||
.collect();
|
||||
|
||||
let query_by_field_methods: Vec<TokenStream> =
|
||||
model.fields.iter()
|
||||
.filter(|f| f.is_query_entrypoint)
|
||||
.map(|field|
|
||||
gen_get_by_field_method(
|
||||
model,
|
||||
&field
|
||||
)
|
||||
)
|
||||
.collect();
|
||||
let query_many_by_field_methods: Vec<TokenStream> =
|
||||
model.fields.iter()
|
||||
.filter(|f| f.is_query_entrypoint)
|
||||
.map(|field|
|
||||
gen_get_many_by_field_method(
|
||||
model,
|
||||
&field
|
||||
)
|
||||
)
|
||||
.collect();
|
||||
|
||||
let fields_with_foreign_refs: Vec<&Field> = model.fields.iter().filter(|f|
|
||||
match f.foreign_mode { FieldForeignMode::ForeignRef(_) => true, FieldForeignMode::NotRef => false }
|
||||
).collect();
|
||||
let related_entity_methods_codes: Vec<TokenStream> = fields_with_foreign_refs.iter().map(|field|
|
||||
gen_get_many_of_related_entity_method(model, &field)
|
||||
).collect();
|
||||
let fields_with_foreign_refs: Vec<&Field> = model
|
||||
.fields
|
||||
.iter()
|
||||
.filter(|f| match f.foreign_mode {
|
||||
FieldForeignMode::ForeignRef(_) => true,
|
||||
FieldForeignMode::NotRef => false,
|
||||
})
|
||||
.collect();
|
||||
let related_entity_methods_codes: Vec<TokenStream> = fields_with_foreign_refs
|
||||
.iter()
|
||||
.map(|field| gen_get_many_of_related_entity_method(model, &field))
|
||||
.collect();
|
||||
|
||||
// TODO: add import line
|
||||
let base_repository_code: TokenStream = quote! {
|
||||
use crate::models::#resource_module_ident::#resource_ident;
|
||||
use crate::db::Database;
|
||||
|
||||
|
||||
pub struct #repository_ident {
|
||||
db: Database
|
||||
}
|
||||
|
|
@ -370,7 +422,7 @@ pub fn generate_repository_file(all_models: &[Model], model: &Model) -> Result<S
|
|||
#(#query_by_field_methods)*
|
||||
|
||||
#(#query_many_by_field_methods)*
|
||||
|
||||
|
||||
#(#related_entity_methods_codes)*
|
||||
}
|
||||
};
|
||||
|
|
@ -380,6 +432,6 @@ pub fn generate_repository_file(all_models: &[Model], model: &Model) -> Result<S
|
|||
|
||||
Ok(SourceNodeContainer {
|
||||
name: format!("{}_repository.rs", model.name.to_snake_case()),
|
||||
inner: SourceNode::File(pretty)
|
||||
inner: SourceNode::File(pretty),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ pub fn generate_repositories_source_files(models: &[Model]) -> Result<SourceNode
|
|||
nodes.push(base::generate_repository_file(models, model)?);
|
||||
// nodes.push(relations::generate_repository_file(model)?);
|
||||
}
|
||||
|
||||
|
||||
let mut mod_index_code: String = String::new();
|
||||
for node in &nodes {
|
||||
let module_name = node.name.replace(".rs", "");
|
||||
|
|
@ -21,11 +21,10 @@ pub fn generate_repositories_source_files(models: &[Model]) -> Result<SourceNode
|
|||
}
|
||||
nodes.push(SourceNodeContainer {
|
||||
name: "mod.rs".into(),
|
||||
inner: SourceNode::File(mod_index_code.to_string())
|
||||
inner: SourceNode::File(mod_index_code.to_string()),
|
||||
});
|
||||
Ok(SourceNodeContainer {
|
||||
name: "".into(),
|
||||
inner: SourceNode::Directory(nodes)
|
||||
inner: SourceNode::Directory(nodes),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,17 +5,23 @@ use crate::models::{Field, FieldForeignMode, Model};
|
|||
|
||||
/// method that can be used to retreive a list of entities of type X that are the children of a parent type Y
|
||||
/// ex: get all comments of a post
|
||||
pub fn gen_get_many_of_related_entity_method(model: &Model, foreign_key_field: &Field) -> TokenStream {
|
||||
pub fn gen_get_many_of_related_entity_method(
|
||||
model: &Model,
|
||||
foreign_key_field: &Field,
|
||||
) -> TokenStream {
|
||||
let resource_ident = format_ident!("{}", &model.name);
|
||||
|
||||
let foreign_ref_params = match &foreign_key_field.foreign_mode {
|
||||
FieldForeignMode::ForeignRef(params) => params,
|
||||
FieldForeignMode::NotRef => {
|
||||
panic!("Expected foreign key");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let select_query = format!("SELECT * FROM {} WHERE {} = $1", model.table_name, foreign_key_field.name);
|
||||
let select_query = format!(
|
||||
"SELECT * FROM {} WHERE {} = $1",
|
||||
model.table_name, foreign_key_field.name
|
||||
);
|
||||
|
||||
let func_name_ident = format_ident!("get_many_of_{}", foreign_ref_params.target_resource_name);
|
||||
|
||||
|
|
@ -28,4 +34,3 @@ pub fn gen_get_many_of_related_entity_method(model: &Model, foreign_key_field: &
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,19 @@
|
|||
use std::{ffi::OsStr, path::Path};
|
||||
use attribute_derive::FromAttr;
|
||||
use std::{ffi::OsStr, path::Path};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use argh::FromArgs;
|
||||
use anyhow::{Result, anyhow};
|
||||
|
||||
use crate::generators::{SourceNode, SourceNodeContainer};
|
||||
|
||||
// use gen_migrations::generate_create_table_sql;
|
||||
// use gen_repositories::{generate_repositories_source_files, SourceNodeContainer};
|
||||
|
||||
pub mod generators;
|
||||
pub mod models;
|
||||
pub mod parse_models;
|
||||
pub mod generators;
|
||||
|
||||
#[derive(FromAttr, PartialEq, Debug, Default)]
|
||||
#[attribute(ident = sql_generator_model)]
|
||||
pub struct SqlGeneratorModelAttr {
|
||||
table_name: Option<String>
|
||||
table_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(FromAttr, PartialEq, Debug, Default)]
|
||||
|
|
@ -25,20 +22,19 @@ pub struct SqlGeneratorFieldAttr {
|
|||
is_primary: Option<bool>,
|
||||
is_unique: Option<bool>,
|
||||
reverse_relation_name: Option<String>,
|
||||
|
||||
|
||||
/// to indicate that this field will be used to obtains entities
|
||||
/// our framework will generate methods for all fields that is an entrypoint
|
||||
is_query_entrypoint: Option<bool>
|
||||
is_query_entrypoint: Option<bool>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
/// Generate SQL CREATE TABLE migrations
|
||||
#[argh(subcommand, name = "gen-migrations")]
|
||||
struct GenerateMigration {
|
||||
/// path of file where to write all in one generated SQL migration
|
||||
#[argh(option, short = 'o')]
|
||||
output: Option<String>
|
||||
output: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
|
|
@ -47,7 +43,7 @@ struct GenerateMigration {
|
|||
struct GenerateRepositories {
|
||||
/// path of the directory that contains repositories
|
||||
#[argh(option, short = 'o')]
|
||||
output: Option<String>
|
||||
output: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
|
|
@ -67,9 +63,9 @@ struct GeneratorArgs {
|
|||
/// path of the directory containing models
|
||||
#[argh(option, short = 'm')]
|
||||
models_path: Option<String>,
|
||||
|
||||
|
||||
#[argh(subcommand)]
|
||||
nested: GeneratorArgsSubCommands
|
||||
nested: GeneratorArgsSubCommands,
|
||||
}
|
||||
|
||||
fn write_source_code(base_path: &Path, snc: SourceNodeContainer) -> Result<()> {
|
||||
|
|
@ -78,7 +74,7 @@ fn write_source_code(base_path: &Path, snc: SourceNodeContainer) -> Result<()> {
|
|||
SourceNode::File(code) => {
|
||||
println!("writing file {:?}", path);
|
||||
std::fs::write(path, code)?;
|
||||
},
|
||||
}
|
||||
SourceNode::Directory(dir) => {
|
||||
for node in dir {
|
||||
write_source_code(&path, node)?;
|
||||
|
|
@ -92,11 +88,14 @@ pub fn main() -> Result<()> {
|
|||
let args: GeneratorArgs = argh::from_env();
|
||||
let project_root = &args.project_root.unwrap_or(".".to_string());
|
||||
let project_root_path = Path::new(&project_root);
|
||||
eprintln!("Using project root at: {:?}", &project_root_path.canonicalize()?);
|
||||
eprintln!(
|
||||
"Using project root at: {:?}",
|
||||
&project_root_path.canonicalize()?
|
||||
);
|
||||
if !project_root_path.exists() {
|
||||
return Err(anyhow!("Could not resolve project root path."));
|
||||
}
|
||||
|
||||
|
||||
// check Cargo.toml
|
||||
let main_manifest_location = "Cargo.toml";
|
||||
let main_manifest_path = project_root_path.join(main_manifest_location);
|
||||
|
|
@ -117,13 +116,17 @@ pub fn main() -> Result<()> {
|
|||
if !models_mod_path.exists() {
|
||||
return Err(anyhow!("Could not resolve models modules."));
|
||||
}
|
||||
if models_mod_path.file_name().map(|x| x == OsStr::new("mod.rs")).unwrap_or(false) {
|
||||
if models_mod_path
|
||||
.file_name()
|
||||
.map(|x| x == OsStr::new("mod.rs"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
models_mod_path.pop();
|
||||
}
|
||||
eprintln!("Found models in project, parsing models");
|
||||
let models = parse_models::parse_models_from_module(&models_mod_path)?;
|
||||
dbg!(&models);
|
||||
|
||||
|
||||
match args.nested {
|
||||
GeneratorArgsSubCommands::GenerateRepositories(opts) => {
|
||||
eprintln!("Generating repositories…");
|
||||
|
|
@ -136,7 +139,7 @@ pub fn main() -> Result<()> {
|
|||
let snc = generators::repositories::generate_repositories_source_files(&models)?;
|
||||
dbg!(&snc);
|
||||
write_source_code(&repositories_mod_path, snc)?;
|
||||
},
|
||||
}
|
||||
GeneratorArgsSubCommands::GenerateMigration(opts) => {
|
||||
eprintln!("Generating migrations…");
|
||||
let sql_code = generators::migrations::generate_create_table_sql(&models)?;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ struct Model {
|
|||
module_path: Vec<String>,
|
||||
name: String,
|
||||
table_name: String,
|
||||
fields: Vec<Field>
|
||||
fields: Vec<Field>,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
|
|
@ -29,7 +29,6 @@ impl Model {
|
|||
// }
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[fully_pub]
|
||||
struct ForeignRefParams {
|
||||
|
|
@ -41,12 +40,11 @@ struct ForeignRefParams {
|
|||
// target_resource_name_plural: String
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[fully_pub]
|
||||
enum FieldForeignMode {
|
||||
ForeignRef(ForeignRefParams),
|
||||
NotRef
|
||||
NotRef,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -58,6 +56,5 @@ struct Field {
|
|||
is_unique: bool,
|
||||
is_primary: bool,
|
||||
is_query_entrypoint: bool,
|
||||
foreign_mode: FieldForeignMode
|
||||
foreign_mode: FieldForeignMode,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
use std::{fs, path::Path};
|
||||
use attribute_derive::FromAttr;
|
||||
use std::{fs, path::Path};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use anyhow::{anyhow, Result};
|
||||
use convert_case::{Case, Casing};
|
||||
use syn::{GenericArgument, PathArguments, Type};
|
||||
|
||||
use crate::{SqlGeneratorFieldAttr, SqlGeneratorModelAttr, models::{Field, FieldForeignMode, ForeignRefParams, Model}};
|
||||
use crate::{
|
||||
models::{Field, FieldForeignMode, ForeignRefParams, Model},
|
||||
SqlGeneratorFieldAttr, SqlGeneratorModelAttr,
|
||||
};
|
||||
|
||||
fn extract_generic_type(base_segments: Vec<String>, ty: &Type) -> Option<&Type> {
|
||||
// If it is not `TypePath`, it is not possible to be `Option<T>`, return `None`
|
||||
|
|
@ -52,24 +55,38 @@ fn extract_generic_type(base_segments: Vec<String>, ty: &Type) -> Option<&Type>
|
|||
|
||||
fn get_type_first_ident(inp: &Type) -> Option<String> {
|
||||
match inp {
|
||||
Type::Path(field_type_path) => {
|
||||
Some(field_type_path.path.segments.get(0).unwrap().ident.to_string())
|
||||
},
|
||||
_ => {
|
||||
None
|
||||
}
|
||||
Type::Path(field_type_path) => Some(
|
||||
field_type_path
|
||||
.path
|
||||
.segments
|
||||
.get(0)
|
||||
.unwrap()
|
||||
.ident
|
||||
.to_string(),
|
||||
),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_first_generic_arg_type_ident(inp: &Type) -> Option<String> {
|
||||
if let Type::Path(field_type_path) = inp {
|
||||
if let PathArguments::AngleBracketed(args) = &field_type_path.path.segments.get(0).unwrap().arguments {
|
||||
if let PathArguments::AngleBracketed(args) =
|
||||
&field_type_path.path.segments.get(0).unwrap().arguments
|
||||
{
|
||||
if args.args.is_empty() {
|
||||
None
|
||||
} else {
|
||||
if let GenericArgument::Type(arg_type) = args.args.get(0).unwrap() {
|
||||
if let Type::Path(arg_type_path) = arg_type {
|
||||
Some(arg_type_path.path.segments.get(0).unwrap().ident.to_string())
|
||||
Some(
|
||||
arg_type_path
|
||||
.path
|
||||
.segments
|
||||
.get(0)
|
||||
.unwrap()
|
||||
.ident
|
||||
.to_string(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
@ -85,7 +102,6 @@ fn get_first_generic_arg_type_ident(inp: &Type) -> Option<String> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
fn parse_model_attribute(item: &syn::ItemStruct) -> Result<Option<SqlGeneratorModelAttr>> {
|
||||
for attr in item.attrs.iter() {
|
||||
let attr_ident = match attr.path().get_ident() {
|
||||
|
|
@ -101,9 +117,12 @@ fn parse_model_attribute(item: &syn::ItemStruct) -> Result<Option<SqlGeneratorMo
|
|||
match SqlGeneratorModelAttr::from_attribute(attr) {
|
||||
Ok(v) => {
|
||||
return Ok(Some(v));
|
||||
},
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(anyhow!("Failed to parse sql_generator_model attribute macro: {}", err));
|
||||
return Err(anyhow!(
|
||||
"Failed to parse sql_generator_model attribute macro: {}",
|
||||
err
|
||||
));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -125,9 +144,13 @@ fn parse_field_attribute(field: &syn::Field) -> Result<Option<SqlGeneratorFieldA
|
|||
match SqlGeneratorFieldAttr::from_attribute(attr) {
|
||||
Ok(v) => {
|
||||
return Ok(Some(v));
|
||||
},
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(anyhow!("Failed to parse sql_generator_field attribute macro on field {:?}, {}", field, err));
|
||||
return Err(anyhow!(
|
||||
"Failed to parse sql_generator_field attribute macro on field {:?}, {}",
|
||||
field,
|
||||
err
|
||||
));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -136,10 +159,7 @@ fn parse_field_attribute(field: &syn::Field) -> Result<Option<SqlGeneratorFieldA
|
|||
|
||||
/// Take struct name as source, apply snake case and pluralize with a s
|
||||
fn generate_table_name_from_struct_name(struct_name: &str) -> String {
|
||||
format!(
|
||||
"{}s",
|
||||
struct_name.to_case(Case::Snake)
|
||||
)
|
||||
format!("{}s", struct_name.to_case(Case::Snake))
|
||||
}
|
||||
|
||||
/// Scan for models struct in a rust file and return a struct representing the model
|
||||
|
|
@ -166,7 +186,7 @@ pub fn parse_models(source_code_path: &Path) -> Result<Vec<Model>> {
|
|||
let field_name = field.ident.clone().unwrap().to_string();
|
||||
let field_type = field.ty.clone();
|
||||
println!("field {} {:?}", field_name, field_type);
|
||||
|
||||
|
||||
let mut output_field = Field {
|
||||
name: field_name,
|
||||
rust_type: "Unknown".into(),
|
||||
|
|
@ -174,7 +194,7 @@ pub fn parse_models(source_code_path: &Path) -> Result<Vec<Model>> {
|
|||
is_primary: false,
|
||||
is_unique: false,
|
||||
is_query_entrypoint: false,
|
||||
foreign_mode: FieldForeignMode::NotRef
|
||||
foreign_mode: FieldForeignMode::NotRef,
|
||||
};
|
||||
|
||||
let first_type: String = match get_type_first_ident(&field_type) {
|
||||
|
|
@ -187,8 +207,12 @@ pub fn parse_models(source_code_path: &Path) -> Result<Vec<Model>> {
|
|||
if first_type == "Option" {
|
||||
output_field.is_nullable = true;
|
||||
let inner_type = match extract_generic_type(
|
||||
vec!["Option".into(), "std:option:Option".into(), "core:option:Option".into()],
|
||||
&field_type
|
||||
vec![
|
||||
"Option".into(),
|
||||
"std:option:Option".into(),
|
||||
"core:option:Option".into(),
|
||||
],
|
||||
&field_type,
|
||||
) {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
|
|
@ -198,15 +222,15 @@ pub fn parse_models(source_code_path: &Path) -> Result<Vec<Model>> {
|
|||
final_type = match get_type_first_ident(inner_type) {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
return Err(anyhow!("Could not extract ident from Option inner type"));
|
||||
return Err(anyhow!(
|
||||
"Could not extract ident from Option inner type"
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
if first_type == "Vec" {
|
||||
let inner_type = match extract_generic_type(
|
||||
vec!["Vec".into()],
|
||||
&field_type
|
||||
) {
|
||||
let inner_type = match extract_generic_type(vec!["Vec".into()], &field_type)
|
||||
{
|
||||
Some(v) => v,
|
||||
None => {
|
||||
return Err(anyhow!("Could not extract type from Vec"));
|
||||
|
|
@ -221,13 +245,14 @@ pub fn parse_models(source_code_path: &Path) -> Result<Vec<Model>> {
|
|||
}
|
||||
output_field.rust_type = final_type;
|
||||
|
||||
|
||||
let field_attrs_opt = parse_field_attribute(field)?;
|
||||
if first_type == "ForeignRef" {
|
||||
let attrs = match &field_attrs_opt {
|
||||
Some(attrs) => attrs,
|
||||
None => {
|
||||
return Err(anyhow!("Found a ForeignRef type but did not found attributes."))
|
||||
return Err(anyhow!(
|
||||
"Found a ForeignRef type but did not found attributes."
|
||||
))
|
||||
}
|
||||
};
|
||||
let rrn = match &attrs.reverse_relation_name {
|
||||
|
|
@ -237,39 +262,48 @@ pub fn parse_models(source_code_path: &Path) -> Result<Vec<Model>> {
|
|||
}
|
||||
};
|
||||
|
||||
let extract_res = extract_generic_type(vec!["ForeignRef".into()], &field_type)
|
||||
.and_then(|t| get_type_first_ident(t));
|
||||
let extract_res =
|
||||
extract_generic_type(vec!["ForeignRef".into()], &field_type)
|
||||
.and_then(|t| get_type_first_ident(t));
|
||||
let target_type_name = match extract_res {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
return Err(anyhow!("Could not extract inner type from ForeignRef."));
|
||||
return Err(anyhow!(
|
||||
"Could not extract inner type from ForeignRef."
|
||||
));
|
||||
}
|
||||
};
|
||||
output_field.foreign_mode = FieldForeignMode::ForeignRef(
|
||||
ForeignRefParams {
|
||||
output_field.foreign_mode =
|
||||
FieldForeignMode::ForeignRef(ForeignRefParams {
|
||||
reverse_relation_name: rrn,
|
||||
target_resource_name: target_type_name.to_case(Case::Snake)
|
||||
}
|
||||
);
|
||||
target_resource_name: target_type_name.to_case(Case::Snake),
|
||||
});
|
||||
}
|
||||
|
||||
// parse attribute
|
||||
if let Some(field_attr) = field_attrs_opt {
|
||||
output_field.is_primary = field_attr.is_primary.unwrap_or_default();
|
||||
output_field.is_unique = field_attr.is_unique.unwrap_or_default();
|
||||
output_field.is_query_entrypoint = field_attr.is_query_entrypoint.unwrap_or_default();
|
||||
output_field.is_query_entrypoint =
|
||||
field_attr.is_query_entrypoint.unwrap_or_default();
|
||||
}
|
||||
|
||||
fields.push(output_field);
|
||||
}
|
||||
models.push(Model {
|
||||
module_path: vec![source_code_path.file_stem().unwrap().to_str().unwrap().to_string()],
|
||||
module_path: vec![source_code_path
|
||||
.file_stem()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string()],
|
||||
name: model_name.clone(),
|
||||
table_name: model_attrs.table_name
|
||||
table_name: model_attrs
|
||||
.table_name
|
||||
.unwrap_or(generate_table_name_from_struct_name(&model_name)),
|
||||
fields
|
||||
fields,
|
||||
})
|
||||
},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
@ -304,7 +338,7 @@ fn parse_models_from_module_inner(module_path: &Path) -> Result<Vec<Model>> {
|
|||
// match original_field.foreign_mode {
|
||||
// FieldForeignMode::NotRef => {},
|
||||
// FieldForeignMode::ForeignRef(ref_params) => {
|
||||
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
|
|
|
|||
|
|
@ -12,23 +12,20 @@ use sqlx_core::error::BoxDynError;
|
|||
use sqlx_core::types::Type;
|
||||
use sqlx_sqlite::{Sqlite, SqliteArgumentValue};
|
||||
|
||||
|
||||
#[fully_pub]
|
||||
trait DatabaseLine {
|
||||
fn id(&self) -> String;
|
||||
}
|
||||
|
||||
|
||||
/// Wrapper to mark a model field as foreign
|
||||
/// You can use a generic argument inside ForeignRef to point to the target model
|
||||
#[derive(Clone, Debug)]
|
||||
#[fully_pub]
|
||||
struct ForeignRef<T: Sized + DatabaseLine> {
|
||||
pub target_type: PhantomData<T>,
|
||||
pub target_id: String
|
||||
pub target_id: String,
|
||||
}
|
||||
|
||||
|
||||
// Implement serde Serialize for ForeignRef
|
||||
impl<T: Sized + DatabaseLine> Serialize for ForeignRef<T> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
|
|
@ -40,22 +37,20 @@ impl<T: Sized + DatabaseLine> Serialize for ForeignRef<T> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
impl<T: Sized + DatabaseLine> ForeignRef<T> {
|
||||
pub fn new(entity: &T) -> ForeignRef<T> {
|
||||
pub fn new(entity: &T) -> ForeignRef<T> {
|
||||
ForeignRef {
|
||||
target_type: PhantomData,
|
||||
target_id: entity.id()
|
||||
target_id: entity.id(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<'r, DB: Database, T: Sized + DatabaseLine> Decode<'r, DB> for ForeignRef<T>
|
||||
where
|
||||
// we want to delegate some of the work to string decoding so let's make sure strings
|
||||
// are supported by the database
|
||||
&'r str: Decode<'r, DB>
|
||||
&'r str: Decode<'r, DB>,
|
||||
{
|
||||
fn decode(
|
||||
value: <DB as Database>::ValueRef<'r>,
|
||||
|
|
@ -66,7 +61,7 @@ where
|
|||
|
||||
Ok(ForeignRef::<T> {
|
||||
target_type: PhantomData,
|
||||
target_id: ref_val
|
||||
target_id: ref_val,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -84,9 +79,11 @@ impl<T: DatabaseLine + Sized> Type<Sqlite> for ForeignRef<T> {
|
|||
}
|
||||
|
||||
impl<T: DatabaseLine + Sized> Encode<'_, Sqlite> for ForeignRef<T> {
|
||||
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
|
||||
fn encode_by_ref(
|
||||
&self,
|
||||
args: &mut Vec<SqliteArgumentValue<'_>>,
|
||||
) -> Result<IsNull, BoxDynError> {
|
||||
args.push(SqliteArgumentValue::Text(self.target_id.clone().into()));
|
||||
Ok(IsNull::No)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue