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
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