sqlx_model_tools/
common.rs

1use std::{
2    collections::BTreeMap,
3    fmt::{Display, Formatter},
4};
5
6use config::{Config, ConfigError};
7use heck::{
8    ToKebabCase, ToLowerCamelCase, ToShoutyKebabCase, ToShoutySnakeCase, ToSnakeCase,
9    ToUpperCamelCase,
10};
11use regex::Regex;
12use serde::Serialize;
13use serde_json::Value;
14use std::fmt::Result as FmtResult;
15use tinytemplate::{format_unescaped, TinyTemplate};
16use tokio::io::AsyncWriteExt;
17
18use crate::mysql::MySqlParse;
19
20#[derive(Clone, PartialEq, Eq)]
21pub struct DataField {
22    pub field_name: String,
23    pub type_name: String,
24    pub is_null: bool,
25    pub is_pk: bool,
26    pub default: Option<String>,
27    pub comment: String,
28}
29
30pub struct DataValue {
31    pub table_name: String,
32    pub columns: Vec<DataField>,
33}
34
35#[derive(Serialize)]
36pub struct RenderField {
37    field_name: String,
38    column_name: String,
39    type_name: String,
40    default: String,
41    is_pk: bool,
42    is_null: bool,
43    comment: String,
44}
45#[derive(Serialize, Clone)]
46pub struct RenderTableInfo {
47    model_name: String,
48    table_name: String,
49    multi_pk: bool,
50}
51#[derive(Serialize)]
52pub struct RenderBody {
53    table: RenderTableInfo,
54    field_data: Vec<RenderField>,
55}
56#[derive(Serialize)]
57pub struct RenderMergeItem {
58    render_data: String,
59    table: RenderTableInfo,
60    field_data: Vec<RenderField>,
61}
62#[derive(Serialize)]
63pub struct RenderMergeBody {
64    items: Vec<RenderMergeItem>,
65}
66
67#[derive(Debug)]
68pub enum ConfigParseError {
69    Config(String),
70    Regex(String),
71    Tpl(String),
72    Io(String),
73}
74impl Display for ConfigParseError {
75    fn fmt(&self, f: &mut Formatter) -> FmtResult {
76        write!(f, "{:?}", self)
77    }
78}
79impl From<ConfigError> for ConfigParseError {
80    fn from(err: ConfigError) -> Self {
81        ConfigParseError::Config(format!("{}", err))
82    }
83}
84impl From<regex::Error> for ConfigParseError {
85    fn from(err: regex::Error) -> Self {
86        ConfigParseError::Regex(format!("{}", err))
87    }
88}
89impl From<tinytemplate::error::Error> for ConfigParseError {
90    fn from(err: tinytemplate::error::Error) -> Self {
91        ConfigParseError::Tpl(format!("{}", err))
92    }
93}
94impl From<std::io::Error> for ConfigParseError {
95    fn from(err: std::io::Error) -> Self {
96        ConfigParseError::Io(format!("{}", err))
97    }
98}
99impl From<sqlx::Error> for ConfigParseError {
100    fn from(err: sqlx::Error) -> Self {
101        ConfigParseError::Io(format!("{}", err))
102    }
103}
104
105#[async_trait::async_trait]
106pub trait TableParseData {
107    type OUT: TableParseData;
108    async fn new(uri: &str) -> Result<Self::OUT, ConfigParseError>;
109    async fn list_tables(&self) -> Result<Vec<String>, ConfigParseError>;
110    async fn parse_table_column(&self, table: &str) -> Result<Vec<DataField>, ConfigParseError>;
111}
112
113pub struct ConfigParse {
114    type_defalut: String,
115    type_map: Vec<(String, Vec<String>)>,
116    default_none: String,
117    default_null: String,
118    type_transform: bool,
119    defalut_map: Vec<(String, String)>,
120    uri: String,
121    table_name_rule: (String, String),
122    model_name_rule: (String, String, String),
123    column_name_rule: (String, String, String),
124    outfile_name_rule: (String, String, String),
125    tables: Vec<String>,
126    outfile_name: String,
127    outfile_merge_tpl: String,
128    outfile_merge: bool,
129    outfile_overwrite: bool,
130    tpl_body: String,
131    table_rule: Option<String>,
132}
133impl ConfigParse {
134    pub async fn run(config_file: &str) -> Result<(), ConfigParseError> {
135        let settings = Config::builder()
136            .add_source(config::File::with_name(config_file))
137            .build()?;
138        let config_parse = ConfigParse::parse(&settings)?;
139        if config_parse.uri.starts_with("mysql") {
140            config_parse
141                .table_runder(MySqlParse::new(config_parse.uri.as_str()).await?)
142                .await?
143        } else {
144            return Err(ConfigParseError::Config(
145                "database type not support".to_string(),
146            ));
147        }
148        Ok(())
149    }
150    fn add_formatter<'t>(&self, mut tpl: TinyTemplate<'t>) -> TinyTemplate<'t> {
151        tpl.add_formatter("rmln", |v, s: &mut String| match v {
152            Value::String(ts) => {
153                use core::fmt::Write;
154                let mut rs = ts.replace('\n', "");
155                rs = rs.replace('\r', "");
156                write!(s, "{}", rs)?;
157                Ok(())
158            }
159            _ => Err(tinytemplate::error::Error::GenericError {
160                msg: "line format only support in string.".to_string(),
161            }),
162        });
163        tpl
164    }
165    async fn table_runder<PT>(&self, db_parse: PT) -> Result<(), ConfigParseError>
166    where
167        PT: TableParseData,
168    {
169        let mut tpl = TinyTemplate::new();
170        tpl.set_default_formatter(&format_unescaped);
171        tpl = self.add_formatter(tpl);
172        tpl.add_template("body", self.tpl_body.as_str())?;
173        if !self.outfile_name.is_empty() {
174            tpl.add_template("path", self.outfile_name.as_str())?;
175        }
176        if !self.outfile_merge_tpl.is_empty() {
177            tpl.add_template("body_merge", self.outfile_merge_tpl.as_str())?;
178        }
179        let mut tables = vec![];
180        match &self.table_rule {
181            Some(table_rule) => {
182                let regex = Regex::new(table_rule)?;
183                for t in db_parse.list_tables().await?.into_iter() {
184                    if regex.is_match(t.as_str()) {
185                        tables.push(t);
186                    }
187                }
188            }
189            None => {
190                tables = self.tables.clone();
191            }
192        }
193        if tables.is_empty() {
194            return Err(ConfigParseError::Config(
195                "No table matching the record was found".to_string(),
196            ));
197        }
198        let mut merge_item = vec![];
199        for table in tables.iter() {
200            let columns = db_parse.parse_table_column(table).await?;
201            let table_pk_num = columns.iter().filter(|e| e.is_pk).count();
202            let table_name =
203                self.replace_name((&self.table_name_rule.0, &self.table_name_rule.1), table);
204            let data = DataValue {
205                table_name,
206                columns,
207            };
208            let model_name = self.parse_name(&self.model_name_rule, table);
209            let mut name = RenderTableInfo {
210                model_name,
211                multi_pk: table_pk_num > 1,
212                table_name: data.table_name.clone(),
213            };
214            let render_body = self.render_data(name.clone(), data).await?;
215            let body_str = tpl.render("body", &render_body)?;
216            if !self.outfile_merge && !self.outfile_name.is_empty() {
217                name.model_name = self.parse_name(&self.outfile_name_rule, table);
218                let res = tpl.render("path", &name)?;
219                self.write_file(Some(res), body_str).await?;
220            } else {
221                merge_item.push(RenderMergeItem {
222                    render_data: body_str,
223                    table: render_body.table,
224                    field_data: render_body.field_data,
225                });
226            }
227        }
228        let outbody = if !self.outfile_merge_tpl.is_empty() {
229            tpl.render("body_merge", &RenderMergeBody { items: merge_item })?
230        } else {
231            merge_item
232                .into_iter()
233                .map(|e| e.render_data)
234                .collect::<Vec<String>>()
235                .join("\n")
236        };
237        if self.outfile_name.is_empty() {
238            self.write_file(None, outbody).await?;
239        } else if self.outfile_merge {
240            let res = tpl.render("path", &Value::Null)?;
241            self.write_file(Some(res), outbody).await?;
242        }
243        Ok(())
244    }
245    async fn write_file(
246        &self,
247        out_put: Option<String>,
248        body: String,
249    ) -> Result<(), ConfigParseError> {
250        match out_put {
251            Some(file_path) => match tokio::fs::File::open(file_path.as_str()).await {
252                Result::Err(e) => match e.kind() {
253                    tokio::io::ErrorKind::NotFound => {
254                        let mut file = tokio::fs::File::create(file_path.as_str()).await?;
255                        file.write_all(body.as_bytes()).await?;
256                    }
257                    _ => {
258                        return Err(ConfigParseError::Io(
259                            e.to_string() + " save in path:" + file_path.as_str(),
260                        ));
261                    }
262                },
263                Result::Ok(_) => {
264                    if self.outfile_overwrite {
265                        let mut file = tokio::fs::File::create(file_path.as_str()).await.unwrap();
266                        file.write_all(body.as_bytes()).await.map_err(|e| {
267                            ConfigParseError::Io(
268                                e.to_string() + " write in file:" + file_path.as_str(),
269                            )
270                        })?;
271                        file.sync_all().await.map_err(|e| {
272                            ConfigParseError::Io(
273                                e.to_string() + " sync in file:" + file_path.as_str(),
274                            )
275                        })?;
276                    }
277                }
278            },
279            _ => {
280                println!("{}", body);
281            }
282        }
283        Ok(())
284    }
285    pub fn parse(settings: &Config) -> Result<Self, ConfigParseError> {
286        let db_url = settings.get_string("db_url")?;
287        let tpl_body = settings.get_string("tpl_body")?;
288        let outfile_name = settings.get_string("outfile_name").unwrap_or_default();
289        let outfile_merge = settings.get_bool("outfile_merge").unwrap_or(false);
290        let outfile_merge_tpl = settings.get_string("outfile_merge_tpl").unwrap_or_default();
291        let outfile_overwrite = settings.get_bool("outfile_overwrite").unwrap_or(false);
292        let type_transform = settings.get_bool("type_transform").unwrap_or(true);
293        let type_defalut = settings.get_string("type_default").unwrap_or_default();
294        let model_name_rule = settings.get_string("model_name_rule").unwrap_or_default();
295        let model_name_start_replace = settings
296            .get_string("model_name_start_replace")
297            .unwrap_or_default();
298        let model_name_end_replace = settings
299            .get_string("model_name_end_replace")
300            .unwrap_or_default();
301        let table_name_start_replace = settings
302            .get_string("table_name_start_replace")
303            .unwrap_or_default();
304        let table_name_end_replace = settings
305            .get_string("table_name_end_replace")
306            .unwrap_or_default();
307        let column_name_rule = settings.get_string("column_name_rule").unwrap_or_default();
308        let column_name_start_replace = settings
309            .get_string("column_name_start_replace")
310            .unwrap_or_default();
311        let column_name_end_replace = settings
312            .get_string("column_name_end_replace")
313            .unwrap_or_default();
314        let outfile_name_rule = settings.get_string("outfile_name_rule").unwrap_or_default();
315        let outfile_name_start_replace = settings
316            .get_string("outfile_name_start_replace")
317            .unwrap_or_default();
318        let outfile_name_end_replace = settings
319            .get_string("outfile_name_end_replace")
320            .unwrap_or_default();
321
322        let mut type_map = vec![];
323        if let Result::Ok(map_arr) = settings.get_table("type_map") {
324            for (_, map_list) in map_arr
325                .into_iter()
326                .map(|(i, c)| (-i.parse::<i32>().unwrap_or(0), c))
327                .collect::<BTreeMap<_, _>>()
328            {
329                for (out_type, val) in map_list.into_table()?.into_iter() {
330                    let mut tmap = vec![];
331                    for aval in val.into_array()? {
332                        tmap.push(aval.into_string()?);
333                    }
334                    type_map.push((out_type, tmap));
335                }
336            }
337        }
338
339        let default_none = settings.get_string("default_none").unwrap_or_default();
340        let default_null = settings.get_string("default_null").unwrap_or_default();
341
342        let mut defalut_map = vec![];
343        if let Result::Ok(set_defalut_map) = settings.get_table("default_map") {
344            for (_, map_list) in set_defalut_map
345                .into_iter()
346                .map(|(i, c)| (-i.parse::<i32>().unwrap_or(0), c))
347                .collect::<BTreeMap<_, _>>()
348            {
349                for (key, val) in map_list.into_table()?.into_iter() {
350                    let out_type = val.into_string()?;
351                    defalut_map.push((key, out_type));
352                }
353            }
354        }
355        let mut table_rule = None;
356        let mut tables = vec![];
357        if let Result::Ok(table) = settings.get_string("tables") {
358            table_rule = Some(table);
359        } else {
360            let table = settings.get_array("tables")?;
361            for map in table.into_iter() {
362                tables.push(map.into_string()?);
363            }
364        }
365        let config_parse = ConfigParse {
366            outfile_name,
367            outfile_merge,
368            outfile_merge_tpl,
369            outfile_overwrite,
370            tpl_body,
371            type_defalut,
372            type_map,
373            default_none,
374            type_transform,
375            default_null,
376            defalut_map,
377            table_name_rule: (table_name_start_replace, table_name_end_replace),
378            model_name_rule: (
379                model_name_rule,
380                model_name_start_replace,
381                model_name_end_replace,
382            ),
383            column_name_rule: (
384                column_name_rule,
385                column_name_start_replace,
386                column_name_end_replace,
387            ),
388            outfile_name_rule: (
389                outfile_name_rule,
390                outfile_name_start_replace,
391                outfile_name_end_replace,
392            ),
393            uri: db_url,
394            table_rule,
395            tables,
396        };
397        Ok(config_parse)
398    }
399    fn parse_column_type(&self, db_field: &str) -> Result<String, ConfigParseError> {
400        for (out_type, out_reg) in self.type_map.iter() {
401            for reg_str in out_reg {
402                let regex = Regex::new(reg_str)?;
403                if regex.is_match(db_field) {
404                    return Ok(out_type.to_owned());
405                }
406            }
407        }
408        Ok(self.type_defalut.clone())
409    }
410    fn replace_name(&self, rule: (&String, &String), table_name: &String) -> String {
411        let mut out_name = table_name.to_owned();
412        if !rule.0.is_empty() {
413            if let Some(data) = table_name.strip_prefix(rule.0.as_str()) {
414                out_name = data.to_owned();
415            }
416        }
417        if !rule.1.is_empty() {
418            if let Some(data) = table_name.strip_prefix(rule.1.as_str()) {
419                out_name = data.to_owned();
420            }
421        }
422        out_name
423    }
424    fn parse_name(&self, rule: &(String, String, String), table_name: &String) -> String {
425        let out_name = self.replace_name((&rule.1, &rule.2), table_name);
426        match rule.0.as_str() {
427            "lower" => out_name.to_lowercase(),
428            "snake" => out_name.to_snake_case(),
429            "upper" => out_name.to_uppercase(),
430            "shouty_snake" => out_name.to_shouty_snake_case(),
431            "shouty_kebab" => out_name.to_shouty_kebab_case(),
432            "kebab" => out_name.to_kebab_case(),
433            "upper_camel" => out_name.to_upper_camel_case(),
434            "lower_camel" => out_name.to_lower_camel_case(),
435            _ => out_name,
436        }
437    }
438    pub async fn render_data(
439        &self,
440        name: RenderTableInfo,
441        columndata: DataValue,
442    ) -> Result<RenderBody, ConfigParseError> {
443        let mut field_datas = vec![];
444        for field in columndata.columns {
445            let column_name = self.parse_name(&self.column_name_rule, &field.field_name);
446            let ty = if self.type_transform {
447                self.parse_column_type(field.type_name.as_str())?
448            } else {
449                field.type_name
450            };
451            let def = match field.default {
452                Some(mut ts) => {
453                    for (in_reg, out_data) in self.defalut_map.iter() {
454                        let reg = Regex::new(in_reg)?;
455                        if reg.is_match(ts.as_str()) {
456                            ts = reg
457                                .replace(ts.as_str(), regex::NoExpand(out_data))
458                                .to_string();
459                        }
460                    }
461                    ts
462                }
463                None => {
464                    if field.is_null {
465                        self.default_null.clone()
466                    } else {
467                        self.default_none.clone()
468                    }
469                }
470            };
471            let field_data = RenderField {
472                field_name: field.field_name,
473                column_name,
474                type_name: ty,
475                is_null: field.is_null,
476                default: def,
477                is_pk: field.is_pk,
478                comment: field.comment,
479            };
480            field_datas.push(field_data);
481        }
482        Ok(RenderBody {
483            table: name,
484            field_data: field_datas,
485        })
486    }
487}