Skip to content

Commit 071fd2f

Browse files
committed
Fix v0.1.551 verification gaps: CHANGELOG, CRUD e2e, template versions
1 parent 6872862 commit 071fd2f

9 files changed

Lines changed: 146 additions & 65 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10-
## [0.1.551] - 2026-07-05
11-
12-
### Added
13-
14-
- **`cargo rustapi generate crud`**: SQLx-sqlite-backed list/get/create/update/delete handlers with schema bootstrap (`src/db.rs`) instead of `TODO` stubs.
15-
- **`file_upload` example** (`crates/rustapi-rs/examples/file_upload.rs`) demonstrating multipart uploads with the public `rustapi_rs::prelude` API.
16-
- **Slim vs full dependency guidance** in [Getting Started](docs/GETTING_STARTED.md).
17-
1810
### Changed
1911

2012
- Default `rustapi-rs` dependency tree slimmed from ~259 to ~158 transitive crates by removing always-on `tracing-subscriber` and gating `rust-i18n` behind the `i18n` feature (English fallbacks by default).
@@ -26,6 +18,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2618

2719
- **Security Audit** (`cargo audit`) passes on the current lockfile (no high-severity `quick-xml` advisories).
2820

21+
## [0.1.551] - 2026-07-05
22+
23+
### Added
24+
25+
- **`cargo rustapi generate crud`**: SQLx-sqlite-backed list/get/create/update/delete handlers with schema bootstrap (`src/db.rs`) instead of `TODO` stubs.
26+
- **`file_upload` example** (`crates/rustapi-rs/examples/file_upload.rs`) demonstrating multipart uploads with the public `rustapi_rs::prelude` API.
27+
- **Slim vs full dependency guidance** in [Getting Started](docs/GETTING_STARTED.md).
28+
2929
### Documentation
3030

3131
- Refreshed [Performance Benchmarks](docs/PERFORMANCE_BENCHMARKS.md) with a new `perf_snapshot` run.

crates/cargo-rustapi/src/commands/generate.rs

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -269,29 +269,34 @@ async fn ensure_db_module(table: &str) -> Result<()> {
269269
use sqlx::sqlite::SqlitePoolOptions;
270270
use sqlx::SqlitePool;
271271
272-
const SCHEMA: &str = "CREATE TABLE IF NOT EXISTS {table} (
272+
/// Table name used by generated CRUD handlers.
273+
pub const TABLE: &str = "{table}";
274+
275+
/// Singular resource label for error messages.
276+
pub const SINGULAR: &str = "{singular}";
277+
278+
const COLUMNS_DDL: &str = "
273279
id INTEGER PRIMARY KEY AUTOINCREMENT,
274280
name TEXT NOT NULL,
275281
description TEXT,
276282
created_at TEXT NOT NULL,
277283
updated_at TEXT NOT NULL
278-
)";
284+
";
279285
280-
/// Open a SQLite pool and ensure the `{table}` table exists.
286+
/// Open a SQLite pool and ensure the resource table exists.
281287
pub async fn init_pool(database_url: &str) -> Result<SqlitePool, sqlx::Error> {{
282288
let pool = SqlitePoolOptions::new()
283289
.max_connections(5)
284290
.connect(database_url)
285291
.await?;
286-
sqlx::query(SCHEMA).execute(&pool).await?;
292+
let schema = format!(
293+
"CREATE TABLE IF NOT EXISTS {{}} ({{}})",
294+
TABLE,
295+
COLUMNS_DDL.trim()
296+
);
297+
sqlx::query(&schema).execute(&pool).await?;
287298
Ok(pool)
288299
}}
289-
290-
/// Table name used by generated CRUD handlers.
291-
pub const TABLE: &str = "{table}";
292-
293-
/// Singular resource label for error messages.
294-
pub const SINGULAR: &str = "{singular}";
295300
"#,
296301
table = table,
297302
singular = singular,
@@ -353,14 +358,15 @@ pub struct Update{type_name} {{
353358
Ok(())
354359
}
355360

356-
async fn generate_sqlx_handler(name: &str, type_name: &str, table: &str) -> Result<()> {
361+
async fn generate_sqlx_handler(name: &str, type_name: &str, _table: &str) -> Result<()> {
357362
let handlers_dir = Path::new("src/handlers");
358363
ensure_handlers_module(handlers_dir, name).await?;
359364

360365
let singular = singularize(name);
361366
let handler_content = format!(
362367
r#"//! {} handlers (SQLx SQLite)
363368
369+
use crate::db::{{SINGULAR, TABLE}};
364370
use crate::models::{{Create{type_name}, Update{type_name}, {type_name}}};
365371
use rustapi_rs::prelude::*;
366372
use sqlx::SqlitePool;
@@ -378,9 +384,10 @@ fn db_error(err: sqlx::Error) -> ApiError {{
378384
#[rustapi_rs::tag("{type_name}")]
379385
#[rustapi_rs::summary("List all {name}")]
380386
pub async fn list(State(pool): State<SqlitePool>) -> Result<Json<Vec<{type_name}>>> {{
381-
let rows = sqlx::query_as::<_, {type_name}>(
382-
"SELECT id, name, description, created_at, updated_at FROM {table} ORDER BY id",
383-
)
387+
let rows = sqlx::query_as::<_, {type_name}>(&format!(
388+
"SELECT id, name, description, created_at, updated_at FROM {{}} ORDER BY id",
389+
TABLE
390+
))
384391
.fetch_all(&pool)
385392
.await
386393
.map_err(db_error)?;
@@ -392,14 +399,15 @@ pub async fn list(State(pool): State<SqlitePool>) -> Result<Json<Vec<{type_name}
392399
#[rustapi_rs::tag("{type_name}")]
393400
#[rustapi_rs::summary("Get {singular} by ID")]
394401
pub async fn get(Path(id): Path<i64>, State(pool): State<SqlitePool>) -> Result<Json<{type_name}>> {{
395-
let row = sqlx::query_as::<_, {type_name}>(
396-
"SELECT id, name, description, created_at, updated_at FROM {table} WHERE id = ?",
397-
)
402+
let row = sqlx::query_as::<_, {type_name}>(&format!(
403+
"SELECT id, name, description, created_at, updated_at FROM {{}} WHERE id = ?",
404+
TABLE
405+
))
398406
.bind(id)
399407
.fetch_optional(&pool)
400408
.await
401409
.map_err(db_error)?
402-
.ok_or_else(|| ApiError::not_found(format!("{singular} {{id}} not found", id = id)))?;
410+
.ok_or_else(|| ApiError::not_found(format!("{{}} {{id}} not found", SINGULAR, id = id)))?;
403411
Ok(Json(row))
404412
}}
405413
@@ -412,9 +420,10 @@ pub async fn create(
412420
Json(body): Json<Create{type_name}>,
413421
) -> Result<WithStatus<Json<{type_name}>, 201>> {{
414422
let now = now_ts();
415-
let result = sqlx::query(
416-
"INSERT INTO {table} (name, description, created_at, updated_at) VALUES (?, ?, ?, ?)",
417-
)
423+
let result = sqlx::query(&format!(
424+
"INSERT INTO {{}} (name, description, created_at, updated_at) VALUES (?, ?, ?, ?)",
425+
TABLE
426+
))
418427
.bind(&body.name)
419428
.bind(&body.description)
420429
.bind(&now)
@@ -424,9 +433,10 @@ pub async fn create(
424433
.map_err(db_error)?;
425434
426435
let id = result.last_insert_rowid();
427-
let row = sqlx::query_as::<_, {type_name}>(
428-
"SELECT id, name, description, created_at, updated_at FROM {table} WHERE id = ?",
429-
)
436+
let row = sqlx::query_as::<_, {type_name}>(&format!(
437+
"SELECT id, name, description, created_at, updated_at FROM {{}} WHERE id = ?",
438+
TABLE
439+
))
430440
.bind(id)
431441
.fetch_one(&pool)
432442
.await
@@ -443,22 +453,24 @@ pub async fn update(
443453
State(pool): State<SqlitePool>,
444454
Json(body): Json<Update{type_name}>,
445455
) -> Result<Json<{type_name}>> {{
446-
let existing = sqlx::query_as::<_, {type_name}>(
447-
"SELECT id, name, description, created_at, updated_at FROM {table} WHERE id = ?",
448-
)
456+
let existing = sqlx::query_as::<_, {type_name}>(&format!(
457+
"SELECT id, name, description, created_at, updated_at FROM {{}} WHERE id = ?",
458+
TABLE
459+
))
449460
.bind(id)
450461
.fetch_optional(&pool)
451462
.await
452463
.map_err(db_error)?
453-
.ok_or_else(|| ApiError::not_found(format!("{singular} {{id}} not found", id = id)))?;
464+
.ok_or_else(|| ApiError::not_found(format!("{{}} {{id}} not found", SINGULAR, id = id)))?;
454465
455466
let name = body.name.unwrap_or(existing.name);
456467
let description = body.description.or(existing.description);
457468
let updated_at = now_ts();
458469
459-
sqlx::query(
460-
"UPDATE {table} SET name = ?, description = ?, updated_at = ? WHERE id = ?",
461-
)
470+
sqlx::query(&format!(
471+
"UPDATE {{}} SET name = ?, description = ?, updated_at = ? WHERE id = ?",
472+
TABLE
473+
))
462474
.bind(&name)
463475
.bind(&description)
464476
.bind(&updated_at)
@@ -467,9 +479,10 @@ pub async fn update(
467479
.await
468480
.map_err(db_error)?;
469481
470-
let row = sqlx::query_as::<_, {type_name}>(
471-
"SELECT id, name, description, created_at, updated_at FROM {table} WHERE id = ?",
472-
)
482+
let row = sqlx::query_as::<_, {type_name}>(&format!(
483+
"SELECT id, name, description, created_at, updated_at FROM {{}} WHERE id = ?",
484+
TABLE
485+
))
473486
.bind(id)
474487
.fetch_one(&pool)
475488
.await
@@ -482,21 +495,20 @@ pub async fn update(
482495
#[rustapi_rs::tag("{type_name}")]
483496
#[rustapi_rs::summary("Delete {singular}")]
484497
pub async fn delete(Path(id): Path<i64>, State(pool): State<SqlitePool>) -> Result<NoContent> {{
485-
let result = sqlx::query("DELETE FROM {table} WHERE id = ?")
498+
let result = sqlx::query(&format!("DELETE FROM {{}} WHERE id = ?", TABLE))
486499
.bind(id)
487500
.execute(&pool)
488501
.await
489502
.map_err(db_error)?;
490503
if result.rows_affected() == 0 {{
491-
return Err(ApiError::not_found(format!("{singular} {{id}} not found", id = id)));
504+
return Err(ApiError::not_found(format!("{{}} {{id}} not found", SINGULAR, id = id)));
492505
}}
493506
Ok(NoContent)
494507
}}
495508
"#,
496509
capitalize(name),
497510
name = name,
498511
type_name = type_name,
499-
table = table,
500512
singular = singular,
501513
);
502514

crates/cargo-rustapi/src/templates/api.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@ version = "0.1.0"
1313
edition = "2021"
1414
1515
[dependencies]
16-
rustapi-rs = {{ version = "0.1"{features} }}
16+
rustapi-rs = {{ version = "{version}"{features} }}
1717
tokio = {{ version = "1", features = ["full"] }}
1818
serde = {{ version = "1", features = ["derive"] }}
1919
tracing = "0.1"
2020
tracing-subscriber = {{ version = "0.3", features = ["env-filter"] }}
2121
uuid = {{ version = "1", features = ["v4"] }}
2222
"#,
2323
name = name,
24+
version = common::rustapi_rs_version(),
2425
features = common::features_to_cargo(features),
2526
);
2627
fs::write(format!("{name}/Cargo.toml"), cargo_toml).await?;

crates/cargo-rustapi/src/templates/full.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,15 @@ version = "0.1.0"
2828
edition = "2021"
2929
3030
[dependencies]
31-
rustapi-rs = {{ version = "0.1"{features} }}
31+
rustapi-rs = {{ version = "{version}"{features} }}
3232
tokio = {{ version = "1", features = ["full"] }}
3333
serde = {{ version = "1", features = ["derive"] }}
3434
tracing = "0.1"
3535
tracing-subscriber = {{ version = "0.3", features = ["env-filter"] }}
3636
uuid = {{ version = "1", features = ["v4"] }}
3737
"#,
3838
name = name,
39+
version = common::rustapi_rs_version(),
3940
features = common::features_to_cargo(&all_features),
4041
);
4142
fs::write(format!("{name}/Cargo.toml"), cargo_toml).await?;

crates/cargo-rustapi/src/templates/minimal.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ version = "0.1.0"
1313
edition = "2021"
1414
1515
[dependencies]
16-
rustapi-rs = {{ version = "0.1"{features} }}
16+
rustapi-rs = {{ version = "{version}"{features} }}
1717
tokio = {{ version = "1", features = ["full"] }}
1818
serde = {{ version = "1", features = ["derive"] }}
1919
tracing-subscriber = "0.3"
2020
"#,
2121
name = name,
22+
version = common::rustapi_rs_version(),
2223
features = common::features_to_cargo(features),
2324
);
2425
fs::create_dir_all(format!("{name}/src")).await?;

crates/cargo-rustapi/src/templates/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@ RUST_LOG=info
137137
Ok(())
138138
}
139139

140+
/// Workspace-aligned `rustapi-rs` version for generated `Cargo.toml` pins.
141+
pub fn rustapi_rs_version() -> &'static str {
142+
env!("CARGO_PKG_VERSION")
143+
}
144+
140145
pub fn features_to_cargo(features: &[String]) -> String {
141146
if features.is_empty() {
142147
String::new()

crates/cargo-rustapi/src/templates/web.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ version = "0.1.0"
1919
edition = "2021"
2020
2121
[dependencies]
22-
rustapi-rs = {{ version = "0.1"{features} }}
22+
rustapi-rs = {{ version = "{version}"{features} }}
2323
tokio = {{ version = "1", features = ["full"] }}
2424
serde = {{ version = "1", features = ["derive"] }}
2525
tracing = "0.1"
2626
tracing-subscriber = {{ version = "0.3", features = ["env-filter"] }}
2727
"#,
2828
name = name,
29+
version = common::rustapi_rs_version(),
2930
features = common::features_to_cargo(&all_features),
3031
);
3132
fs::write(format!("{name}/Cargo.toml"), cargo_toml).await?;

0 commit comments

Comments
 (0)