1use std::fmt::Write as _;
35
36#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct ColumnSchema {
39 pub name: String,
41 pub rust_type: String,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct TableSchema {
48 pub name: String,
50 pub columns: Vec<ColumnSchema>,
52}
53
54pub struct SchemaGenerator {
56 header: String,
58 emit_use: bool,
60 emit_model_structs: bool,
65 model_derives: String,
70}
71
72impl Default for SchemaGenerator {
73 fn default() -> Self {
74 Self::new()
75 }
76}
77
78impl SchemaGenerator {
79 pub fn new() -> Self {
81 Self {
82 header: format!(
83 "// Auto-generated by sz-orm-cli generate schema at {}\n\
84 // DO NOT EDIT MANUALLY — re-run the command to refresh.\n\
85 //\n\
86 // This file contains typed_query! table declarations\n\
87 // enabling compile-time column-name verification.\n",
88 chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC")
89 ),
90 emit_use: true,
91 emit_model_structs: false,
92 model_derives: "Debug, Clone, serde::Serialize, serde::Deserialize".to_string(),
93 }
94 }
95
96 pub fn with_header(mut self, header: impl Into<String>) -> Self {
98 self.header = header.into();
99 self
100 }
101
102 pub fn emit_use(mut self, emit: bool) -> Self {
104 self.emit_use = emit;
105 self
106 }
107
108 pub fn emit_model_structs(mut self, emit: bool) -> Self {
125 self.emit_model_structs = emit;
126 self
127 }
128
129 pub fn with_model_derives(mut self, derives: impl Into<String>) -> Self {
134 self.model_derives = derives.into();
135 self
136 }
137
138 pub fn generate(&self, tables: &[TableSchema]) -> String {
140 let mut out = String::new();
141
142 writeln!(out, "{}", self.header).expect("write to String is infallible");
144 writeln!(out).expect("write to String is infallible");
145
146 if self.emit_use {
148 writeln!(out, "use sz_orm_core::typed_query;").expect("write to String is infallible");
149 if self.emit_model_structs
152 && !self.model_derives.is_empty()
153 && self.model_derives.contains("serde::")
154 {
155 writeln!(out, "use serde::{{Serialize, Deserialize}};")
156 .expect("write to String is infallible");
157 }
158 writeln!(out).expect("write to String is infallible");
159 }
160
161 if self.emit_model_structs {
163 for (idx, table) in tables.iter().enumerate() {
164 if idx > 0 {
165 writeln!(out).expect("write to String is infallible");
166 }
167 write!(out, "{}", self.render_model_struct(table))
168 .expect("write to String is infallible");
169 }
170 if !tables.is_empty() {
172 writeln!(out).expect("write to String is infallible");
173 writeln!(out).expect("write to String is infallible");
174 }
175 }
176
177 for (idx, table) in tables.iter().enumerate() {
179 if idx > 0 {
180 writeln!(out).expect("write to String is infallible");
181 }
182 write!(out, "{}", self.render_table(table)).expect("write to String is infallible");
183 }
184
185 out
186 }
187
188 fn render_table(&self, table: &TableSchema) -> String {
190 let mut out = String::new();
191 writeln!(out, "typed_query! {{").expect("write to String is infallible");
192 writeln!(out, " table {} {{", table.name).expect("write to String is infallible");
193 for col in &table.columns {
194 writeln!(out, " {}: {},", col.name, col.rust_type)
195 .expect("write to String is infallible");
196 }
197 writeln!(out, " }}").expect("write to String is infallible");
198 writeln!(out, "}}").expect("write to String is infallible");
199 out
200 }
201
202 fn render_model_struct(&self, table: &TableSchema) -> String {
207 let struct_name = to_pascal_case(&table.name);
208 let mut out = String::new();
209 if !self.model_derives.is_empty() {
211 writeln!(out, "#[derive({})]", self.model_derives)
212 .expect("write to String is infallible");
213 }
214 writeln!(out, "#[table_name = \"{}\"]", table.name).expect("write to String is infallible");
216 writeln!(out, "pub struct {} {{", struct_name).expect("write to String is infallible");
217 for col in &table.columns {
218 writeln!(out, " pub {}: {},", col.name, col.rust_type)
219 .expect("write to String is infallible");
220 }
221 writeln!(out, "}}").expect("write to String is infallible");
222 out
223 }
224}
225
226fn to_pascal_case(input: &str) -> String {
231 let mut result = String::with_capacity(input.len());
232 let mut next_upper = true;
233 for ch in input.chars() {
234 if ch == '_' || ch == '-' || ch == ' ' {
235 next_upper = true;
236 continue;
237 }
238 if next_upper {
239 result.push(ch.to_ascii_uppercase());
240 next_upper = false;
241 } else {
242 result.push(ch);
243 }
244 }
245 result
246}
247
248pub fn sql_type_to_rust(sql_type: &str, nullable: bool) -> String {
265 let lower = sql_type.to_lowercase();
267 let after_param_strip = lower.split('(').next().unwrap_or(&lower).trim();
269 let base_type = after_param_strip
272 .strip_suffix(" unsigned")
273 .or_else(|| after_param_strip.strip_suffix(" zerofill"))
274 .unwrap_or(after_param_strip)
275 .trim();
276
277 let rust = match base_type {
278 "tinyint" => "i8",
280 "smallint" | "int2" | "smallserial" => "i16",
281 "bigint" | "int8" | "bigserial" => "i64",
282 "int" | "integer" | "int4" | "mediumint" | "serial" => "i32",
283 "float8" | "double" | "double precision" => "f64",
285 "float4" | "real" | "float" => "f32",
286 "decimal" | "numeric" => "f64",
287 "bool" | "boolean" => "bool",
289 "blob" | "bytea" | "binary" | "varbinary" | "tinyblob" | "mediumblob" | "longblob" => {
291 "Vec<u8>"
292 }
293 "date" => "String",
295 "datetime" | "timestamp" | "timestamptz" => "String",
296 "time" | "timetz" => "String",
297 "interval" => "String",
298 "json" | "jsonb" => "String",
300 "uuid" => "String",
302 "char" | "varchar" | "text" | "tinytext" | "mediumtext" | "longtext" => "String",
304 "enum" | "set" => "String",
305 _ => "String",
307 };
308
309 if nullable {
310 format!("Option<{}>", rust)
311 } else {
312 rust.to_string()
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319
320 #[test]
321 fn test_sql_type_to_rust_int() {
322 assert_eq!(sql_type_to_rust("INT", false), "i32");
323 assert_eq!(sql_type_to_rust("BIGINT", false), "i64");
324 assert_eq!(sql_type_to_rust("SMALLINT", false), "i16");
325 assert_eq!(sql_type_to_rust("TINYINT", false), "i8");
326 }
327
328 #[test]
329 fn test_sql_type_to_rust_float() {
330 assert_eq!(sql_type_to_rust("FLOAT", false), "f32");
331 assert_eq!(sql_type_to_rust("DOUBLE", false), "f64");
332 assert_eq!(sql_type_to_rust("DECIMAL(10,2)", false), "f64");
333 }
334
335 #[test]
336 fn test_sql_type_to_rust_bool() {
337 assert_eq!(sql_type_to_rust("BOOLEAN", false), "bool");
338 assert_eq!(sql_type_to_rust("TINYINT(1)", false), "i8");
339 }
340
341 #[test]
342 fn test_sql_type_to_rust_string() {
343 assert_eq!(sql_type_to_rust("VARCHAR(255)", false), "String");
344 assert_eq!(sql_type_to_rust("TEXT", false), "String");
345 assert_eq!(sql_type_to_rust("CHAR(36)", false), "String");
346 }
347
348 #[test]
349 fn test_sql_type_to_rust_binary() {
350 assert_eq!(sql_type_to_rust("BLOB", false), "Vec<u8>");
351 assert_eq!(sql_type_to_rust("BYTEA", false), "Vec<u8>");
352 }
353
354 #[test]
355 fn test_sql_type_to_rust_nullable() {
356 assert_eq!(sql_type_to_rust("INT", true), "Option<i32>");
357 assert_eq!(sql_type_to_rust("VARCHAR(255)", true), "Option<String>");
358 }
359
360 #[test]
361 fn test_sql_type_to_rust_pg_types() {
362 assert_eq!(sql_type_to_rust("int8", false), "i64");
363 assert_eq!(sql_type_to_rust("int2", false), "i16");
364 assert_eq!(sql_type_to_rust("float8", false), "f64");
365 assert_eq!(sql_type_to_rust("float4", false), "f32");
366 assert_eq!(sql_type_to_rust("bytea", false), "Vec<u8>");
367 }
368
369 #[test]
370 fn test_sql_type_to_rust_json_uuid() {
371 assert_eq!(sql_type_to_rust("JSON", false), "String");
372 assert_eq!(sql_type_to_rust("JSONB", false), "String");
373 assert_eq!(sql_type_to_rust("UUID", false), "String");
374 }
375
376 #[test]
377 fn test_schema_generator_single_table() {
378 let gen = SchemaGenerator::new().emit_use(false);
379 let tables = vec![TableSchema {
380 name: "users".to_string(),
381 columns: vec![
382 ColumnSchema {
383 name: "id".to_string(),
384 rust_type: "i64".to_string(),
385 },
386 ColumnSchema {
387 name: "name".to_string(),
388 rust_type: "String".to_string(),
389 },
390 ],
391 }];
392 let output = gen.generate(&tables);
393
394 assert!(output.contains("table users {"));
395 assert!(output.contains("id: i64,"));
396 assert!(output.contains("name: String,"));
397 assert!(output.contains("typed_query! {"));
398 }
399
400 #[test]
401 fn test_schema_generator_multiple_tables() {
402 let gen = SchemaGenerator::new().emit_use(false);
403 let tables = vec![
404 TableSchema {
405 name: "users".to_string(),
406 columns: vec![ColumnSchema {
407 name: "id".to_string(),
408 rust_type: "i64".to_string(),
409 }],
410 },
411 TableSchema {
412 name: "orders".to_string(),
413 columns: vec![ColumnSchema {
414 name: "order_id".to_string(),
415 rust_type: "i64".to_string(),
416 }],
417 },
418 ];
419 let output = gen.generate(&tables);
420
421 assert!(output.contains("table users {"));
422 assert!(output.contains("table orders {"));
423 let users_end = output.find("}").unwrap();
425 let orders_start = output.find("table orders").unwrap();
426 let between = &output[users_end..orders_start];
427 assert!(between.contains("\n\n"));
428 }
429
430 #[test]
431 fn test_schema_generator_with_use_statement() {
432 let gen = SchemaGenerator::new().emit_use(true);
433 let tables = vec![TableSchema {
434 name: "t".to_string(),
435 columns: vec![],
436 }];
437 let output = gen.generate(&tables);
438
439 assert!(output.contains("use sz_orm_core::typed_query;"));
440 }
441
442 #[test]
443 fn test_schema_generator_header() {
444 let gen = SchemaGenerator::new();
445 let output = gen.generate(&[]);
446 assert!(output.contains("Auto-generated"));
447 assert!(output.contains("DO NOT EDIT MANUALLY"));
448 }
449
450 #[test]
451 fn test_schema_generator_custom_header() {
452 let gen = SchemaGenerator::new().with_header("// Custom header\n");
453 let output = gen.generate(&[]);
454 assert!(output.starts_with("// Custom header"));
455 assert!(!output.contains("Auto-generated"));
456 }
457
458 #[test]
459 fn test_schema_generator_empty_tables() {
460 let gen = SchemaGenerator::new().emit_use(false);
461 let output = gen.generate(&[]);
462 assert!(output.contains("Auto-generated"));
464 assert!(!output.contains("typed_query! {"));
467 }
468
469 #[test]
470 fn test_schema_generator_option_type() {
471 let gen = SchemaGenerator::new().emit_use(false);
472 let tables = vec![TableSchema {
473 name: "products".to_string(),
474 columns: vec![ColumnSchema {
475 name: "price".to_string(),
476 rust_type: "Option<f64>".to_string(),
477 }],
478 }];
479 let output = gen.generate(&tables);
480
481 assert!(output.contains("price: Option<f64>,"));
482 }
483
484 #[test]
485 fn test_schema_generator_compound_type() {
486 let gen = SchemaGenerator::new().emit_use(false);
487 let tables = vec![TableSchema {
488 name: "files".to_string(),
489 columns: vec![ColumnSchema {
490 name: "content".to_string(),
491 rust_type: "Vec<u8>".to_string(),
492 }],
493 }];
494 let output = gen.generate(&tables);
495
496 assert!(output.contains("content: Vec<u8>,"));
497 }
498
499 #[test]
500 fn test_generated_code_is_valid_syntax() {
501 let gen = SchemaGenerator::new().emit_use(false);
503 let tables = vec![TableSchema {
504 name: "typed_validate_test".to_string(),
505 columns: vec![
506 ColumnSchema {
507 name: "id".to_string(),
508 rust_type: "i64".to_string(),
509 },
510 ColumnSchema {
511 name: "name".to_string(),
512 rust_type: "String".to_string(),
513 },
514 ],
515 }];
516 let output = gen.generate(&tables);
517
518 assert!(output.contains("typed_query! {"));
520 assert!(output.contains("table typed_validate_test {"));
521 assert!(output.contains("id: i64,"));
522 assert!(output.contains("name: String,"));
523 let count_open = output.matches("typed_query! {").count();
525 let count_close = output.matches("}\n}").count();
526 assert_eq!(count_open, 1);
527 assert_eq!(count_close, 1);
528 }
529
530 #[test]
533 fn test_sql_type_to_rust_interval_not_int() {
534 assert_eq!(sql_type_to_rust("INTERVAL", false), "String");
536 assert_eq!(sql_type_to_rust("interval", false), "String");
537 assert_eq!(sql_type_to_rust("INTERVAL DAY TO SECOND", false), "String");
539 }
540
541 #[test]
542 fn test_sql_type_to_rust_mediumint() {
543 assert_eq!(sql_type_to_rust("MEDIUMINT", false), "i32");
545 assert_eq!(sql_type_to_rust("MEDIUMINT(8)", false), "i32");
546 }
547
548 #[test]
549 fn test_sql_type_to_rust_integer_alias() {
550 assert_eq!(sql_type_to_rust("INTEGER", false), "i32");
552 assert_eq!(sql_type_to_rust("INTEGER(11)", false), "i32");
553 }
554
555 #[test]
556 fn test_sql_type_to_rust_serial_types() {
557 assert_eq!(sql_type_to_rust("SERIAL", false), "i32");
559 assert_eq!(sql_type_to_rust("BIGSERIAL", false), "i64");
560 assert_eq!(sql_type_to_rust("SMALLSERIAL", false), "i16");
561 }
562
563 #[test]
564 fn test_sql_type_to_rust_unsigned_suffix() {
565 assert_eq!(sql_type_to_rust("INT UNSIGNED", false), "i32");
567 assert_eq!(sql_type_to_rust("BIGINT UNSIGNED", false), "i64");
568 assert_eq!(sql_type_to_rust("TINYINT UNSIGNED", false), "i8");
569 assert_eq!(sql_type_to_rust("SMALLINT UNSIGNED", false), "i16");
570 assert_eq!(sql_type_to_rust("MEDIUMINT UNSIGNED", false), "i32");
571 }
572
573 #[test]
574 fn test_sql_type_to_rust_zerofill_suffix() {
575 assert_eq!(sql_type_to_rust("INT ZEROFILL", false), "i32");
577 assert_eq!(sql_type_to_rust("INT(4) ZEROFILL", false), "i32");
578 }
579
580 #[test]
581 fn test_sql_type_to_rust_timestamptz() {
582 assert_eq!(sql_type_to_rust("TIMESTAMPTZ", false), "String");
584 assert_eq!(sql_type_to_rust("timestamptz", false), "String");
585 }
586
587 #[test]
588 fn test_sql_type_to_rust_timetz() {
589 assert_eq!(sql_type_to_rust("TIMETZ", false), "String");
591 assert_eq!(sql_type_to_rust("timetz", false), "String");
592 }
593
594 #[test]
595 fn test_sql_type_to_rust_double_precision() {
596 assert_eq!(sql_type_to_rust("DOUBLE PRECISION", false), "f64");
598 assert_eq!(sql_type_to_rust("double precision", false), "f64");
599 }
600
601 #[test]
602 fn test_sql_type_to_rust_varbinary() {
603 assert_eq!(sql_type_to_rust("VARBINARY(255)", false), "Vec<u8>");
605 assert_eq!(sql_type_to_rust("VARBINARY", false), "Vec<u8>");
606 }
607
608 #[test]
609 fn test_sql_type_to_rust_blob_variants() {
610 assert_eq!(sql_type_to_rust("TINYBLOB", false), "Vec<u8>");
612 assert_eq!(sql_type_to_rust("MEDIUMBLOB", false), "Vec<u8>");
613 assert_eq!(sql_type_to_rust("LONGBLOB", false), "Vec<u8>");
614 }
615
616 #[test]
617 fn test_sql_type_to_rust_text_variants() {
618 assert_eq!(sql_type_to_rust("TINYTEXT", false), "String");
620 assert_eq!(sql_type_to_rust("MEDIUMTEXT", false), "String");
621 assert_eq!(sql_type_to_rust("LONGTEXT", false), "String");
622 }
623
624 #[test]
625 fn test_sql_type_to_rust_enum_and_set() {
626 assert_eq!(sql_type_to_rust("ENUM('a','b')", false), "String");
628 assert_eq!(sql_type_to_rust("SET('a','b')", false), "String");
629 }
630
631 #[test]
632 fn test_sql_type_to_rust_decimal_with_space() {
633 assert_eq!(sql_type_to_rust("DECIMAL(10, 2)", false), "f64");
635 assert_eq!(sql_type_to_rust("NUMERIC(8, 2)", false), "f64");
636 }
637
638 #[test]
639 fn test_sql_type_to_rust_unknown_defaults_string() {
640 assert_eq!(sql_type_to_rust("UNKNOWN_TYPE", false), "String");
642 assert_eq!(sql_type_to_rust("citext", false), "String");
643 assert_eq!(sql_type_to_rust("money", false), "String");
644 }
645
646 #[test]
647 fn test_sql_type_to_rust_interval_nullable() {
648 assert_eq!(sql_type_to_rust("INTERVAL", true), "Option<String>");
650 }
651
652 #[test]
657 fn test_to_pascal_case_basic() {
658 assert_eq!(to_pascal_case("users"), "Users");
659 assert_eq!(to_pascal_case("orders"), "Orders");
660 }
661
662 #[test]
663 fn test_to_pascal_case_snake_case() {
664 assert_eq!(to_pascal_case("order_items"), "OrderItems");
665 assert_eq!(to_pascal_case("user_profiles"), "UserProfiles");
666 }
667
668 #[test]
669 fn test_to_pascal_case_kebab_case() {
670 assert_eq!(to_pascal_case("user-profile"), "UserProfile");
671 assert_eq!(to_pascal_case("order-item"), "OrderItem");
672 }
673
674 #[test]
675 fn test_to_pascal_case_with_spaces() {
676 assert_eq!(to_pascal_case("user profile"), "UserProfile");
677 }
678
679 #[test]
680 fn test_to_pascal_case_single_char() {
681 assert_eq!(to_pascal_case("a"), "A");
682 assert_eq!(to_pascal_case("_a"), "A");
683 }
684
685 #[test]
686 fn test_to_pascal_case_already_pascal() {
687 assert_eq!(to_pascal_case("Users"), "Users");
689 }
690
691 #[test]
692 fn test_emit_model_structs_generates_struct() {
693 let gen = SchemaGenerator::new()
694 .emit_use(false)
695 .emit_model_structs(true);
696 let tables = vec![TableSchema {
697 name: "users".to_string(),
698 columns: vec![
699 ColumnSchema {
700 name: "id".to_string(),
701 rust_type: "i64".to_string(),
702 },
703 ColumnSchema {
704 name: "name".to_string(),
705 rust_type: "String".to_string(),
706 },
707 ],
708 }];
709 let output = gen.generate(&tables);
710
711 assert!(output.contains("pub struct Users {"));
713 assert!(output.contains("pub id: i64,"));
714 assert!(output.contains("pub name: String,"));
715 assert!(output.contains("#[derive("));
717 assert!(output.contains("Debug"));
718 assert!(output.contains("Clone"));
719 assert!(output.contains("#[table_name = \"users\"]"));
721 assert!(output.contains("typed_query! {"));
723 assert!(output.contains("table users {"));
724 }
725
726 #[test]
727 fn test_emit_model_structs_disabled_by_default() {
728 let gen = SchemaGenerator::new().emit_use(false);
729 let tables = vec![TableSchema {
730 name: "users".to_string(),
731 columns: vec![ColumnSchema {
732 name: "id".to_string(),
733 rust_type: "i64".to_string(),
734 }],
735 }];
736 let output = gen.generate(&tables);
737
738 assert!(!output.contains("pub struct Users"));
740 assert!(output.contains("typed_query! {"));
742 }
743
744 #[test]
745 fn test_emit_model_structs_multiple_tables() {
746 let gen = SchemaGenerator::new()
747 .emit_use(false)
748 .emit_model_structs(true);
749 let tables = vec![
750 TableSchema {
751 name: "users".to_string(),
752 columns: vec![ColumnSchema {
753 name: "id".to_string(),
754 rust_type: "i64".to_string(),
755 }],
756 },
757 TableSchema {
758 name: "order_items".to_string(),
759 columns: vec![ColumnSchema {
760 name: "item_id".to_string(),
761 rust_type: "i64".to_string(),
762 }],
763 },
764 ];
765 let output = gen.generate(&tables);
766
767 assert!(output.contains("pub struct Users {"));
769 assert!(output.contains("pub struct OrderItems {"));
770 }
771
772 #[test]
773 fn test_emit_model_structs_with_custom_derives() {
774 let gen = SchemaGenerator::new()
775 .emit_use(false)
776 .emit_model_structs(true)
777 .with_model_derives("Debug, Clone");
778 let tables = vec![TableSchema {
779 name: "users".to_string(),
780 columns: vec![ColumnSchema {
781 name: "id".to_string(),
782 rust_type: "i64".to_string(),
783 }],
784 }];
785 let output = gen.generate(&tables);
786
787 assert!(output.contains("#[derive(Debug, Clone)]"));
789 assert!(!output.contains("serde::Serialize"));
791 }
792
793 #[test]
794 fn test_emit_model_structs_with_empty_derives() {
795 let gen = SchemaGenerator::new()
796 .emit_use(false)
797 .emit_model_structs(true)
798 .with_model_derives("");
799 let tables = vec![TableSchema {
800 name: "users".to_string(),
801 columns: vec![ColumnSchema {
802 name: "id".to_string(),
803 rust_type: "i64".to_string(),
804 }],
805 }];
806 let output = gen.generate(&tables);
807
808 assert!(!output.contains("#[derive("));
810 assert!(output.contains("pub struct Users {"));
812 }
813
814 #[test]
815 fn test_emit_model_structs_with_nullable_fields() {
816 let gen = SchemaGenerator::new()
817 .emit_use(false)
818 .emit_model_structs(true);
819 let tables = vec![TableSchema {
820 name: "products".to_string(),
821 columns: vec![
822 ColumnSchema {
823 name: "id".to_string(),
824 rust_type: "i64".to_string(),
825 },
826 ColumnSchema {
827 name: "price".to_string(),
828 rust_type: "Option<f64>".to_string(),
829 },
830 ColumnSchema {
831 name: "description".to_string(),
832 rust_type: "Option<String>".to_string(),
833 },
834 ],
835 }];
836 let output = gen.generate(&tables);
837
838 assert!(output.contains("pub struct Products {"));
839 assert!(output.contains("pub id: i64,"));
840 assert!(output.contains("pub price: Option<f64>,"));
841 assert!(output.contains("pub description: Option<String>,"));
842 }
843
844 #[test]
845 fn test_emit_model_structs_with_serde_use() {
846 let gen = SchemaGenerator::new()
847 .emit_use(true)
848 .emit_model_structs(true);
849 let tables = vec![TableSchema {
850 name: "users".to_string(),
851 columns: vec![ColumnSchema {
852 name: "id".to_string(),
853 rust_type: "i64".to_string(),
854 }],
855 }];
856 let output = gen.generate(&tables);
857
858 assert!(output.contains("use serde::{Serialize, Deserialize};"));
860 }
861
862 #[test]
863 fn test_emit_model_structs_empty_tables() {
864 let gen = SchemaGenerator::new()
865 .emit_use(false)
866 .emit_model_structs(true);
867 let output = gen.generate(&[]);
868
869 assert!(!output.contains("pub struct"));
871 assert!(output.contains("Auto-generated"));
873 }
874}