// API-REST-GENERATOR — ENGINEERING REPORT

api-rest-generator v0.2.0 · mid-transition Kotlin/Gradle → Rust/Cargo port · 4 SQL dialects × 4 output targets · dual implementations cohabit · parity-tested across a 27,738-line sample DDL corpus

>_EXECUTIVE SUMMARY

api-rest-generator is a REST backend code generator. Input is a raw CREATE TABLE dump in any of four SQL dialects (MySQL, PostgreSQL, SQLite, MSSQL). Output is a complete REST surface — entities, controllers, DAOs / repositories, migrations, and module wiring — targeting one of four backends (Spring Boot in Java, Kotlin, or Groovy; or Loco in Rust with Axum + SeaORM). Two binaries ship from one crate: api-rest-generator (full multi-target dispatch) and loco-gen (focused Rust-only flag surface, same engine).

Status: mid-transition. The repo currently carries two implementations of the same generator — the original Kotlin/Gradle pipeline (9,094 LOC) and the Rust/Cargo port (3,956 LOC). Both compile and produce output independently. The Rust port is being grown module-for-module against the Kotlin reference, with a parity test (tests/parity_smoke.rs) that runs both pipelines over shared sample DDLs and diffs the output. When parity holds across the full sample corpus + every dialect × target combination, the Kotlin path retires; until then both ship.

4
SQL dialects
4
Output targets
2
Binaries
3,956
Rust LOC
9,094
Kotlin LOC
218
Rust #[test]
806
Kotlin @Test
4
Sample DDLs
27,738
Sample DDL lines

~DIALECT × TARGET MATRIX

Every input dialect feeds every output target. The crate ships sample DDLs covering all four dialect families; the test suite parameterises across (dialect × target) so a regression in any one cell fails CI for both pipelines.

Dialect ↓ / Target →Java (Spring Boot)Kotlin (Spring Boot)Groovy (Spring Boot)Rust/Loco (Axum + SeaORM)
MySQLOKOKOKOK
PostgreSQLOKOKOKOK
SQLiteOKOKOKOK
MSSQLOKOKOKOK

$SAMPLE DDL CORPUS

Real-world reference schemas covering every supported dialect, used as parity-test fixtures and as demo inputs. All four live in samples/.

SampleDialectSourceTablesLines
chinook-sqlite.sqlSQLiteChinook digital media store; [bracketed] identifiers + composite PKs1115,902
northwind-mssql.sqlMSSQLClassic MSSQL; double-quoted identifiers + Order Details space-in-name139,350
pagila-postgresql.sqlPostgreSQLPostgres Sakila port; ALTER TABLE ADD PRIMARY KEY style221,842
sakila-mysql.sqlMySQLDVD rental store; exercises triggers/views/functions filtering16644
Aggregate corpus27,738

Table counts are the README-reported figures for each schema; tests/sample_ddls.rs pins the parse with conservative lower bounds (Sakila ≥15, Chinook ≥11, Pagila ≥15, Northwind ≥10) plus invariants — every table gets a PK (synthetic where needed) and no quote/bracket/tab leaks into the parsed identifiers. The Loco toolchain is not required to run this test, so parser/normalizer regressions are caught in CI without installing loco.


%INTERFACE SURFACE

Two entry points with two operating models. api-rest-generator is config-driven (no CLI flags — it reads src/main/resources/config.properties at a hard-coded relative path); loco-gen is flag-driven with new / generate subcommands.

// config.properties (api-rest-generator + Kotlin pipeline)

KeyValuesDefault
target.folderany pathbuild/generated/src/main/<lang>/
target.packagecom/example/generated(empty)
file.namee.g. mysql_dump.sql(empty)
target.languagejava / kotlin / groovy / rust-loco (alias loco)java (unknown values fall through to java)
database.typemysql / postgresql / sqlite / mssqlmysql

// loco-gen subcommands + flags (Rust/Loco)

FlagDefaultApplies to
--ddl FILE / -drequirednew + generate
--name NAME / -nrequirednew
--out DIR / -o. (new) / required (generate)new + generate
--db DIALECTmysqlnew + generate
--loco-db DBsqlite (or postgres)new
--wireoffnew + generate
--migrateoffnew
--loco-binloconew

loco-gen new shells out to loco new -n <name> --db <loco-db> --bg none --assets none, then writes entities / controllers / migrations into the scaffold. --wire rewrites an idempotent // BEGIN loco-gen routes block in src/app.rs (one .add_route(crate::controllers::<table>::routes()) per entity), preserving scaffold-shipped modules like controllers::home.


&KOTLIN → RUST PORT STATUS

The Rust port mirrors the Kotlin layout module-for-module to keep the diff readable. Each Kotlin source file has (or will have) a matching .rs port; the table below pins which ones are live.

Kotlin sourceRust portStatus
Main.ktsrc/bin/main.rs + src/bin/loco_gen.rsPorted — two binaries split out
utils/Util.ktsrc/parser.rsPorted — DDL → Vec<Entity>
dto/Entity.kt + dto/ColumnToField.ktsrc/entity.rsPorted — data structs
utils/EntityToRESTConstants.ktsrc/constants.rsPorted — SQL-type → target-type maps for all 4 dialects
utils/Configuration.ktsrc/config.rsPorted — config-file loading + per-language defaults
utils/Globals.ktsrc/globals.rsPorted — mutable global state (shape preserved for transition)
utils/Util.kt (name shaping)src/normalize.rsPorted — snake_case ↔ PascalCase / camelCase
templates/Templates.ktsrc/templates.rsPorted — .tmpl loader + {{placeholder}} renderer (JVM targets)
— (Rust-native, no Kotlin equivalent)src/loco.rsNew — Loco/SeaORM codegen path; no JVM analogue
src/lib.rsNew — public API surface for programmatic use

Rust source totals 3,956 LOC across 11 files; the Kotlin codebase totals 9,094 LOC across 36 .kt files (8 main + 28 test). The 2.3× line-count ratio reflects Kotlin's reliance on Spring / JPA / Lombok annotations (which expand into JVM bytecode the Rust port doesn't need an annotation framework to produce) and Java-style class boilerplate the Rust port collapses into structs + free functions.


@DIALECT NORMALISATION

Each non-MySQL dialect runs a dedicated pre-parse normalizer (src/normalize.rs) so the shared parse_words cascade sees a clean token stream. MySQL needs none — its CREATE TABLE grammar parses directly.

DialectNormalizerWhat it does
MySQL— (none)Backticked identifiers + inline/table-level constraints parse directly.
PostgreSQLnormalize_postgresql_wordsHandles pg_dump shape: post-table ALTER TABLE ... ADD CONSTRAINT for PK/FK, PG-native type tokens.
SQLitenormalize_sqlite_wordsStrips IF NOT EXISTS and MSSQL-style [brackets]; drops entire PRAGMA / INSERT / BEGIN / COMMIT / DELETE runs up to the next CREATE / ALTER.
MSSQLnormalize_mssql_wordsCollapses quoted multi-word identifiers ("Order Details"), strips [...] and "..." escapes, folds (max) out of types, and drops SET / USE / GO / EXEC / PRINT / IF / DROP / INSERT / BEGIN / END control statements.

^RUST/LOCO COLTYPE MAPPING

The Loco emitter (src/loco.rs) maps SQL tokens to SeaORM Rust types and the matching Loco ColType for the create_table migration.

SQL typeRust (SeaORM)Loco ColType
bigint, int8, bigseriali64BigInteger
int, integer, int4, seriali32Integer
smallint, int2i16SmallInteger
varchar, char, text, nvarcharStringString
bool, boolean, bitboolBoolean
float, realf32Float
double, decimal, numeric, moneyf64Double
date / timeDate / TimeDate / Time
datetime, datetime2, timestamp(tz)DateTimeWithTimeZoneTimestampWithTimeZone
blob, bytea, binary, varbinary, imageVec<u8>Blob
primary key column (any)i32PkAuto

Reserved Rust keywords (a column named type / match / move) are emitted as raw identifiers (r#type) so the structs compile without losing the original column name in JSON. tests/loco_keyword_escape.rs pins this; tests/loco_collision_pins.rs pins snake-table collisions, duplicate emission, FK-clobber, and silent-drop edge cases in write_loco_project.


!TEST COVERAGE

Both pipelines carry their own test suite; the Rust suite is grown alongside the port to lock in parity as each module lands.

SuiteLives inCountScope
Rust unit (#[test] in src/)src/*.rs155Per-module: parser, normalize, constants, templates, loco, entity, config
Rust integration (tests/)tests/*.rs63End-to-end on sample DDLs, parity vs Kotlin, normalize invariants, Loco smoke / collisions / edge cases / keyword escape
Rust total218
Kotlin @Testsrc/test/kotlin/...806Original Kotlin pipeline: unit + template + integration (per README test table: 28+ classes covering parser, type mappers, template engine, full pipelines, cross-DB × cross-language matrix)

// RUST INTEGRATION TESTS

FileWhat it pins
tests/sample_ddls.rsAll 4 sample DDLs (chinook / northwind / pagila / sakila) round-trip cleanly through the Rust parser → codegen path for every target.
tests/parity_smoke.rsRuns both pipelines (Kotlin via ./gradlew run, Rust via cargo run) on the shared fixture set and diffs the output. The transition is "done" when this passes empty across the full sample corpus.
tests/normalize_units.rsProperty-style coverage of the snake_case ↔ PascalCase / camelCase transforms — the single hottest source of cross-language drift.
tests/loco_smoke.rsThe Rust-native target (loco-gen) end-to-end — no Kotlin analogue, so parity testing doesn't apply.
tests/loco_collision_pins.rsLoco codegen: snake-table collisions, duplicate emission, FK-clobber, and silent-drop edge cases in write_loco_project.
tests/loco_edge_cases.rsLoco codegen edge cases on malformed / boundary DDL inputs.
tests/loco_keyword_escape.rsRust-keyword escaping (self / Self / crate / super / r#type) in generated SeaORM field names.

// KOTLIN TEST CATEGORIES

Full per-class breakdown lives in README “RUNNING TESTS”. Three tiers:


#CI POSTURE

api-rest-generator is in the meta-repo's polish-gates allowlist with all four gates opt-out for the Rust path: fmt, clippy, doc, test. The allowlist entry reads “mid-transition from Kotlin to Rust — Rust code is still the secondary path. Treat as opt-out until the transition completes.” The Kotlin path stays the source of truth; Rust ports land continuously without blocking on Rust-side polish until parity is established.


/PROJECT METADATA

ItemValue
LicenseMIT
AuthorMenkeTechnologies
Repositorygithub.com/MenkeTechnologies/api-rest-generator
Cratecrates.io/crates/api-rest-generator
API docsdocs.rs/api-rest-generator
Issuesgithub.com/MenkeTechnologies/api-rest-generator/issues
EditionRust 2021 (Cargo) · Kotlin/JVM (Gradle)