From 430a6e1f094c7e98f9f1be3546014ed0d8dcb1cc Mon Sep 17 00:00:00 2001 From: longfangsong Date: Sat, 16 Oct 2021 15:23:23 +0800 Subject: [PATCH 1/2] initial commit --- Cargo.toml | 2 + docs/config.tosd | 99 ++++++++++++++++ example-configs/course.toml | 18 +++ src/config/cli.rs | 41 +++++++ src/config/file.rs | 86 ++++++++++++++ src/config/mod.rs | 110 ++++++++++++++++++ .../database/mod.rs | 0 .../database/postgres.rs | 0 .../framework}/golang.rs | 14 +-- src/implementation/framework/mod.rs | 3 + src/{technology => implementation}/mod.rs | 6 +- src/main.rs | 33 +++--- src/model/data_type.rs | 58 +++++++++ src/technology/language/mod.rs | 4 - 14 files changed, 442 insertions(+), 32 deletions(-) create mode 100644 docs/config.tosd create mode 100644 example-configs/course.toml create mode 100644 src/config/cli.rs create mode 100644 src/config/file.rs create mode 100644 src/config/mod.rs rename src/{technology => implementation}/database/mod.rs (100%) rename src/{technology => implementation}/database/postgres.rs (100%) rename src/{technology/language => implementation/framework}/golang.rs (89%) create mode 100644 src/implementation/framework/mod.rs rename src/{technology => implementation}/mod.rs (68%) delete mode 100644 src/technology/language/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 73b42fd..efeb4b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ serde = {version="1.0.126", features = ["derive"]} serde_yaml = "0.8.17" serde_json = "1.0.64" toml = "0.5.8" +anyhow = "1.0.41" +structopt = "0.3.21" [profile.release] lto = "fat" diff --git a/docs/config.tosd b/docs/config.tosd new file mode 100644 index 0000000..b436da9 --- /dev/null +++ b/docs/config.tosd @@ -0,0 +1,99 @@ +[toml-schema] +version = "1.0" + +[types] +[types.field] +[types.field.name] +type = "string" +[types.field.type] +type = "string" +allowedvalues = [ + "int", + "serial", + "integer", + "small_int", + "tiny_int", + "medium_int", + "big_int", + "big_serial", + "unsigned_serial", + "uint", + "unsigned_tiny_int", + "unsigned_tiny_integer", + "unsigned", + "unsigned_medium_int", + "unsigned_medium_integer", + "unsigned_big_int", + "unsigned_big_integer", + "float", + "float32", + "double", + "float64", + "string", + "char(\\d+)", + "varchar(\\d+)", + "tiny_text", + "text", + "medium_text", + "long_text", + "tiny_blob", + "blob", + "medium_blob", + "long_blob", + "bool", + "date", + "date_time", + "time", + "timestamp", +] + +[elements] +[elements.api] +type = "table" +optional = true +[elements.api.name] +type = "string" +[elements.api.fields] +type = "array" +arraytype = "string" +[elements.api.fields.elements] +type = "array" +arraytypeof = "field" +[elements.implementation] +type = "table" +[elements.implementation.framework] +type = "string" +allowedvalues = ["golang"] +optional = true +default = "golang" +[elements.implementation.database] +type = "table" +[elements.implementation.database.engine] +type = "string" +allowedvalues = ["postgres", "pgsql", "postgresql"] +optional = true +default = "pgsql" +[elements.implementation.database.url] +type = "string" +optional = true +[elements.cicd] +[elements.cicd.docker] +type = "table" +optional = true +[elements.cicd.docker.generate_file] +type = "bool" +default = true +[elements.cicd.docker.username] +type = string +optional = true +[elements.cicd.docker.tag] +type = string +optional = true +[elements.cicd.k8s] +type = "bool" +optional = true +default = true +[elements.cicd.github_actions] +type = "bool" +optional = true +default = true diff --git a/example-configs/course.toml b/example-configs/course.toml new file mode 100644 index 0000000..5b28951 --- /dev/null +++ b/example-configs/course.toml @@ -0,0 +1,18 @@ +[api] +name = "course" +[[api.fields]] +name = "name" +type = "string" +[[api.fields]] +name = "start" +type = "tinyint" +[[api.fields]] +name = "end" +type = "tinyint" + +[implementation] +framework = "golang" + +[implemention.database] +engine = "pgsql" +url = "postgresql://localhost:5432/postgres" diff --git a/src/config/cli.rs b/src/config/cli.rs new file mode 100644 index 0000000..e9eeeb8 --- /dev/null +++ b/src/config/cli.rs @@ -0,0 +1,41 @@ +use std::path::PathBuf; +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +#[structopt( + name = "autoapi", + about = "A tool for generating CRUD API program automatically.", + rename_all = "kebab" +)] +pub struct Config { + /// Config file path. + #[structopt(short="i", long, alias="input", parse(from_os_str), env = "CONFIG")] + pub config: PathBuf, + /// Output project path. + #[structopt(short, long, parse(from_os_str), env = "OUTPUT")] + pub output: PathBuf, + /// Set this flag to overwrite the `output` directory before generating instead of report an error. + #[structopt(short, long, env = "FORCE")] + pub force: bool, + + /// Output project path. + #[structopt(alias="dbms", long, env = "DATABASE")] + pub database_engine: Option, + #[structopt(short, long, env = "API_NAME")] + pub name: Option, + + #[structopt(alias="ddl", long, env = "DDL")] + pub load_from_ddl: Option, + #[structopt(short="load-db", long, env = "LOAD_DB")] + pub load_from_db: Option, + + #[structopt(short="d", long, env = "DOCKER")] + pub generate_docker: Option, + #[structopt(alias="du", long, env = "DOCKER_USERNAME")] + pub docker_username: Option, + #[structopt(alias="dt", long, env = "DOCKER_TAG")] + pub docker_tag: Option, + + #[structopt(short="k8s", long, env = "KUBERNETES")] + pub kubernetes: Option, +} diff --git a/src/config/file.rs b/src/config/file.rs new file mode 100644 index 0000000..e00d126 --- /dev/null +++ b/src/config/file.rs @@ -0,0 +1,86 @@ +use std::fs::OpenOptions; + +use anyhow::{anyhow, Result}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Field { + pub name: String, + #[serde(rename = "type")] + pub data_type: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct API { + pub name: String, + pub fields: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct DataBase { + pub engine: Option, + #[serde(alias = "address")] + #[serde(alias = "connection_string")] + pub url: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Implementation { + pub framework: String, + #[serde(default)] + #[serde(alias = "db")] + pub database: DataBase, +} + +fn fn_true() -> bool { + true +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Docker { + #[serde(default = "fn_true")] + pub generate_file: bool, + pub username: Option, + pub tag: Option, +} + +impl Default for Docker { + fn default() -> Self { + Self { + generate_file: true, + username: None, + tag: None, + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct CICD { + #[serde(default)] + pub docker: Docker, + #[serde(alias = "k8s")] + pub kubernetes: Option, + #[serde(default = "fn_true")] + #[serde(alias = "ghaction")] + #[serde(alias = "gh_action")] + pub github_action: bool, +} + +impl Default for CICD { + fn default() -> Self { + Self { + docker: Default::default(), + kubernetes: None, + github_action: true, + } + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Config { + pub api: Option, + pub implementation: Implementation, + #[serde(default)] + pub cicd: CICD, +} + diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..ecdffee --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,110 @@ +mod cli; +mod file; +use anyhow::anyhow; +use std::{fs::File, io::Read, path::PathBuf}; + +use structopt::StructOpt; + +pub struct Config { + pub output: PathBuf, + pub force: bool, + // For now, `file::Config` contains all the configuration options we need for generating the API. + pub generate_config: file::Config, +} + +pub fn from_cli_config() -> anyhow::Result { + let cli_config = cli::Config::from_args_safe()?; + let mut config_file = File::open(&cli_config.config)?; + let mut file_config: file::Config = match cli_config + .config + .extension() + .and_then(|it| it.to_str()) + .ok_or(anyhow!("Cannot open config file"))? + { + "toml" => { + let mut content = String::new(); + config_file.read_to_string(&mut content)?; + toml::from_str(&content)? + } + "json" => serde_json::from_reader(config_file)?, + "yaml" => serde_yaml::from_reader(config_file)?, + _ => return Err(anyhow!("Unsupported config file type")), + }; + // Merging config from file and cli + if file_config.implementation.database.engine.is_none() { + file_config.implementation.database.engine = Some("pgsql".to_string()); + } + if let Some(database_engine) = cli_config.database_engine { + file_config.implementation.database.engine = Some(database_engine); + } + if file_config.implementation.database.engine.is_none() + && file_config.implementation.database.url.is_some() + { + let mut url = file_config.implementation.database.url.take().unwrap(); + if url.starts_with("postgres://") { + file_config.implementation.database.engine = Some("postgres".to_string()); + } else if url.starts_with("pgsql://") { + println!("warning: pgsql:// won't work, I'll use postgres:// instead"); + url = url.replace("pgsql://", "postgres://"); + file_config.implementation.database.engine = Some("postgres".to_string()); + } else if url.starts_with("mysql://") { + file_config.implementation.database.engine = Some("mysql".to_string()); + } else if url.starts_with("sqlite://") { + file_config.implementation.database.engine = Some("sqlite".to_string()); + } else { + return Err(anyhow!( + "database engine not set and cannot auto refereed from connection string" + )); + } + file_config.implementation.database.url = Some(url); + } + if file_config.api.is_none() { + if let Some(_load_from_ddl) = cli_config.load_from_ddl { + todo!("load api config from ddl_file"); + } + let try_load_from_db = if let Some(addr) = cli_config.load_from_db { + Some(addr) + } else if let Some(addr) = file_config.implementation.database.url { + Some(addr) + } else { + None + }; + if try_load_from_db.is_some() { + if cli_config.name.is_none() { + return Err(anyhow!("I need to know API name before load it from db!")); + } + } + todo!("load api config from database"); + } + + if let Some(generate_docker) = cli_config.generate_docker { + file_config.cicd.docker.generate_file = generate_docker; + } + if let Some(docker_tag) = cli_config.docker_tag { + file_config.cicd.docker.tag = Some(docker_tag); + } + if let Some(docker_username) = cli_config.docker_username { + file_config.cicd.docker.username = Some(docker_username); + } + if let Some(k8s) = cli_config.kubernetes { + file_config.cicd.kubernetes = Some(k8s); + } + file_config.cicd.kubernetes = match file_config.cicd.kubernetes { + Some(false) => Some(false), + Some(true) if file_config.implementation.database.url.is_none() => { + return Err(anyhow!("generating kubernetes yaml file requires database connection string")); + } + Some(true) if file_config.cicd.docker.username.is_none() => { + // todo: support other docker image registry than dockerhub + return Err(anyhow!("generating kubernetes yaml file requires docker username")); + } + Some(true) => Some(true), + None if file_config.implementation.database.url.is_none() => Some(false), + None => Some(true) + }; + Ok(Config { + output: cli_config.output, + force: cli_config.force, + generate_config: file_config, + }) +} diff --git a/src/technology/database/mod.rs b/src/implementation/database/mod.rs similarity index 100% rename from src/technology/database/mod.rs rename to src/implementation/database/mod.rs diff --git a/src/technology/database/postgres.rs b/src/implementation/database/postgres.rs similarity index 100% rename from src/technology/database/postgres.rs rename to src/implementation/database/postgres.rs diff --git a/src/technology/language/golang.rs b/src/implementation/framework/golang.rs similarity index 89% rename from src/technology/language/golang.rs rename to src/implementation/framework/golang.rs index a19a0ca..788e880 100644 --- a/src/technology/language/golang.rs +++ b/src/implementation/framework/golang.rs @@ -1,4 +1,4 @@ -//! Golang specific tools. +//! Trivial golang web specific tools. use serde::{Deserialize, Serialize}; use std::{collections::HashMap, path::Path}; @@ -7,7 +7,7 @@ use tera::{Result, Tera, Value}; use crate::{ model::{data_type::DataType, Model}, render::render_simple, - technology::Technology, + implementation::Implementation, }; /// "imports" for different golang files @@ -60,7 +60,7 @@ fn data_type_is_string(args: &HashMap) -> Result { } impl Golang { - pub fn new(technology: &Technology, model: &Model) -> Self { + pub fn new(technology: &Implementation, model: &Model) -> Self { let imports_model = if model .fields .iter() @@ -71,8 +71,8 @@ impl Golang { vec![] }; let (db_driver, imports_database) = match technology.database { - crate::technology::DataBase::PgSQL => ("pgsql", "github.com/lib/pq"), - crate::technology::DataBase::MySQL => ("mysql", "github.com/go-sql-driver/mysql"), + crate::implementation::DataBase::PgSQL => ("pgsql", "github.com/lib/pq"), + crate::implementation::DataBase::MySQL => ("mysql", "github.com/go-sql-driver/mysql"), }; Self { imports: Imports { @@ -86,13 +86,13 @@ impl Golang { pub fn register( tera: &mut Tera, - technology: &Technology, + implementation: &Implementation, model: &Model, context: &mut tera::Context, ) { tera.register_function("data_type", data_type_in_template); tera.register_function("is_string", data_type_is_string); - let golang = Golang::new(technology, model); + let golang = Golang::new(implementation, model); context.insert("golang", &golang); } diff --git a/src/implementation/framework/mod.rs b/src/implementation/framework/mod.rs new file mode 100644 index 0000000..372ca20 --- /dev/null +++ b/src/implementation/framework/mod.rs @@ -0,0 +1,3 @@ +//! Framework the user want to use. + +pub mod golang; diff --git a/src/technology/mod.rs b/src/implementation/mod.rs similarity index 68% rename from src/technology/mod.rs rename to src/implementation/mod.rs index e6f1992..da33721 100644 --- a/src/technology/mod.rs +++ b/src/implementation/mod.rs @@ -1,9 +1,9 @@ mod database; -pub mod language; +pub mod framework; pub use database::DataBase; -// TODO: language enum +// TODO: framework enum /// Technology is about the specific technology the user want to implement the API. -pub struct Technology { +pub struct Implementation { pub database: DataBase, } diff --git a/src/main.rs b/src/main.rs index 83662e2..e191b78 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,39 +1,36 @@ -use technology::Technology; +use implementation::Implementation; use crate::{ model::{data_type::DataType, Field, Model}, - technology::language::golang, + implementation::framework::golang, }; mod model; mod render; -mod technology; +mod implementation; +mod config; fn main() { let mut tera = render::load_templates(); render::filter::register(&mut tera); - let config = Technology { - database: technology::DataBase::PgSQL, + let config = config::from_cli_config().unwrap(); + let implementation = Implementation { + database: implementation::DataBase::PgSQL, }; let model = Model { - name: "shuSB".to_string(), + name: config.generate_config.api.as_ref().unwrap().name.clone(), primary_key: Field { name: "id".to_string(), data_type: DataType::UInt(64), }, - fields: vec![ - Field { - name: "name".to_string(), - data_type: DataType::String(None), - }, - Field { - name: "IQ".to_string(), - data_type: DataType::Int(32), - }, - ], + fields: config.generate_config.api.unwrap().fields.into_iter() + .map(|it| Field { + name: it.name, + data_type: it.data_type.into(), + }).collect(), }; let mut context = tera::Context::new(); context.insert("model", &model); - golang::register(&mut tera, &config, &model, &mut context); - golang::render(&tera, "./shuSB", &mut context); + golang::register(&mut tera, &implementation, &model, &mut context); + golang::render(&tera, config.output, &mut context); } diff --git a/src/model/data_type.rs b/src/model/data_type.rs index 289bc0e..a908d1d 100644 --- a/src/model/data_type.rs +++ b/src/model/data_type.rs @@ -1,4 +1,6 @@ +use convert_case::{Case, Casing}; use serde::{Deserialize, Serialize}; +use toml::value::Datetime; /// Meta information about a string. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -19,3 +21,59 @@ pub enum DataType { // TODO: separate date, time and datetime. DateTime, } + +impl From for DataType { + fn from(s: String) -> Self { + match s.as_str().to_case(Case::Snake).as_str() { + "tiny_int" | "tinyint" | "tiny_integer" | "int8" => DataType::Int(8), + "small_int" | "smallint" | "small_integer" | "int16" => DataType::Int(16), + "int" | "integer" | "medium_int" | "mediumint" | "medium_integer" | "int32" => DataType::Int(32), + "big_int" | "bigint" | "big_integer" | "int64" => DataType::Int(64), + "unsigned_tiny_int" | "unsigned_tiny_integer" | "uint8" => DataType::UInt(8), + "unsigned_small_int" | "unsigned_small_integer" | "uint16" => DataType::UInt(16), + "uint" | "unsigned" | "unsigned_medium_int" | "unsigned_medium_integer" | "uint32" => DataType::UInt(32), + "unsigned_big_int" | "unsigned_big_integer" | "uint64" => DataType::UInt(64), + "float" | "float32" => DataType::Float(32), + "double" | "float64" => DataType::Float(64), + "string" | "text" | "medium_text" => DataType::String(None), + s => { + if s.starts_with("char(") { + let length: usize = s + .trim_start_matches("char(") + .trim_end_matches(")") + .parse() + .unwrap(); + DataType::String(Some(StringMeta { + length, + fixed_length: true, + })) + } else if s.starts_with("varchar(") { + let length: usize = s + .trim_start_matches("varchar(") + .trim_end_matches(")") + .parse() + .unwrap(); + DataType::String(Some(StringMeta { + length, + fixed_length: false, + })) + } else { + todo!( + "will support these in the future: + tiny_text, + long_text, + tiny_blob, + blob, + medium_blob, + long_blob, + bool, + date, + date_time, + time, + timestamp" + ) + } + } + } + } +} diff --git a/src/technology/language/mod.rs b/src/technology/language/mod.rs deleted file mode 100644 index 217688a..0000000 --- a/src/technology/language/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -//! Language the user want to use. -// TODO: maybe we want to support multiple framework for a single language in the future, then "framework" would be a better name - -pub mod golang; From ca56c4fc3726faeaca5561fa391b8296a9b8eca9 Mon Sep 17 00:00:00 2001 From: longfangsong Date: Sat, 16 Oct 2021 17:52:43 +0800 Subject: [PATCH 2/2] add clippy test to ci --- .github/workflows/run-tests.yml | 5 +++ Cargo.toml | 2 +- src/config/cli.rs | 20 +++++------ src/config/file.rs | 6 ++-- src/config/mod.rs | 22 ++++++------ .../{database/mod.rs => database.rs} | 2 -- src/implementation/database/postgres.rs | 35 ------------------- src/implementation/framework/golang.rs | 20 +++++------ src/main.rs | 16 ++++++--- src/model/data_type.rs | 13 ++++--- 10 files changed, 59 insertions(+), 82 deletions(-) rename src/implementation/{database/mod.rs => database.rs} (93%) delete mode 100644 src/implementation/database/postgres.rs diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 926a530..4df6e5f 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -17,6 +17,11 @@ jobs: - uses: actions-rs/toolchain@v1 with: toolchain: nightly + - name: Run clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + args: -- -D warnings - name: Run unit tests uses: actions-rs/cargo@v1 with: diff --git a/Cargo.toml b/Cargo.toml index efeb4b9..ef7a1b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ tera = "1.12.1" convert_case = "0.4.0" pluralize-rs = "0.1.0" include_dir = { version = "0.6.0", features = ["search"] } -serde = {version="1.0.126", features = ["derive"]} +serde = { version = "1.0.126", features = ["derive"] } serde_yaml = "0.8.17" serde_json = "1.0.64" toml = "0.5.8" diff --git a/src/config/cli.rs b/src/config/cli.rs index e9eeeb8..082d947 100644 --- a/src/config/cli.rs +++ b/src/config/cli.rs @@ -9,7 +9,7 @@ use structopt::StructOpt; )] pub struct Config { /// Config file path. - #[structopt(short="i", long, alias="input", parse(from_os_str), env = "CONFIG")] + #[structopt(short = "i", long, alias = "input", parse(from_os_str), env = "CONFIG")] pub config: PathBuf, /// Output project path. #[structopt(short, long, parse(from_os_str), env = "OUTPUT")] @@ -19,23 +19,23 @@ pub struct Config { pub force: bool, /// Output project path. - #[structopt(alias="dbms", long, env = "DATABASE")] + #[structopt(alias = "dbms", long, env = "DATABASE")] pub database_engine: Option, #[structopt(short, long, env = "API_NAME")] pub name: Option, - #[structopt(alias="ddl", long, env = "DDL")] + #[structopt(alias = "ddl", long, env = "DDL")] pub load_from_ddl: Option, - #[structopt(short="load-db", long, env = "LOAD_DB")] + #[structopt(short = "load-db", long, env = "LOAD_DB")] pub load_from_db: Option, - #[structopt(short="d", long, env = "DOCKER")] - pub generate_docker: Option, - #[structopt(alias="du", long, env = "DOCKER_USERNAME")] + #[structopt(short = "d", long, env = "DOCKER")] + pub generate_docker: Option, + #[structopt(alias = "du", long, env = "DOCKER_USERNAME")] pub docker_username: Option, - #[structopt(alias="dt", long, env = "DOCKER_TAG")] - pub docker_tag: Option, + #[structopt(alias = "dt", long, env = "DOCKER_TAG")] + pub docker_tag: Option, - #[structopt(short="k8s", long, env = "KUBERNETES")] + #[structopt(short = "k8s", long, env = "KUBERNETES")] pub kubernetes: Option, } diff --git a/src/config/file.rs b/src/config/file.rs index e00d126..5ef96b7 100644 --- a/src/config/file.rs +++ b/src/config/file.rs @@ -1,6 +1,3 @@ -use std::fs::OpenOptions; - -use anyhow::{anyhow, Result}; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Clone)] @@ -10,6 +7,7 @@ pub struct Field { pub data_type: String, } +#[allow(clippy::upper_case_acronyms)] #[derive(Serialize, Deserialize, Debug, Clone)] pub struct API { pub name: String, @@ -54,6 +52,7 @@ impl Default for Docker { } } +#[allow(clippy::upper_case_acronyms)] #[derive(Serialize, Deserialize, Debug, Clone)] pub struct CICD { #[serde(default)] @@ -83,4 +82,3 @@ pub struct Config { #[serde(default)] pub cicd: CICD, } - diff --git a/src/config/mod.rs b/src/config/mod.rs index ecdffee..67b3479 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,7 +1,7 @@ mod cli; mod file; use anyhow::anyhow; -use std::{fs::File, io::Read, path::PathBuf}; +use std::{ffi::OsStr, fs::File, io::Read, path::PathBuf}; use structopt::StructOpt; @@ -18,8 +18,8 @@ pub fn from_cli_config() -> anyhow::Result { let mut file_config: file::Config = match cli_config .config .extension() - .and_then(|it| it.to_str()) - .ok_or(anyhow!("Cannot open config file"))? + .and_then(OsStr::to_str) + .ok_or_else(|| anyhow!("Cannot open config file"))? { "toml" => { let mut content = String::new(); @@ -64,17 +64,15 @@ pub fn from_cli_config() -> anyhow::Result { } let try_load_from_db = if let Some(addr) = cli_config.load_from_db { Some(addr) - } else if let Some(addr) = file_config.implementation.database.url { - Some(addr) } else { - None + file_config.implementation.database.url.clone() }; if try_load_from_db.is_some() { if cli_config.name.is_none() { return Err(anyhow!("I need to know API name before load it from db!")); } + todo!("load api config from database"); } - todo!("load api config from database"); } if let Some(generate_docker) = cli_config.generate_docker { @@ -92,15 +90,19 @@ pub fn from_cli_config() -> anyhow::Result { file_config.cicd.kubernetes = match file_config.cicd.kubernetes { Some(false) => Some(false), Some(true) if file_config.implementation.database.url.is_none() => { - return Err(anyhow!("generating kubernetes yaml file requires database connection string")); + return Err(anyhow!( + "generating kubernetes yaml file requires database connection string" + )); } Some(true) if file_config.cicd.docker.username.is_none() => { // todo: support other docker image registry than dockerhub - return Err(anyhow!("generating kubernetes yaml file requires docker username")); + return Err(anyhow!( + "generating kubernetes yaml file requires docker username" + )); } Some(true) => Some(true), None if file_config.implementation.database.url.is_none() => Some(false), - None => Some(true) + None => Some(true), }; Ok(Config { output: cli_config.output, diff --git a/src/implementation/database/mod.rs b/src/implementation/database.rs similarity index 93% rename from src/implementation/database/mod.rs rename to src/implementation/database.rs index 66815f0..a9f4e18 100644 --- a/src/implementation/database/mod.rs +++ b/src/implementation/database.rs @@ -2,8 +2,6 @@ use serde::{Deserialize, Serialize}; -pub mod postgres; - /// The database the user wants to use. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] #[serde(rename_all = "lowercase")] diff --git a/src/implementation/database/postgres.rs b/src/implementation/database/postgres.rs deleted file mode 100644 index 0dc12e5..0000000 --- a/src/implementation/database/postgres.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! PostgreSQL database tool functions -use std::collections::HashMap; - -use tera::{Result, Value}; - -use crate::model::data_type::DataType; - -fn db_type(data_type: DataType) -> String { - match data_type { - DataType::Int(x) if x <= 8 => "int8".to_string(), - DataType::Int(x) if x <= 16 => "int16".to_string(), - DataType::Int(x) if x <= 32 => "int32".to_string(), - DataType::Int(_) => "int64".to_string(), - DataType::UInt(x) if x <= 8 => "uint8".to_string(), - DataType::UInt(x) if x <= 16 => "uint16".to_string(), - DataType::UInt(x) if x <= 32 => "uint32".to_string(), - DataType::UInt(_) => "uint64".to_string(), - DataType::Float(x) if x <= 32 => "float32".to_string(), - DataType::Float(_) => "float64".to_string(), - DataType::String(None) => "text".to_string(), - DataType::String(Some(x)) if x.fixed_length => format!("char({})", x.length), - DataType::String(Some(x)) if !x.fixed_length => format!("varchar({})", x.length), - DataType::String(Some(_)) => unreachable!(), - DataType::DateTime => "time".to_string(), - } -} - -fn db_type_in_template(args: &HashMap) -> Result { - let data_type_value: DataType = serde_json::from_value(args.get("data_type").unwrap().clone())?; - Ok(Value::String(db_type(data_type_value))) -} - -pub fn register(tera: &mut tera::Tera) { - tera.register_function("db_type", db_type_in_template); -} diff --git a/src/implementation/framework/golang.rs b/src/implementation/framework/golang.rs index 788e880..6ade6dd 100644 --- a/src/implementation/framework/golang.rs +++ b/src/implementation/framework/golang.rs @@ -5,9 +5,9 @@ use std::{collections::HashMap, path::Path}; use tera::{Result, Tera, Value}; use crate::{ + implementation::Implementation, model::{data_type::DataType, Model}, render::render_simple, - implementation::Implementation, }; /// "imports" for different golang files @@ -29,17 +29,17 @@ pub struct Golang { // TODO: It is highly possible that each language needs `data_type`, `register` and `render`, maybe we can add a trait. /// Get the string representing of a data_type in Golang. -fn data_type(data_type: DataType) -> String { +fn data_type(data_type: &DataType) -> String { match data_type { - DataType::Int(x) if x <= 8 => "int8".to_string(), - DataType::Int(x) if x <= 16 => "int16".to_string(), - DataType::Int(x) if x <= 32 => "int32".to_string(), + DataType::Int(x) if *x <= 8 => "int8".to_string(), + DataType::Int(x) if *x <= 16 => "int16".to_string(), + DataType::Int(x) if *x <= 32 => "int32".to_string(), DataType::Int(_) => "int64".to_string(), - DataType::UInt(x) if x <= 8 => "uint8".to_string(), - DataType::UInt(x) if x <= 16 => "uint16".to_string(), - DataType::UInt(x) if x <= 32 => "uint32".to_string(), + DataType::UInt(x) if *x <= 8 => "uint8".to_string(), + DataType::UInt(x) if *x <= 16 => "uint16".to_string(), + DataType::UInt(x) if *x <= 32 => "uint32".to_string(), DataType::UInt(_) => "uint64".to_string(), - DataType::Float(x) if x <= 32 => "float32".to_string(), + DataType::Float(x) if *x <= 32 => "float32".to_string(), DataType::Float(_) => "float64".to_string(), DataType::String(_) => "string".to_string(), DataType::DateTime => "time".to_string(), @@ -48,7 +48,7 @@ fn data_type(data_type: DataType) -> String { fn data_type_in_template(args: &HashMap) -> Result { let data_type_value: DataType = serde_json::from_value(args.get("data_type").unwrap().clone())?; - Ok(Value::String(data_type(data_type_value))) + Ok(Value::String(data_type(&data_type_value))) } /// A function which can be used in the template for judging whether the datatype is a string diff --git a/src/main.rs b/src/main.rs index e191b78..9324ddd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,14 @@ use implementation::Implementation; use crate::{ - model::{data_type::DataType, Field, Model}, implementation::framework::golang, + model::{data_type::DataType, Field, Model}, }; +mod config; +mod implementation; mod model; mod render; -mod implementation; -mod config; fn main() { let mut tera = render::load_templates(); @@ -23,11 +23,17 @@ fn main() { name: "id".to_string(), data_type: DataType::UInt(64), }, - fields: config.generate_config.api.unwrap().fields.into_iter() + fields: config + .generate_config + .api + .unwrap() + .fields + .into_iter() .map(|it| Field { name: it.name, data_type: it.data_type.into(), - }).collect(), + }) + .collect(), }; let mut context = tera::Context::new(); context.insert("model", &model); diff --git a/src/model/data_type.rs b/src/model/data_type.rs index a908d1d..bf13e96 100644 --- a/src/model/data_type.rs +++ b/src/model/data_type.rs @@ -1,6 +1,5 @@ use convert_case::{Case, Casing}; use serde::{Deserialize, Serialize}; -use toml::value::Datetime; /// Meta information about a string. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -27,11 +26,15 @@ impl From for DataType { match s.as_str().to_case(Case::Snake).as_str() { "tiny_int" | "tinyint" | "tiny_integer" | "int8" => DataType::Int(8), "small_int" | "smallint" | "small_integer" | "int16" => DataType::Int(16), - "int" | "integer" | "medium_int" | "mediumint" | "medium_integer" | "int32" => DataType::Int(32), + "int" | "integer" | "medium_int" | "mediumint" | "medium_integer" | "int32" => { + DataType::Int(32) + } "big_int" | "bigint" | "big_integer" | "int64" => DataType::Int(64), "unsigned_tiny_int" | "unsigned_tiny_integer" | "uint8" => DataType::UInt(8), "unsigned_small_int" | "unsigned_small_integer" | "uint16" => DataType::UInt(16), - "uint" | "unsigned" | "unsigned_medium_int" | "unsigned_medium_integer" | "uint32" => DataType::UInt(32), + "uint" | "unsigned" | "unsigned_medium_int" | "unsigned_medium_integer" | "uint32" => { + DataType::UInt(32) + } "unsigned_big_int" | "unsigned_big_integer" | "uint64" => DataType::UInt(64), "float" | "float32" => DataType::Float(32), "double" | "float64" => DataType::Float(64), @@ -40,7 +43,7 @@ impl From for DataType { if s.starts_with("char(") { let length: usize = s .trim_start_matches("char(") - .trim_end_matches(")") + .trim_end_matches(')') .parse() .unwrap(); DataType::String(Some(StringMeta { @@ -50,7 +53,7 @@ impl From for DataType { } else if s.starts_with("varchar(") { let length: usize = s .trim_start_matches("varchar(") - .trim_end_matches(")") + .trim_end_matches(')') .parse() .unwrap(); DataType::String(Some(StringMeta {