Skip to content

chore(api): flatten Error enum and add ergonomic constructors workspace-wide (#70)#71

Merged
StefanSteiner merged 1 commit into
tableau:mainfrom
StefanSteiner:ssteiner/issue-70-flatten-error
May 28, 2026
Merged

chore(api): flatten Error enum and add ergonomic constructors workspace-wide (#70)#71
StefanSteiner merged 1 commit into
tableau:mainfrom
StefanSteiner:ssteiner/issue-70-flatten-error

Conversation

@StefanSteiner

Copy link
Copy Markdown
Contributor

Resolves #70.

Replaces hyperdb_api::Error's nested Client(client::Error) / Other { Box<dyn StdError> } shape with a flat canonical enum per the Microsoft Pragmatic Rust Guidelines M-ERRORS-CANONICAL-STRUCTS and M-ERRORS-AVOID-WRAPPING-AND-AS-DYN. Callers now match directly on variants — no kind() -> Option<ErrorKind> indirection, no Box<dyn StdError> cause channel, no catch-all bucket variant.

The same ergonomic-constructor pattern was applied to every other workspace error type (SalesforceAuthError, hyperdb_bootstrap::Error) and one residual McpError site, so call sites no longer need .to_string() / .into() ceremony for string-literal arguments anywhere in the workspace.

Breaking change. Lands as chore: to defer release-please from cutting an early version; the v0.3.0 bump happens after the rest of the bundle (#61, #62, #69) merges. See MIGRATING-0.3.md for the consolidated migration guide.

What changed

hyperdb_api::Error

  • Flat enum with 18 variants — each a distinct, matchable failure mode:
    Connection, Authentication, Tls, Server, Protocol, Io, Closed, Timeout, Cancelled, Conversion, Config, FeatureNotSupported, InvalidName, InvalidTableDefinition, NotFound, AlreadyExists, Column, ColumnIndexOutOfBounds, Internal.
  • Error::Connection { source: Option<std::io::Error> } preserves the underlying io::Error for direct constructor calls (typed cause channel — no Box<dyn>).
  • Error::Server { sqlstate, message, detail, hint } provides structured access to PostgreSQL server-error fields. The Display impl renders all four.
  • Error::Column { name, kind: ColumnErrorKind } + new ColumnErrorKind enum (Missing / Null / TypeMismatch). Shipped here so FromRow redesign (breaking): typed RowAccessor, structured errors, enforced NULL semantics #62's RowAccessor work doesn't require a second Error-breaking change.
  • Error::ColumnIndexOutOfBounds { idx, column_count } for positional row access.
  • 19 ergonomic constructors (one per variant; struct-variant constructors take their fields by name, all string fields take impl Into<String>). Pattern-matching keeps PascalCase variant names; only construction switches to snake_case.
  • From<client::Error> mapping is exhaustive over client::ErrorKind; adding a kind upstream forces an explicit mapping decision here.
  • Removed: Error::Client, Error::Other, Error::new, Error::with_cause, Error::kind(), the pub use ... ErrorKind re-export, and the From<std::convert::Infallible> impl.

Cross-crate cleanup (same pattern)

  • hyperdb_api_salesforce::SalesforceAuthError — added 8 ergonomic constructors (config, private_key, jwt, http, authorization, token_exchange, token_parse, io); 26 internal call sites rewritten.
  • hyperdb_bootstrap::Error — added 6 ergonomic constructors (unsupported_platform, unknown_platform_slug, io, http_status, curl_failed, checksum_mismatch); 26 call sites rewritten across download.rs, extract.rs, install.rs, platform.rs, release.rs, scrape.rs.
  • hyperdb_mcp::McpError — already ergonomic; one residual .to_string() site cleaned up.
  • hyperdb_api_core::client::Error — already ergonomic via existing convenience constructors; no changes needed.

Migration scope

  • 137 Error::new / Error::with_cause call sites migrated across hyperdb-api/src/, plus ~10 more in hyperdb-api-core/tests/, hyperdb-api/tests/, hyperdb-api/examples/, hyperdb-api/benches/, and the workspace README.md. Mapping was applied mechanically per the table in MIGRATING-0.3.md (e.g. catalog name validation → InvalidName, gRPC unsupported → FeatureNotSupported, NULL/conversion failures → Conversion, ambiguous lifecycle → Internal).
  • 205 stale rustdoc references to Error::Other / Error::Client updated to the appropriate new variant.
  • hyperdb-mcp::From<hyperdb_api::Error> updated to dispatch on structured variants first (SQLSTATE matched directly via Error::Server { sqlstate: Some(...), .. }, no string parsing), with a substring fallback retained for is_connection_lost / is_resource_busy heuristics that need it.

Tests

New unit tests in hyperdb-api/src/error.rs covering:

  • Error::Server Display with all field combinations (sqlstate / detail / hint present and absent).
  • From<client::Error> for every client::ErrorKind variant — exhaustive smoke test.
  • The Query mapping does not double-print detail / hint (regression test for a bug caught in review).
  • Error::sqlstate() returns Some only for Server.
  • Error::Column and Error::ColumnIndexOutOfBounds Display formatting.
  • Error::connection_with_io exposes the typed io::Error via std::error::Error::source() and downcasts cleanly.
  • Error::internal round-trip via Display.

Behavioral note

Error::sqlstate() now returns Some(...) only for Error::Server. Previously, a wrapped client::Error could surface SQLSTATE codes for non-Query kinds (e.g. SQLSTATE 57014 query_canceled arriving as Cancelled). After this change those codes are folded into the message string but not surfaced through sqlstate(). Documented in MIGRATING-0.3.md.

Verification

  • cargo build --workspace --all-targets — clean
  • cargo clippy --workspace --all-targets -- -D warnings — clean
  • cargo test --workspace --lib — 375 tests, 0 failed
  • cargo test -p hyperdb-mcp --test error_tests — 9 tests, 0 failed (incl. existing SQLSTATE classification tests, retargeted to use Error::server(...))
  • cargo doc --workspace --no-deps — 6 warnings, equal to the pre-change baseline (all in hyperdb-mcp, unrelated to this change)
  • cargo fmt --check — clean

Process notes

  • Plan-driven 5-phase workflow with parallel adversarial review at the plan and pre-PR checkpoints. Reviewers caught the detail/hint duplication bug in the From<client::Error> Query arm before merge — added a regression test.
  • The MAJOR finding about no-test-coverage is now closed (9 new unit tests).
  • Cross-crate constructor sweep dispatched as parallel agents (one per crate); main thread reconciled.
  • Two MAJOR findings deferred as follow-up: process.rs lifecycle I/O failures could be promoted from Internal to connection_with_io to preserve typed sources; some inserter.rs state-machine errors could be reframed as caller misuse rather than Internal. Neither is blocking.

Related

Test plan

  • CI green on this branch
  • Smoke-test cargo run --example async_parity_smoke against a local hyperd
  • Cross-link review of MIGRATING-0.3.md recipes against the actual public API

…ce-wide (tableau#70)

Replaces hyperdb_api::Error's nested Client/Other shape with a flat
canonical enum. Callers now match directly on variants — no kind()
indirection, no Box<dyn StdError> cause channel, no Other catch-all.

Applies the same ergonomic-constructor pattern to every other workspace
error type so call sites no longer need .to_string() / .into() ceremony
for string-literal arguments anywhere in the workspace.

This is a breaking change to public Error types. Lands as `chore:` to
defer release-please version bump until the rest of the v0.3.0 bundle
(tableau#61, tableau#62, tableau#69) lands. See MIGRATING-0.3.md.

What changed:

- hyperdb_api::Error: flat enum with 18 variants (Connection,
  Authentication, Tls, Server, Protocol, Io, Closed, Timeout, Cancelled,
  Conversion, Config, FeatureNotSupported, InvalidName,
  InvalidTableDefinition, NotFound, AlreadyExists, Column,
  ColumnIndexOutOfBounds, Internal). 19 ergonomic constructors (one
  per variant; all string fields take impl Into<String>).

- New ColumnErrorKind enum (Missing / Null / TypeMismatch). Shipped in
  tableau#70 so tableau#62's RowAccessor work doesn't require a second Error-type
  breaking change.

- Error::Connection { source: Option<std::io::Error> } preserves the
  underlying io::Error for direct constructor calls, satisfying
  M-ERRORS-AVOID-WRAPPING-AND-AS-DYN.

- Error::Server { sqlstate, message, detail, hint } provides structured
  access to PostgreSQL server-error fields. Display walks all fields.

- From<client::Error> mapping is exhaustive over client::ErrorKind.
  Adding a kind upstream forces an explicit mapping decision.

- 137 Error::new / Error::with_cause sites migrated across src/, tests/,
  examples/, benches/, README. ~205 stale rustdoc references to
  Error::Other / Error::Client updated. ErrorKind re-export removed.

- hyperdb-mcp::From<hyperdb_api::Error> updated to dispatch on
  structured variants first (SQLSTATE matched directly via
  Error::Server { sqlstate: Some(...), .. }, no string parsing).

- Unit tests added covering new variant Display formatting,
  From<client::Error> mapping completeness (exhaustive), the
  detail/hint duplication regression, and typed source preservation
  through std::error::Error::source().

Cross-crate ergonomic-constructor sweep:

- hyperdb_api_salesforce::SalesforceAuthError: 8 new constructors
  (config, private_key, jwt, http, authorization, token_exchange,
  token_parse, io); 26 internal call sites rewritten.

- hyperdb_bootstrap::Error: 6 new constructors (unsupported_platform,
  unknown_platform_slug, io, http_status, curl_failed,
  checksum_mismatch); 26 call sites rewritten.

- hyperdb_mcp::McpError: already ergonomic; 1 residual .to_string()
  call site cleaned up.

- hyperdb_api_core::client::Error: already ergonomic via existing
  convenience constructors; no changes needed.

- hyperdb-api-node: uses napi::Error directly (NAPI library type, not
  workspace-defined); already ergonomic; no changes needed.

Caveat: SQLSTATE codes from non-Server-kind upstream errors are now
folded into the message string instead of surfaced via Error::sqlstate();
see MIGRATING-0.3.md for context and recovery options.

Verification:
- cargo build --workspace --all-targets — clean
- cargo clippy --workspace --all-targets -- -D warnings — clean
- cargo test --workspace --lib — 375 tests, 0 failed
- cargo doc --workspace --no-deps — 6 warnings (= pre-change baseline)
- cargo fmt --check — clean
@StefanSteiner StefanSteiner force-pushed the ssteiner/issue-70-flatten-error branch from 6c99d09 to a7d1c5f Compare May 28, 2026 07:39
@StefanSteiner StefanSteiner merged commit 2b86194 into tableau:main May 28, 2026
12 checks passed
StefanSteiner added a commit that referenced this pull request May 28, 2026
* feat: stabilize v0.3.0 public API bundle

This commit aggregates the breaking and additive API changes that ship
together as v0.3.0. The individual PRs landed under chore: prefixes to
defer release-please from cutting an early version; this single feat:
commit with a BREAKING CHANGE: footer is the trigger for the v0.3.0
release PR.

Bundle contents (all merged to main):
- #70 (PR #71)  — Flat public Error enum + ergonomic constructors
                  workspace-wide
- #69 (PR #73)  — Transaction API consolidation (RAII guard recommended;
                  raw trio deprecated and #[doc(hidden)])
- #61 + #62
  (PR #74)      — FromRow modernization: #[derive(FromRow)] in new
                  hyperdb-api-derive crate, RowAccessor with cached
                  name->index lookup, breaking trait signature change,
                  blanket tuple impls deleted, #[hyperdb(rename)] and
                  #[hyperdb(index)] attributes
- #76           — Follow-ups A/B/C: typed io::Error sources in process.rs,
                  Error::InvalidOperation variant for caller misuse,
                  structured SQLSTATE on Cancelled/Closed/Connection

Follow-up D (flatten internal client::Error) deferred to v0.3.x as
issue #75 — internal-only, zero external consumers, larger than originally
scoped.

The code change in this commit is a small documentation refresh on the
crate-level rustdoc to (a) include hyperdb-api-derive in the companion
crates list and (b) fix a stale crate name (sea-query-hyper ->
sea-query-hyperdb). The body of the commit is the BREAKING CHANGE:
footer below; release-please uses it to generate the v0.3.0 entry in
CHANGELOG.md.

See MIGRATING-0.3.md for full migration recipes covering every breaking
change in the bundle.

BREAKING CHANGE: v0.3.0 reshapes the public hyperdb_api::Error enum
into a flat canonical structure (no Box<dyn StdError> cause channel,
no kind() method, no Other catch-all variant), and its constructor
surface (Error::new and Error::with_cause are deleted in favor of
domain-specific snake_case constructors). It also changes the FromRow
trait signature from fn from_row(row: &Row) to fn from_row(row:
RowAccessor<'_>), deletes the blanket 1/2/3/4-tuple FromRow impls,
deprecates Connection::begin_transaction/commit/rollback (use the RAII
guard at Connection::transaction() instead), introduces a new
Error::InvalidOperation variant, and changes Error::Cancelled and
Error::Closed from tuple to struct variants carrying structured
sqlstate. Every variant has a snake_case constructor; the FromRow
derive lives in a re-exported hyperdb-api-derive crate. See
MIGRATING-0.3.md for migration recipes.

* chore(ci): publish hyperdb-api-derive in release.yml + dry-run in ci.yml

Pre-release adversarial review of the v0.3.0 rollup CI/CD config caught
that hyperdb-api-derive (added in PR #74) was missing from release.yml's
publish-in-dependency-order step. hyperdb-api/Cargo.toml strictly pins
hyperdb-api-derive = "=0.X.Y", so cargo publish -p hyperdb-api would
fail at release time when crates.io can't resolve the strict version
of derive (because release.yml never published it).

Verified topologically:
- hyperdb-api-derive has zero workspace deps (only syn/quote/proc-macro2
  from the registry), so it can publish before any workspace crate.
- It's a runtime dep of hyperdb-api only.
- Inserted right after hyperdb-api-salesforce; existing order otherwise
  unchanged. Added a dependency-order comment to the publish step
  explaining the topology so future contributors don't break it.

Also adds hyperdb-api-derive to ci.yml's publish dry-run job. The
dry-run job exists exactly to catch this class of bug before release
time. Without this addition, the same blocker could re-emerge after a
future major-version refactor of derive.

Updates the stale "7 workspace-member version rows" comment in
release-please.yml to reflect the current 8-member workspace
(hyperdb-api-derive added in #74). The lockfile-sync sentinel
enumerates members at runtime via cargo metadata, so the count is
informational; correctness is unchanged.

Verified locally:
- cargo publish -p hyperdb-api-derive --dry-run: succeeds
- cargo publish -p sea-query-hyperdb --dry-run: succeeds
- cargo publish -p hyperdb-bootstrap --dry-run: succeeds
- cargo metadata workspace topology check: order in release.yml is
  consistent with non-dev deps across all 7 publishable crates.
@StefanSteiner StefanSteiner deleted the ssteiner/issue-70-flatten-error branch June 4, 2026 07:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Flatten Error type to canonical M-ERRORS shape (drop Client wrapper, Box<dyn> source, Option<ErrorKind>)

1 participant