@typokit/plugin-axum 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +186 -0
- package/dist/index.js.map +1 -0
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.linux-x64-musl.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +57 -0
- package/src/index.ts +309 -0
- package/src/lib.rs +80 -0
- package/src/rust_codegen/database.rs +898 -0
- package/src/rust_codegen/handlers.rs +1111 -0
- package/src/rust_codegen/middleware.rs +156 -0
- package/src/rust_codegen/mod.rs +91 -0
- package/src/rust_codegen/project.rs +593 -0
- package/src/rust_codegen/router.rs +385 -0
- package/src/rust_codegen/services.rs +476 -0
- package/src/rust_codegen/structs.rs +1363 -0
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
use std::collections::HashMap;
|
|
2
|
+
|
|
3
|
+
use typokit_transform_native::type_extractor::TypeMetadata;
|
|
4
|
+
use super::GeneratedOutput;
|
|
5
|
+
|
|
6
|
+
/// Generate per-entity service stub files and a services/mod.rs.
|
|
7
|
+
///
|
|
8
|
+
/// Produces:
|
|
9
|
+
/// - `src/services/{entity}.rs` per @table entity with CRUD function stubs (overwrite: false)
|
|
10
|
+
/// - `src/services/mod.rs` with pub mod declarations (overwrite: true)
|
|
11
|
+
pub fn generate_services(type_map: &HashMap<String, TypeMetadata>) -> Vec<GeneratedOutput> {
|
|
12
|
+
let mut outputs = Vec::new();
|
|
13
|
+
let mut module_names: Vec<String> = Vec::new();
|
|
14
|
+
|
|
15
|
+
// Collect @table entities sorted by name for deterministic output
|
|
16
|
+
let mut table_entities: Vec<&TypeMetadata> = type_map
|
|
17
|
+
.values()
|
|
18
|
+
.filter(|meta| is_table_entity(meta))
|
|
19
|
+
.collect();
|
|
20
|
+
table_entities.sort_by_key(|meta| meta.name.clone());
|
|
21
|
+
|
|
22
|
+
for meta in &table_entities {
|
|
23
|
+
let snake_name = to_snake_case(&meta.name);
|
|
24
|
+
module_names.push(snake_name.clone());
|
|
25
|
+
|
|
26
|
+
let id_type = find_id_type(meta);
|
|
27
|
+
let input_struct = format!("{}WithoutId", meta.name);
|
|
28
|
+
let content = generate_service_file(&meta.name, &snake_name, &id_type, &input_struct);
|
|
29
|
+
outputs.push(GeneratedOutput {
|
|
30
|
+
path: format!("src/services/{}.rs", snake_name),
|
|
31
|
+
content,
|
|
32
|
+
overwrite: false,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module_names.sort();
|
|
37
|
+
outputs.push(generate_services_mod(&module_names));
|
|
38
|
+
|
|
39
|
+
outputs
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Generate service file content for a single entity.
|
|
43
|
+
fn generate_service_file(
|
|
44
|
+
entity_name: &str,
|
|
45
|
+
snake_name: &str,
|
|
46
|
+
id_type: &str,
|
|
47
|
+
input_struct: &str,
|
|
48
|
+
) -> String {
|
|
49
|
+
let mut s = String::new();
|
|
50
|
+
|
|
51
|
+
s.push_str("// AUTO-GENERATED by @typokit/transform-native\n");
|
|
52
|
+
s.push_str("// This file will NOT be overwritten — edit freely.\n\n");
|
|
53
|
+
|
|
54
|
+
s.push_str("use sqlx::PgPool;\n");
|
|
55
|
+
s.push_str("use crate::error::AppError;\n");
|
|
56
|
+
s.push_str("use crate::models;\n\n");
|
|
57
|
+
|
|
58
|
+
// list
|
|
59
|
+
s.push_str(&format!(
|
|
60
|
+
"/// List all {}s with pagination.\n",
|
|
61
|
+
snake_name
|
|
62
|
+
));
|
|
63
|
+
s.push_str(&format!(
|
|
64
|
+
"pub async fn list_{}(\n",
|
|
65
|
+
snake_name
|
|
66
|
+
));
|
|
67
|
+
s.push_str(" _pool: &PgPool,\n");
|
|
68
|
+
s.push_str(" _page: u32,\n");
|
|
69
|
+
s.push_str(" _page_size: u32,\n");
|
|
70
|
+
s.push_str(&format!(
|
|
71
|
+
") -> Result<Vec<models::{}>, AppError> {{\n",
|
|
72
|
+
entity_name
|
|
73
|
+
));
|
|
74
|
+
s.push_str(" // TODO: Implement service-layer list logic\n");
|
|
75
|
+
s.push_str(" todo!()\n");
|
|
76
|
+
s.push_str("}\n\n");
|
|
77
|
+
|
|
78
|
+
// get_by_id
|
|
79
|
+
s.push_str(&format!(
|
|
80
|
+
"/// Get a {} by ID.\n",
|
|
81
|
+
snake_name
|
|
82
|
+
));
|
|
83
|
+
s.push_str(&format!(
|
|
84
|
+
"pub async fn get_{}_by_id(\n",
|
|
85
|
+
snake_name
|
|
86
|
+
));
|
|
87
|
+
s.push_str(" _pool: &PgPool,\n");
|
|
88
|
+
s.push_str(&format!(
|
|
89
|
+
" _id: &{},\n",
|
|
90
|
+
id_type
|
|
91
|
+
));
|
|
92
|
+
s.push_str(&format!(
|
|
93
|
+
") -> Result<Option<models::{}>, AppError> {{\n",
|
|
94
|
+
entity_name
|
|
95
|
+
));
|
|
96
|
+
s.push_str(" // TODO: Implement service-layer get_by_id logic\n");
|
|
97
|
+
s.push_str(" todo!()\n");
|
|
98
|
+
s.push_str("}\n\n");
|
|
99
|
+
|
|
100
|
+
// create
|
|
101
|
+
s.push_str(&format!(
|
|
102
|
+
"/// Create a new {}.\n",
|
|
103
|
+
snake_name
|
|
104
|
+
));
|
|
105
|
+
s.push_str(&format!(
|
|
106
|
+
"pub async fn create_{}(\n",
|
|
107
|
+
snake_name
|
|
108
|
+
));
|
|
109
|
+
s.push_str(" _pool: &PgPool,\n");
|
|
110
|
+
s.push_str(&format!(
|
|
111
|
+
" _input: &models::{},\n",
|
|
112
|
+
input_struct
|
|
113
|
+
));
|
|
114
|
+
s.push_str(&format!(
|
|
115
|
+
") -> Result<models::{}, AppError> {{\n",
|
|
116
|
+
entity_name
|
|
117
|
+
));
|
|
118
|
+
s.push_str(" // TODO: Implement service-layer create logic\n");
|
|
119
|
+
s.push_str(" todo!()\n");
|
|
120
|
+
s.push_str("}\n\n");
|
|
121
|
+
|
|
122
|
+
// update
|
|
123
|
+
s.push_str(&format!(
|
|
124
|
+
"/// Update a {} by ID.\n",
|
|
125
|
+
snake_name
|
|
126
|
+
));
|
|
127
|
+
s.push_str(&format!(
|
|
128
|
+
"pub async fn update_{}(\n",
|
|
129
|
+
snake_name
|
|
130
|
+
));
|
|
131
|
+
s.push_str(" _pool: &PgPool,\n");
|
|
132
|
+
s.push_str(&format!(
|
|
133
|
+
" _id: &{},\n",
|
|
134
|
+
id_type
|
|
135
|
+
));
|
|
136
|
+
s.push_str(&format!(
|
|
137
|
+
" _input: &models::{},\n",
|
|
138
|
+
input_struct
|
|
139
|
+
));
|
|
140
|
+
s.push_str(&format!(
|
|
141
|
+
") -> Result<Option<models::{}>, AppError> {{\n",
|
|
142
|
+
entity_name
|
|
143
|
+
));
|
|
144
|
+
s.push_str(" // TODO: Implement service-layer update logic\n");
|
|
145
|
+
s.push_str(" todo!()\n");
|
|
146
|
+
s.push_str("}\n\n");
|
|
147
|
+
|
|
148
|
+
// delete
|
|
149
|
+
s.push_str(&format!(
|
|
150
|
+
"/// Delete a {} by ID.\n",
|
|
151
|
+
snake_name
|
|
152
|
+
));
|
|
153
|
+
s.push_str(&format!(
|
|
154
|
+
"pub async fn delete_{}(\n",
|
|
155
|
+
snake_name
|
|
156
|
+
));
|
|
157
|
+
s.push_str(" _pool: &PgPool,\n");
|
|
158
|
+
s.push_str(&format!(
|
|
159
|
+
" _id: &{},\n",
|
|
160
|
+
id_type
|
|
161
|
+
));
|
|
162
|
+
s.push_str(&format!(
|
|
163
|
+
") -> Result<Option<models::{}>, AppError> {{\n",
|
|
164
|
+
entity_name
|
|
165
|
+
));
|
|
166
|
+
s.push_str(" // TODO: Implement service-layer delete logic\n");
|
|
167
|
+
s.push_str(" todo!()\n");
|
|
168
|
+
s.push_str("}\n");
|
|
169
|
+
|
|
170
|
+
s
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// Generate the `src/services/mod.rs` with pub mod declarations.
|
|
174
|
+
fn generate_services_mod(module_names: &[String]) -> GeneratedOutput {
|
|
175
|
+
let mut output = String::new();
|
|
176
|
+
output.push_str("// AUTO-GENERATED by @typokit/transform-native — DO NOT EDIT\n\n");
|
|
177
|
+
|
|
178
|
+
for name in module_names {
|
|
179
|
+
output.push_str(&format!("pub mod {};\n", name));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
GeneratedOutput {
|
|
183
|
+
path: "src/services/mod.rs".to_string(),
|
|
184
|
+
content: output,
|
|
185
|
+
overwrite: true,
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/// Check if a TypeMetadata has the @table JSDoc annotation.
|
|
190
|
+
fn is_table_entity(meta: &TypeMetadata) -> bool {
|
|
191
|
+
meta.jsdoc
|
|
192
|
+
.as_ref()
|
|
193
|
+
.map(|j| j.contains_key("table"))
|
|
194
|
+
.unwrap_or(false)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/// Find the Rust type for the @id property.
|
|
198
|
+
fn find_id_type(meta: &TypeMetadata) -> String {
|
|
199
|
+
for (_name, prop) in &meta.properties {
|
|
200
|
+
if prop
|
|
201
|
+
.jsdoc
|
|
202
|
+
.as_ref()
|
|
203
|
+
.map(|j| j.contains_key("id"))
|
|
204
|
+
.unwrap_or(false)
|
|
205
|
+
{
|
|
206
|
+
return id_rust_type(&prop.type_str);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if let Some(prop) = meta.properties.get("id") {
|
|
210
|
+
return id_rust_type(&prop.type_str);
|
|
211
|
+
}
|
|
212
|
+
"String".to_string()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/// Map TS type to Rust type for ID fields.
|
|
216
|
+
fn id_rust_type(type_str: &str) -> String {
|
|
217
|
+
match type_str {
|
|
218
|
+
"string" => "String".to_string(),
|
|
219
|
+
"number" => "i64".to_string(),
|
|
220
|
+
_ => "String".to_string(),
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/// Convert a camelCase or PascalCase string to snake_case.
|
|
225
|
+
fn to_snake_case(s: &str) -> String {
|
|
226
|
+
let mut result = String::new();
|
|
227
|
+
for (i, c) in s.chars().enumerate() {
|
|
228
|
+
if c.is_uppercase() {
|
|
229
|
+
if i > 0 {
|
|
230
|
+
result.push('_');
|
|
231
|
+
}
|
|
232
|
+
result.push(c.to_lowercase().next().unwrap());
|
|
233
|
+
} else {
|
|
234
|
+
result.push(c);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
result
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
#[cfg(test)]
|
|
241
|
+
mod tests {
|
|
242
|
+
use super::*;
|
|
243
|
+
use typokit_transform_native::type_extractor::PropertyMetadata;
|
|
244
|
+
|
|
245
|
+
fn make_table_entity(name: &str) -> TypeMetadata {
|
|
246
|
+
let mut jsdoc = HashMap::new();
|
|
247
|
+
jsdoc.insert("table".to_string(), "".to_string());
|
|
248
|
+
|
|
249
|
+
let mut properties = HashMap::new();
|
|
250
|
+
let mut id_jsdoc = HashMap::new();
|
|
251
|
+
id_jsdoc.insert("id".to_string(), "".to_string());
|
|
252
|
+
id_jsdoc.insert("generated".to_string(), "uuid".to_string());
|
|
253
|
+
properties.insert(
|
|
254
|
+
"id".to_string(),
|
|
255
|
+
PropertyMetadata {
|
|
256
|
+
type_str: "string".to_string(),
|
|
257
|
+
optional: false,
|
|
258
|
+
jsdoc: Some(id_jsdoc),
|
|
259
|
+
},
|
|
260
|
+
);
|
|
261
|
+
properties.insert(
|
|
262
|
+
"name".to_string(),
|
|
263
|
+
PropertyMetadata {
|
|
264
|
+
type_str: "string".to_string(),
|
|
265
|
+
optional: false,
|
|
266
|
+
jsdoc: None,
|
|
267
|
+
},
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
TypeMetadata {
|
|
271
|
+
name: name.to_string(),
|
|
272
|
+
properties,
|
|
273
|
+
jsdoc: Some(jsdoc),
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
fn make_non_table_entity(name: &str) -> TypeMetadata {
|
|
278
|
+
let mut properties = HashMap::new();
|
|
279
|
+
properties.insert(
|
|
280
|
+
"id".to_string(),
|
|
281
|
+
PropertyMetadata {
|
|
282
|
+
type_str: "string".to_string(),
|
|
283
|
+
optional: false,
|
|
284
|
+
jsdoc: None,
|
|
285
|
+
},
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
TypeMetadata {
|
|
289
|
+
name: name.to_string(),
|
|
290
|
+
properties,
|
|
291
|
+
jsdoc: None,
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
#[test]
|
|
296
|
+
fn test_generate_services_empty_type_map() {
|
|
297
|
+
let type_map = HashMap::new();
|
|
298
|
+
let outputs = generate_services(&type_map);
|
|
299
|
+
// Should only produce mod.rs
|
|
300
|
+
assert_eq!(outputs.len(), 1);
|
|
301
|
+
assert_eq!(outputs[0].path, "src/services/mod.rs");
|
|
302
|
+
assert!(outputs[0].overwrite);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
#[test]
|
|
306
|
+
fn test_generate_services_skips_non_table_entities() {
|
|
307
|
+
let mut type_map = HashMap::new();
|
|
308
|
+
type_map.insert("Config".to_string(), make_non_table_entity("Config"));
|
|
309
|
+
|
|
310
|
+
let outputs = generate_services(&type_map);
|
|
311
|
+
// Only mod.rs — no service file for non-table entity
|
|
312
|
+
assert_eq!(outputs.len(), 1);
|
|
313
|
+
assert_eq!(outputs[0].path, "src/services/mod.rs");
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
#[test]
|
|
317
|
+
fn test_generate_services_for_table_entity() {
|
|
318
|
+
let mut type_map = HashMap::new();
|
|
319
|
+
type_map.insert("User".to_string(), make_table_entity("User"));
|
|
320
|
+
|
|
321
|
+
let outputs = generate_services(&type_map);
|
|
322
|
+
assert_eq!(outputs.len(), 2); // user.rs + mod.rs
|
|
323
|
+
|
|
324
|
+
let user_service = outputs.iter().find(|o| o.path == "src/services/user.rs").unwrap();
|
|
325
|
+
assert!(!user_service.overwrite);
|
|
326
|
+
assert!(user_service.content.contains("pub async fn list_user("));
|
|
327
|
+
assert!(user_service.content.contains("pub async fn get_user_by_id("));
|
|
328
|
+
assert!(user_service.content.contains("pub async fn create_user("));
|
|
329
|
+
assert!(user_service.content.contains("pub async fn update_user("));
|
|
330
|
+
assert!(user_service.content.contains("pub async fn delete_user("));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
#[test]
|
|
334
|
+
fn test_service_file_imports_model_types() {
|
|
335
|
+
let mut type_map = HashMap::new();
|
|
336
|
+
type_map.insert("User".to_string(), make_table_entity("User"));
|
|
337
|
+
|
|
338
|
+
let outputs = generate_services(&type_map);
|
|
339
|
+
let user_service = outputs.iter().find(|o| o.path == "src/services/user.rs").unwrap();
|
|
340
|
+
assert!(user_service.content.contains("use crate::models;"));
|
|
341
|
+
assert!(user_service.content.contains("models::User"));
|
|
342
|
+
assert!(user_service.content.contains("models::UserWithoutId"));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
#[test]
|
|
346
|
+
fn test_service_file_has_correct_signatures() {
|
|
347
|
+
let mut type_map = HashMap::new();
|
|
348
|
+
type_map.insert("User".to_string(), make_table_entity("User"));
|
|
349
|
+
|
|
350
|
+
let outputs = generate_services(&type_map);
|
|
351
|
+
let user_service = outputs.iter().find(|o| o.path == "src/services/user.rs").unwrap();
|
|
352
|
+
|
|
353
|
+
// list: returns Vec
|
|
354
|
+
assert!(user_service.content.contains("-> Result<Vec<models::User>, AppError>"));
|
|
355
|
+
// get_by_id: returns Option
|
|
356
|
+
assert!(user_service.content.contains("-> Result<Option<models::User>, AppError>"));
|
|
357
|
+
// create: returns entity
|
|
358
|
+
assert!(user_service.content.contains(") -> Result<models::User, AppError>"));
|
|
359
|
+
// update: returns Option
|
|
360
|
+
assert!(user_service.content.contains("_input: &models::UserWithoutId"));
|
|
361
|
+
// delete: returns Option
|
|
362
|
+
assert!(user_service.content.contains("pub async fn delete_user("));
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
#[test]
|
|
366
|
+
fn test_service_file_overwrite_false() {
|
|
367
|
+
let mut type_map = HashMap::new();
|
|
368
|
+
type_map.insert("User".to_string(), make_table_entity("User"));
|
|
369
|
+
|
|
370
|
+
let outputs = generate_services(&type_map);
|
|
371
|
+
let user_service = outputs.iter().find(|o| o.path == "src/services/user.rs").unwrap();
|
|
372
|
+
assert!(!user_service.overwrite);
|
|
373
|
+
assert!(user_service.content.contains("will NOT be overwritten"));
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
#[test]
|
|
377
|
+
fn test_services_mod_overwrite_true() {
|
|
378
|
+
let mut type_map = HashMap::new();
|
|
379
|
+
type_map.insert("User".to_string(), make_table_entity("User"));
|
|
380
|
+
|
|
381
|
+
let outputs = generate_services(&type_map);
|
|
382
|
+
let mod_file = outputs.iter().find(|o| o.path == "src/services/mod.rs").unwrap();
|
|
383
|
+
assert!(mod_file.overwrite);
|
|
384
|
+
assert!(mod_file.content.contains("pub mod user;"));
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
#[test]
|
|
388
|
+
fn test_generate_services_multiple_entities_sorted() {
|
|
389
|
+
let mut type_map = HashMap::new();
|
|
390
|
+
type_map.insert("Todo".to_string(), make_table_entity("Todo"));
|
|
391
|
+
type_map.insert("User".to_string(), make_table_entity("User"));
|
|
392
|
+
type_map.insert("Category".to_string(), make_table_entity("Category"));
|
|
393
|
+
|
|
394
|
+
let outputs = generate_services(&type_map);
|
|
395
|
+
// 3 entity files + mod.rs
|
|
396
|
+
assert_eq!(outputs.len(), 4);
|
|
397
|
+
|
|
398
|
+
let mod_file = outputs.iter().find(|o| o.path == "src/services/mod.rs").unwrap();
|
|
399
|
+
let lines: Vec<&str> = mod_file.content.lines().collect();
|
|
400
|
+
let mod_lines: Vec<&&str> = lines.iter().filter(|l| l.starts_with("pub mod")).collect();
|
|
401
|
+
assert_eq!(mod_lines.len(), 3);
|
|
402
|
+
assert_eq!(*mod_lines[0], "pub mod category;");
|
|
403
|
+
assert_eq!(*mod_lines[1], "pub mod todo;");
|
|
404
|
+
assert_eq!(*mod_lines[2], "pub mod user;");
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
#[test]
|
|
408
|
+
fn test_service_id_type_number() {
|
|
409
|
+
let mut type_map = HashMap::new();
|
|
410
|
+
let mut properties = HashMap::new();
|
|
411
|
+
let mut id_jsdoc = HashMap::new();
|
|
412
|
+
id_jsdoc.insert("id".to_string(), "".to_string());
|
|
413
|
+
properties.insert(
|
|
414
|
+
"id".to_string(),
|
|
415
|
+
PropertyMetadata {
|
|
416
|
+
type_str: "number".to_string(),
|
|
417
|
+
optional: false,
|
|
418
|
+
jsdoc: Some(id_jsdoc),
|
|
419
|
+
},
|
|
420
|
+
);
|
|
421
|
+
let mut jsdoc = HashMap::new();
|
|
422
|
+
jsdoc.insert("table".to_string(), "".to_string());
|
|
423
|
+
type_map.insert(
|
|
424
|
+
"Item".to_string(),
|
|
425
|
+
TypeMetadata {
|
|
426
|
+
name: "Item".to_string(),
|
|
427
|
+
properties,
|
|
428
|
+
jsdoc: Some(jsdoc),
|
|
429
|
+
},
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
let outputs = generate_services(&type_map);
|
|
433
|
+
let item_service = outputs.iter().find(|o| o.path == "src/services/item.rs").unwrap();
|
|
434
|
+
assert!(item_service.content.contains("_id: &i64"));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
#[test]
|
|
438
|
+
fn test_service_uses_pool_and_app_error() {
|
|
439
|
+
let mut type_map = HashMap::new();
|
|
440
|
+
type_map.insert("User".to_string(), make_table_entity("User"));
|
|
441
|
+
|
|
442
|
+
let outputs = generate_services(&type_map);
|
|
443
|
+
let user_service = outputs.iter().find(|o| o.path == "src/services/user.rs").unwrap();
|
|
444
|
+
assert!(user_service.content.contains("use sqlx::PgPool;"));
|
|
445
|
+
assert!(user_service.content.contains("use crate::error::AppError;"));
|
|
446
|
+
assert!(user_service.content.contains("_pool: &PgPool"));
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
#[test]
|
|
450
|
+
fn test_service_pagination_params() {
|
|
451
|
+
let mut type_map = HashMap::new();
|
|
452
|
+
type_map.insert("User".to_string(), make_table_entity("User"));
|
|
453
|
+
|
|
454
|
+
let outputs = generate_services(&type_map);
|
|
455
|
+
let user_service = outputs.iter().find(|o| o.path == "src/services/user.rs").unwrap();
|
|
456
|
+
assert!(user_service.content.contains("_page: u32"));
|
|
457
|
+
assert!(user_service.content.contains("_page_size: u32"));
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
#[test]
|
|
461
|
+
fn test_deterministic_output() {
|
|
462
|
+
let mut type_map = HashMap::new();
|
|
463
|
+
type_map.insert("Todo".to_string(), make_table_entity("Todo"));
|
|
464
|
+
type_map.insert("User".to_string(), make_table_entity("User"));
|
|
465
|
+
|
|
466
|
+
let outputs1 = generate_services(&type_map);
|
|
467
|
+
let outputs2 = generate_services(&type_map);
|
|
468
|
+
|
|
469
|
+
assert_eq!(outputs1.len(), outputs2.len());
|
|
470
|
+
for (a, b) in outputs1.iter().zip(outputs2.iter()) {
|
|
471
|
+
assert_eq!(a.path, b.path);
|
|
472
|
+
assert_eq!(a.content, b.content);
|
|
473
|
+
assert_eq!(a.overwrite, b.overwrite);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|