protobuf_dbml/transpiler/
mod.rs1use 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 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 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}