Add bunny.net Database (libSQL) support (iter-20)#22
Conversation
Adds a new bunny-api-database crate covering the full Database control
plane (26 paths across config, database v1/v2, group, live metrics) plus
a libSQL data-plane ping convenience. Wires it into a new `hoppy db`
subcommand tree with token redaction by default and local slug validation.
- bunny-api-database: types + client for v1, v2, group, live, config
- LiveStatus tagged enum with #[serde(other)] forward-compat fallback
- Region enums modelled as String for forward-compat (bunny adds silently)
- DatabaseClient::ping rewrites libsql:// → https:// + /v2/pipeline
and returns a typed PingResult { ok, latency_ms, error }
- CLI: db list/get/create/delete, fork, restore, versions, ping,
statistics, usage, active-usage, live, v2, group, token, config
- Slug validator rejects upper, leading digit, underscore, len > 24
- Token mint defaults to redacted output; --reveal opts in to raw JWT
- Wiremock + insta tests for crate (21) and CLI (10)
- Docs: README features+envvars, bunny-database-research note,
bunny-api-quirks updated with v2 500, slug footgun, libSQL URL
preservation, live-metrics custom headers, invalidate vs revoke
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Free Run ID: 📒 Files selected for processing (7)
📝 WalkthroughWalkthroughThis PR introduces a new crate ChangesDatabase API Client and CLI Integration
Estimated code review effort🎯 4 (Complex) | ⏱️ ~65 minutes Poem
Note 🎁 Summarized by CodeRabbit FreeYour organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login. Comment |
There was a problem hiding this comment.
Pull request overview
Adds first-class bunny.net Database (libSQL) support to Hoppy by introducing a new bunny-api-database client crate, wiring it into the CLI as a new hoppy db command tree, and documenting/testing the new functionality.
Changes:
- Added
crates/bunny-api-database(control-plane REST client + data-planeping, with wiremock tests and fixtures). - Added
hoppy dbCLI subtree (v1/v2 DB ops, groups, tokens, config, live metrics, ping) and integrated it into command dispatch/auth wiring. - Added documentation and snapshot/CLI tests for the new database commands.
Reviewed changes
Copilot reviewed 35 out of 36 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
src/main.rs |
Routes Commands::Db to the new database command handler. |
src/commands/mod.rs |
Registers the new database command module. |
src/commands/database.rs |
Implements hoppy db handlers, output row mapping, slug validation, ping, and token redaction behavior. |
src/cli.rs |
Adds the db command subtree and associated subcommands/flags. |
src/auth.rs |
Adds BUNNY_DATABASE_URL support and constructs DatabaseClient. |
crates/bunny-api-database/Cargo.toml |
Defines the new database client crate and dependencies. |
crates/bunny-api-database/src/lib.rs |
Public crate entrypoint + re-exports. |
crates/bunny-api-database/src/types.rs |
Adds request/response types for Database v1/v2, live metrics, config, and ping result. |
crates/bunny-api-database/src/client.rs |
Implements REST endpoints + libSQL pipeline URL rewrite and ping. |
crates/bunny-api-database/tests/database_api.rs |
Wiremock tests validating endpoints, headers, query params, and ping behavior. |
tests/cli_database.rs |
End-to-end CLI tests for list/get/create validation, token redaction, config, and ping. |
tests/snapshots/*.snap |
Snapshot baselines for new database CLI outputs. |
fixtures/database/*.json |
Mock API fixtures used by unit/integration tests. |
README.md |
Documents the new Database feature set and BUNNY_DATABASE_URL. |
hoppy-knowledgebase/api/bunny-database-research.md |
Adds a research note describing control-plane vs data-plane and quirks. |
hoppy-knowledgebase/api/bunny-api-quirks.md |
Documents known database API quirks (v2 create 500, slug footgun, etc.). |
hoppy-knowledgebase/api/bunny-api-overview.md |
Updates overview to include database control-plane and libSQL data-plane. |
hoppy-knowledgebase/iterations/iteration-20-database.md |
Updates iteration metadata/status. |
Cargo.toml / Cargo.lock |
Adds the new crate to the workspace and dependency graph. |
| DbAction::Delete { id } => { | ||
| let resp = client.delete_database(id).await?; | ||
| let row = StatusRow { | ||
| status: format!("deleted: {}", resp.database), | ||
| }; | ||
| output::print_single(&row, format); | ||
| } |
| DbAction::Restore { id, version } => { | ||
| let resp = client | ||
| .restore_database( | ||
| id, | ||
| &RestoreVersionDatabasePayload { | ||
| generation: version.clone(), | ||
| }, | ||
| ) | ||
| .await?; | ||
| let row = StatusRow { | ||
| status: format!("restored to generation {}", resp.generation), | ||
| }; | ||
| output::print_single(&row, format); | ||
| } |
| DbGroupAction::Delete { id } => { | ||
| let resp = client.delete_group(id).await?; | ||
| let row: GroupRow = (&resp.group).into(); | ||
| output::print_single(&row, format); | ||
| } |
| let db = client.get_database(id).await?.database; | ||
| let token = if let Some(path) = token_file { | ||
| std::fs::read_to_string(path) | ||
| .with_context(|| format!("reading token file {path}"))? | ||
| .trim() | ||
| .to_owned() | ||
| } else { |
| let json = serde_json::to_string_pretty(&resp)?; | ||
| println!("{json}"); | ||
| // Suppress unused for `format` in non-table cases: | ||
| let _ = format; |
| let mut body = GenerateTokenDatabaseGroupPayload::new((*authorization).into()); | ||
| if let Some(ts) = expires_at { | ||
| body.expires_at = Some(ts.clone()); | ||
| } | ||
| let resp = client.generate_group_keys(id, &body).await?; | ||
| // Group key minting also surfaces a JWT — same redaction policy | ||
| // as `db token mint` would be ideal, but groups are batch-only, | ||
| // so for now we reveal it as it's typically used in scripts that | ||
| // immediately distribute the token. | ||
| let row = StatusRow { | ||
| status: format!( | ||
| "minted group token (length={}, expires_at={})", | ||
| resp.token.chars().count(), | ||
| resp.expires_at.as_deref().unwrap_or("never") | ||
| ), |
| hoppy db live --id db_01HX... --id db_02HX... | ||
| ``` | ||
|
|
||
| By default, `hoppy db token mint` prints `{ "Token": "<set, length=N>", ... }` |
| //! v1 and v2 are kept as parallel modules — the spec exposes parallel schemas | ||
| //! (`Database` vs `Database2`, etc.) and unifying them would silently lose | ||
| //! fields one side or the other doesn't model. |
| #[derive(Debug, Clone, Default, Serialize)] | ||
| pub struct ListVersionsDatabaseGroupPayload { | ||
| #[serde(skip_serializing_if = "Option::is_none")] | ||
| pub limit: Option<u64>, | ||
| #[serde(skip_serializing_if = "Option::is_none")] | ||
| pub older_than: Option<String>, | ||
| #[serde(skip_serializing_if = "Option::is_none")] | ||
| pub newer_than: Option<String>, | ||
| } |
- Add confirmation prompts (bypassable with --yes) to destructive commands: db delete, db restore, db v2 delete, db group delete (Copilot) - db group generate-keys now prints the actual minted token using the same redaction policy as db token mint, instead of just a length+expiry status row that left the token unrecoverable (Copilot, CodeRabbit) - Use tokio::fs::read_to_string in the async ping handler (Copilot) - Rename ListVersionsDatabaseGroupPayload -> ListVersionsDatabasePayload; the payload is for listing database versions, not groups (Copilot) - README: document --reveal / --reveal-env in the global options table and fix the JSON example to use lowercase serde field names (Copilot, CodeRabbit) - types.rs: clarify "parallel types" wording in module docs (Copilot) - Fixture: bump token_mint.json expires_at to 2099 to avoid future test brittleness (CodeRabbit) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
iter-20 added bunny-api-database (1 test file) and tests/cli_database.rs to the root, taking the workspace test-binary count from 22 to 24 and the target from 7 to 8. Replaces the TBD row with concrete numbers and the placeholder iter-20-fork-in-the-road section with the actual move-files tasks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
bunny-api-databasecrate: types + client covering all 26 paths inspecs/database.json(Database v1, Database v2, DatabaseGroup, Live Metrics, Config) plus a libSQL data-planepingthat returns a typedPingResult { ok, latency_ms, error }.hoppy dbCLI subtree:list/get/create/delete,fork,restore,versions,ping,statistics,usage,active-usage,live, plusdb v2,db group,db token,db configgroups.db token mintprints<set, length=N>placeholder unless--revealis passed; same logic for JSON / table / text.^[a-z][a-z0-9-]{0,23}$) catches the upstream "Internal error" 500 footgun before any HTTP call.String,LiveStatuswith#[serde(other)]fallback.bunny-database-research.mdnote, quirks updated with v2 create 500, slug-length footgun, libSQL URL preservation, live-metrics custom headers, v1 invalidate vs v2 revoke wart.Test plan
cargo fmt— cleancargo clippy --workspace --all-targets -- -D warnings— cleancargo test --workspace— all pass (21 new crate tests, 10 new CLI tests)hoppy db --helprenders correctly with all subcommands--revealJSON outputget_database+mint_database_token) and data-plane (/v2/pipeline)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Tests