Skip to content

Give concrete HipError subclasses default messages - #120

Open
Sarita-Joshi wants to merge 1 commit into
trycatchal:mainfrom
Sarita-Joshi:feat/default-error-messages
Open

Give concrete HipError subclasses default messages#120
Sarita-Joshi wants to merge 1 commit into
trycatchal:mainfrom
Sarita-Joshi:feat/default-error-messages

Conversation

@Sarita-Joshi

Copy link
Copy Markdown
Contributor

What does this PR do?

Constructed without a message, each concrete HipError subclass now uses its own default:

Class Default
HipBadInputs Inputs not valid
HipUnauthorized Unauthorized
HipForbidden Forbidden
HipNotFound Resource not found
HipConflict Conflict
HipInternal Internal server error

Every string is one the library already put on the wire (mongoose loaders' 404, zod helpers' 422, the lifecycle scrub message), and all in-tree throw sites pass an explicit message — so no response changes.

Why: it binds the HTTP status and the client-visible message to a single symbol, so a caller can pass the class where an error is wanted and do new DenialError() at each throw site. A reusable authorization gate then gets a fresh error with its own stack per denial, instead of sharing one prebuilt instance across every request or wrapping the constructor in a factory thunk:

function requireOwner(DenialError: new () => HipError = HipForbidden) { /* … */ }
requireOwner(HipNotFound); // denial answers exactly as a missing row does

An explicit message still wins, including an empty one (??, not ||), and a subclass that declares no default behaves exactly as before.

INTERNAL_ERROR_MESSAGE moves to ./errors.js (it is HipInternal's default) and is re-exported from ./core.js, so existing imports are unaffected — verified identical from hipthrusts, hipthrusts/core and hipthrusts/errors in both the CJS and ESM builds.

Wire change worth noting: a bare new HipForbidden() previously produced {"error":""} and now produces {"error":"Forbidden"}. That's the point of the change, but it is visible — additive/minor.

No version bump — releases are cut as their own PR.

Checklist

  • Tests added/updated (type-level tests for changes to the type machinery)
  • pnpm test, pnpm lint, pnpm typecheck, and pnpm build pass locally
  • Docs updated (README / examples) if the public API changed

Also ran pnpm format:check, pnpm check:exports, pnpm smoke — all clean; 241 tests pass.

🤖 Generated with Claude Code

Constructed without a message, each subclass now uses its own: HipBadInputs
"Inputs not valid", HipUnauthorized "Unauthorized", HipForbidden "Forbidden",
HipNotFound "Resource not found", HipConflict "Conflict", HipInternal
"Internal server error". Every string is one the library already put on the
wire, so nothing internal changes -- all in-tree throw sites pass an explicit
message.

This binds the HTTP status and the client-visible message to a single symbol,
so a caller can pass the CLASS where an error is wanted and do
`new DenialError()` at each throw site: a reusable authorization gate gets a
fresh error with its own stack per denial, instead of sharing one prebuilt
instance across every request or wrapping the constructor in a factory thunk.

An explicit message still wins, including an empty one (`??`, not `||`), and a
subclass that declares no default behaves exactly as before.

INTERNAL_ERROR_MESSAGE moves to ./errors.js, where it is HipInternal's default,
and is re-exported from ./core.js so existing imports are unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@trycatchal trycatchal left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm! thanks!

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.

2 participants