rustyroad 1.8.5

Rusty Road is a framework written in Rust that is based on Ruby on Rails. It is designed to provide the familiar conventions and ease of use of Ruby on Rails, while also taking advantage of the performance and efficiency of Rust.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
<p align="center">
  <a href="https://github.com/RustyRoad/RustyRoad" rel="noopener">
    <img src="https://avatars.githubusercontent.com/u/138265565?s=400&u=eb116ae7b42e521b884d1288213df00032130f6a&v=4" alt="RustyRoad logo" width="200">
  </a>
</p>

<h1 align="center">RustyRoad</h1>

<p align="center">
  Rails-flavored scaffolding and migrations for Rust web apps (Actix + Tera + SQLx).
</p>

<div align="center">

[![Rust](https://img.shields.io/badge/rust-gray.svg?&logo=rust&logoColor=orange)](https://www.rust-lang.org/)
[![CI](https://img.shields.io/github/actions/workflow/status/RustyRoad/RustyRoad/ci.yml?branch=main)](https://github.com/RustyRoad/RustyRoad/actions)
[![Crates.io](https://img.shields.io/crates/v/rustyroad.svg)](https://crates.io/crates/rustyroad)
[![Docs.rs](https://img.shields.io/docsrs/rustyroad)](https://docs.rs/rustyroad)
[![Issues](https://img.shields.io/github/issues/RustyRoad/RustyRoad.svg)](https://github.com/RustyRoad/RustyRoad/issues)
[![PRs](https://img.shields.io/github/issues-pr/RustyRoad/RustyRoad.svg)](https://github.com/RustyRoad/RustyRoad/pulls)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

</div>

> RustyRoad is under active development. For day-to-day use, prefer the latest released version on crates.io.

---

<sup>In loving memory of Rusty (2014–2023), a wonderful loving pup. I am forever grateful for the time I had with him.</sup>

---

## What is RustyRoad?

RustyRoad is a Rust **CLI + generator toolkit** inspired by Ruby on Rails.

It focuses on:
- generating a consistent project structure
- generating controllers/routes/models
- generating and running database migrations
- providing a few productivity-focused database commands

Under the hood, generated projects use Actix for HTTP, Tera for templates, and SQLx for database support.

If you're curious about the motivation, there's a short write-up here:
https://rileyseaburg.com/posts/rust-needs-a-rails

## Features

- Project generator (`rustyroad new`)
- Generators (`rustyroad generate ...`)
- Database migrations (`rustyroad migration ...`)
- Database inspection / queries (`rustyroad db ...`, `rustyroad query ...`)
- **MCP Server** for AI agent integration (`rustyroad-mcp`)
- Optional GrapesJS feature (drag-and-drop editor) via `rustyroad feature add grapesjs`

## Install

### From crates.io

```bash
cargo install rustyroad
```

### From source

```bash
git clone --recurse-submodules https://github.com/RustyRoad/RustyRoad
cd RustyRoad
cargo build --release
```

## Quick start

Create a new project:

```bash
rustyroad new my_project
```

Generate a route/controller:

```bash
rustyroad generate route users
```

## Configuration

### How `rustyroad.toml` is used

RustyRoad reads your database settings from a TOML file in your **project root**.

- Default (dev): RustyRoad reads `./rustyroad.toml`
- If `ENVIRONMENT` is set and **not** `dev`: RustyRoad reads `./rustyroad.<ENVIRONMENT>.toml`

Examples:

- `ENVIRONMENT=prod` → reads `rustyroad.prod.toml`
- `ENVIRONMENT=test` → reads `rustyroad.test.toml`

There is **no** special `rustyroad.dev.toml`—dev is the plain `rustyroad.toml` file.

**Tip:** You can also use `ENV=prod` as a shorthand for `ENVIRONMENT=prod`. If both are set, `ENVIRONMENT` wins.

If you're unsure what RustyRoad is going to read on your machine, run:

```bash
rustyroad config
```

(It prints `ENVIRONMENT=...`, the config filename, and a sanitized view of the parsed database settings.)

## Generate an API from PostgreSQL

`rustyroad pull` introspects the live PostgreSQL schema and generates a complete
typed database API. TypeScript remains the default target:

```bash
rustyroad pull
```

This writes Drizzle tables and repositories, Zod schemas, oRPC procedures, a
Fastify server adapter, OpenAPI, and a Hey API configuration to `./db`.

To also write a standalone Drizzle/Zod schema pair for application validation,
use:

```bash
rustyroad pull --zod
```

This writes `schema.ts` and `zod.ts` to `./src/schemas`; pass `--zod-out` to
choose another folder. `--zod-models` and `--zod-models-out` are accepted as
aliases.

### Typed JSON/JSONB columns

Unannotated PostgreSQL `json` and `jsonb` columns intentionally generate as
`Record<string, unknown>`. To make the database own a richer shape, put a JSON
Schema on the column comment after the `@rustyroad-json-schema` marker:

```sql
COMMENT ON COLUMN platform_trash_zone_mappings.coverage_metrics IS
  'Targeting coverage metrics.
@rustyroad-json-schema {"type":"object","properties":{"households":{"type":"integer"},"radiusMiles":{"type":"number"}},"required":["households"],"additionalProperties":false}';
```

The marker and its compact JSON value must occupy one line. Normal prose may
appear on other lines. A malformed marker stops `pull` rather than silently
weakening the generated contract.

RustyRoad uses the annotation for all TypeScript outputs:

- the Drizzle column's `$type<...>()` declaration;
- `drizzle-zod` select, insert, and update refinements;
- the OpenAPI 3.1 property schema and generated client type.

The supported JSON Schema shape keywords are `type`, `properties`, `required`,
`additionalProperties`, `items`, `enum`, `const`, `oneOf`, and `anyOf`. Supported
scalar types are `string`, `number`, `integer`, `boolean`, and `null`. Keywords
outside this subset remain in OpenAPI but do not add TypeScript or Zod behavior.
Normalize data into typed columns or related tables when it must participate in
foreign keys, indexes, uniqueness, or relational queries.

Use the Rust target for the equivalent Actix + SQLx stack:

```bash
rustyroad pull --language rust
```

The Rust target writes `./src/db` by default:

- `models.rs` — SQLx row types and separate create/patch input types
- `repositories.rs` — bound, typed CRUD queries for every table with a
  single-column primary key
- `procedures.rs` — Actix handlers for list/get/create/update/delete under
  `/api`
- `api.rs` — the developer-owned composition point for custom services
- `mod.rs` — the module facade exported to the application

Register the generated procedures with the application's pool:

```rust,ignore
mod db;

HttpServer::new(move || {
    App::new()
        .app_data(web::Data::new(pool.clone()))
        .configure(db::configure)
})
```

Database-derived files are regenerated on each pull. Composition files are
written once and preserved, so custom code in `api.rs` or `api.ts` survives.
Pass `--force` only when those files should be reset.

The Rust output expects `actix-web`, `serde`, and SQLx's Postgres/runtime and
database-type features. The command prints the exact `cargo add` invocation
after generation. At present, `pull` introspection is PostgreSQL-only.

## Migrations

RustyRoad expects migrations in this exact location (do **not** create a plain `./migrations/` folder):

```
./config/database/migrations/<timestamp>-<name>/up.sql
./config/database/migrations/<timestamp>-<name>/down.sql
```

### Migration Commands

List migrations:

```bash
rustyroad migration list
ENV=test rustyroad migration list
```

Repair a legacy ledger that contains duplicate rows:

```bash
rustyroad migration repair-ledger
ENVIRONMENT=prod rustyroad migration repair-ledger --yes
```

The repair runs in one transaction, keeps the newest state for each migration
identity, and restores the uniqueness guard. It does not execute `up.sql` or
`down.sql` files.

### Ledger provenance is not effect verification

`rustyroad migration list` reports whether a migration is **recorded in the
ledger**, not whether its SQL or live database effects have been proven. Each
new ledger row records provenance and the SHA-256 checksum of its migration SQL:

- `executed` — RustyRoad successfully submitted the SQL before recording it
- `baselined` — the row was adopted without executing the SQL
- `legacy` — the row predates provenance tracking

Effects remain `UNVERIFIED` unless separate verification evidence populates
`verified_at`. In particular, `rustyroad migration baseline` never executes SQL;
it records `baselined` provenance and causes subsequent migration runs to skip
the recorded identities. Use `rustyroad db schema` as live evidence for tables
and columns, while remembering that it does not verify data changes or every
database object.

`migration version-status` reads `_rustyroad_history`, which is intentionally
separate from `_rustyroad_migrations`. A ledger baseline therefore does not
become a published schema version.

Run all migrations (up) in order:

```bash
rustyroad migration all
```

Validate the complete migration chain without modifying a persistent database:

```bash
ENVIRONMENT=test rustyroad migration validate
ENVIRONMENT=prod rustyroad migration validate
```

Validation reads the active environment configuration: `rustyroad.toml` for dev or `rustyroad.<environment>.toml` for environments such as test, staging, and prod. It never connects to the configured `database_name`. Instead, it creates a randomly named disposable database on the configured PostgreSQL/MySQL server (or an OS-managed temporary file for SQLite), runs every `up.sql` in timestamp order through a generated login scoped to that disposable database, and removes the disposable database and login after success or failure.

When validating with `ENVIRONMENT=prod`, disposable resources are created on the server from `rustyroad.prod.toml`, but the configured production database is not opened or modified. The configured administrative user must be allowed to create and drop databases and temporary roles/users.

### Breaking migration warnings

RustyRoad scans generated migrations and every `up.sql` before `migration run` or `migration all` opens a database connection. It warns about operations that can destroy data or break foreign-key compatibility, including:

- PostgreSQL `ALTER COLUMN ... TYPE` and `USING` conversions
- Explicit casts such as `CAST(...)` and `::type`
- MySQL `MODIFY COLUMN` and `CHANGE COLUMN`
- Dropped constraints or foreign keys

Interactive apply commands require confirmation when findings exist. Non-interactive commands stop with exit code 2. After reviewing both sides of every foreign key, validating against a disposable database, and backing up the target database, automation can acknowledge the risk explicitly:

```bash
rustyroad migration run change_customer_id_type --allow-breaking
rustyroad migration all --allow-breaking
```

The preflight is intentionally conservative: it identifies risky SQL but cannot prove that a cast preserves every value or that application code remains compatible.

Run a single migration by name (the name is the part after the timestamp in the folder name):

```bash
rustyroad migration run create_users_table
```

Rollback a migration:

```bash
rustyroad migration rollback create_users_table
```

Generate a migration (folder + files):

```bash
rustyroad migration generate create_users_table id:serial:primary_key email:string:not_null,unique
```

### Auto-convert Rogue Migrations

If you (or an AI agent) accidentally created migrations in the wrong location (like `./migrations/`), RustyRoad can detect and convert them:

```bash
# Preview what would be converted
rustyroad migration convert --dry-run

# Convert and keep source files
rustyroad migration convert

# Convert and remove source files
rustyroad migration convert --remove-source
```

RustyRoad will also warn you when running any migration command if it detects rogue migrations.

## Database commands

Inspect schema:

```bash
rustyroad db schema
```

Inspect enum types and their allowed values:

```bash
rustyroad db enums
```

Run ad-hoc queries:

```bash
rustyroad query "SELECT * FROM users LIMIT 10;"
rustyroad query "SELECT COUNT(*) AS total_users FROM users;"
```

## MCP Server (AI Agent Integration)

RustyRoad includes an MCP (Model Context Protocol) server that exposes database tools to AI agents like OpenCode, Claude, etc. This prevents agents from using raw `psql` commands or connecting to the wrong database.

### Available Tools

- `rustyroad_query` - Execute SQL queries
- `rustyroad_schema` - Get database schema  
- `rustyroad_enums` - Get database enum types and their allowed values
- `rustyroad_migrate` - Run migrations
- `rustyroad_migration_generate` - Create new migrations
- `rustyroad_config` - View configuration
- `rustyroad_convert_migrations` - Fix rogue migrations

### Setup

Register with OpenCode:

```bash
rustyroad-mcp --register
```

Or manually add to `~/.config/opencode/opencode.json`:

```json
{
  "mcp": {
    "rustyroad": {
      "type": "local",
      "command": ["/path/to/rustyroad-mcp"],
      "enabled": true,
      "environment": {
        "RUSTYROAD_PROJECT_DIR": "/path/to/your/project"
      }
    }
  }
}
```

## Optional: GrapesJS

RustyRoad can scaffold an optional GrapesJS editor experience:

```bash
rustyroad feature add grapesjs
```

You can learn more about GrapesJS at https://grapesjs.com/ and see the example project at `example-grapesjs/`.

## Examples

- `example/` – a basic generated app
- `example-grapesjs/` – a generated app with GrapesJS enabled

## Troubleshooting

### Building from source on Windows (PostgreSQL linkage)

If you build this repository from source on Windows and see errors about `POSTGRES_LIB_PATH` or `libpq.lib`:

1. Install PostgreSQL from the [official website](https://www.postgresql.org/download/windows/)
2. Set `POSTGRES_LIB_PATH` environment variable to the directory containing `libpq.lib` (e.g., `C:\Program Files\PostgreSQL\13\lib`)
3. For generated projects, create `.cargo/config.toml` in your project root:

```toml
[target.'cfg(windows)']
rustflags = ["-C", "link-arg=/LIBPATH:C:\\Program Files\\PostgreSQL\\13\\lib"]
```

## Contributing

Contributions are welcome! Please see `CONTRIBUTING.md`.

## License

MIT — see `LICENSE`.