protobuf_dbml/transpiler/
mod.rs

1use std::io::Result;
2
3use crate::generator::{Block, Codegen};
4use crate::{NAME, VERSION};
5
6use dbml_rs::*;
7
8mod traits;
9use dbml_rs::ast::table::TableBlock;
10use traits::*;
11pub mod config;
12mod err;
13
14enum EntityProfile {
15  Normal,
16  Create,
17  Update,
18}
19
20pub fn transpile(ast: analyzer::SemanticSchemaBlock, config: &config::Config) -> Result<String> {
21  let codegen = Codegen::new()
22    .line(format!("//! Generated by {NAME} {VERSION}"))
23    .line_skip(1)
24    .line(r#"syntax = "proto3";"#)
25    .line_skip(1)
26    .line(r#"import "google/protobuf/timestamp.proto";"#)
27    .line_cond(
28      !config.package_name.is_empty(),
29      format!("\npackage {};", config.package_name),
30    );
31
32  let codegen = gen_entity_modules(&ast, codegen, config);
33  let codegen = gen_enum_modules(&ast, codegen, config);
34
35  Ok(codegen.to_string())
36}
37
38fn gen_entity_modules(
39  ast: &analyzer::SemanticSchemaBlock,
40  codegen: Codegen,
41  config: &config::Config,
42) -> Codegen {
43  ast.tables.iter().fold(codegen, |acc, table| {
44    let mut blocks = Vec::with_capacity(3);
45
46    if true {
47      blocks.push(gen_entity_module_with_profile(
48        table.clone(),
49        config,
50        EntityProfile::Normal,
51      ));
52    }
53    if config.is_with_create_schema {
54      blocks.push(gen_entity_module_with_profile(
55        table.clone(),
56        config,
57        EntityProfile::Create,
58      ));
59    }
60    if config.is_with_update_schema {
61      blocks.push(gen_entity_module_with_profile(
62        table.clone(),
63        config,
64        EntityProfile::Update,
65      ));
66    }
67
68    acc.block_vec(blocks)
69  })
70}
71
72fn gen_entity_module_with_profile(
73  table: TableBlock,
74  config: &config::Config,
75  profile: EntityProfile,
76) -> Block {
77  let ast::table::TableBlock {
78    ident,
79    cols: fields,
80    indexes,
81    ..
82  } = table;
83
84  let transformed_table_name = (config.table_name_transform_fn)(
85    ident.schema.as_ref().map(|v| v.as_str()),
86    &ident.name,
87    ident.alias.as_ref().map(|v| v.as_str()),
88  );
89
90  let transformed_table_name = match profile {
91    EntityProfile::Normal => format!("message {}", transformed_table_name),
92    EntityProfile::Create => format!("message Create{}", transformed_table_name),
93    EntityProfile::Update => format!("message Update{}", transformed_table_name),
94  };
95
96  let table_block = Block::new(1, Some(transformed_table_name));
97
98  // field listing
99  fields
100    .into_iter()
101    .enumerate()
102    .fold(table_block, |acc, (i, field)| {
103      let mut out_fields = match profile {
104        EntityProfile::Normal => {
105          let mut out_fields = vec![];
106
107          if field.settings.is_nullable {
108            out_fields.push(format!("optional"))
109          }
110
111          out_fields
112        }
113        EntityProfile::Create => {
114          let mut out_fields = vec![];
115
116          if field.settings.is_pk {
117            match config.is_create_schema_primary_key_included {
118              None if field.settings.is_incremental => {
119                return acc;
120              }
121              Some(false) => {
122                return acc;
123              }
124              _ => (),
125            }
126          }
127
128          if field.settings.is_nullable || field.settings.default.is_some() {
129            out_fields.push(format!("optional"))
130          }
131
132          out_fields
133        }
134        EntityProfile::Update => {
135          let mut out_fields = vec![];
136
137          if field.settings.is_pk && !config.is_update_schema_primary_key_included {
138            return acc;
139          }
140
141          if !field.settings.is_pk {
142            out_fields.push(format!("optional"))
143          }
144
145          out_fields
146        }
147      };
148
149      if let ast::table::ColumnTypeName::Enum(s) = field.r#type.type_name {
150        let transformed_enum_name = (config.enum_name_transform_fn)(
151          // FIXME: fill this None. this can lead to potential bug
152          None, &s,
153        );
154        out_fields.push(transformed_enum_name)
155      } else if let Some(exp_type) = field.r#type.to_col_type() {
156        out_fields.push(exp_type)
157      } else {
158        panic!("unsupported type")
159      }
160
161      out_fields.push(field.name);
162
163      acc.line(format!("{} = {};", out_fields.join(" "), i + 1))
164    })
165}
166
167fn gen_enum_modules(
168  ast: &analyzer::SemanticSchemaBlock,
169  codegen: Codegen,
170  config: &config::Config,
171) -> Codegen {
172  ast.enums.iter().fold(codegen, |acc, r#enum| {
173    let ast::enums::EnumBlock {
174      ident: ast::enums::EnumIdent { name, schema },
175      values,
176    } = r#enum;
177
178    let transformed_enum_name =
179      (config.enum_name_transform_fn)(schema.as_ref().map(|v| v.as_str()), &name);
180    let enum_block = Block::new(1, Some(format!("enum {}", transformed_enum_name)));
181
182    let enum_block = values
183      .into_iter()
184      .enumerate()
185      .fold(enum_block, |acc, (i, value)| {
186        let value_name = value.value.clone();
187
188        acc.line(format!("{} = {};", value_name, i))
189      });
190
191    acc.line_skip(1).block(enum_block)
192  })
193}