Skip to content

Commit

Permalink
feat: update to support contextual-error v2.0.0
Browse files Browse the repository at this point in the history
Improves compatability with native Error cause

BREAKING CHANGE: HttpError's static .cause() method was
changed to .getCause().
Constructor arguments were consolidated, so there are now two optional arguments instead.
Target ES version is now ES2022
  • Loading branch information
jdpnielsen committed Sep 17, 2024
1 parent ce083b9 commit 157fff6
Show file tree
Hide file tree
Showing 13 changed files with 251 additions and 68 deletions.
20 changes: 20 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
name: "🐛 Bug Report"
about: Report a reproducible bug or regression.
title: ''
labels: bug
assignees: ''

---

## Current Behavior

<!-- Describe how the issue manifests. -->

## Expected Behavior

<!-- Describe what the desired behavior would be. -->

## Debug log

<!-- Re-run the command with the --debug flag enabled and paste the output here -->
34 changes: 34 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
name: 🌈 Feature request
about: Suggest an amazing new idea for this project
title: ''
labels: enhancement
assignees: ''

---

## Feature Request

**Is your feature request related to a problem? Please describe.**
<!-- A clear and concise description of what the problem is. Ex. I have an issue when [...] -->

**Describe the solution you'd like**
<!-- A clear and concise description of what you want to happen. Add any considered drawbacks. -->

**Describe alternatives you've considered**
<!-- A clear and concise description of any alternative solutions or features you've considered. -->

## Are you willing to resolve this issue by submitting a Pull Request?

<!--
Remember that first-time contributors are welcome! 🙌
-->

- [ ] Yes, I have the time, and I know how to start.
- [ ] Yes, I have the time, but I don't know how to start. I would need guidance.
- [ ] No, I don't have the time, although I believe I could do it if I had the time...
- [ ] No, I don't have the time and I wouldn't even know how to start.

<!--
👋 Have a great day and thank you for the feature request!
-->
42 changes: 42 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!--
😀 Wonderful! Thank you for opening a pull request.
Please fill in the information below to expedite the review
and (hopefully) merge of your change.
-->

### Description of change

<!--
Please be clear and concise what the change is intended to do,
why this change is needed, and how you've verified that it
corrects what you intended.
In some cases it may be helpful to include the current behavior
and the new behavior.
If the change is related to an open issue, you can link it here.
If you include `Fixes #0000` (replacing `0000` with the issue number)
when this is merged it will automatically mark the issue as fixed and
close it.
-->

### Pull-Request Checklist

<!--
Please make sure to review and check all of the following.
If an item is not applicable, you can add "N/A" to the end.
-->

- [ ] Code is up-to-date with the `main` branch
- [ ] `npm run lint` passes with this change
- [ ] `npm run test` passes with this change
- [ ] This pull request links relevant issues as `Fixes #0000`
- [ ] There are new or updated unit tests validating the change
- [ ] Documentation has been updated to reflect this change
- [ ] The new commits follow conventions outlined in the [conventional commit spec](https://www.conventionalcommits.org/en/v1.0.0/)

<!--
🎉 Thank you for contributing!
-->
23 changes: 23 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Pull Request

on: [pull_request]

jobs:
build:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [16.x, 22.x]

steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run build --if-present
- run: npm test
- name: Coverage check
run: npm run cov:check
30 changes: 30 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Release
on:
push:
branches:
- main
jobs:
release:
name: Release
runs-on: ubuntu-latest
permissions:
contents: write
issues: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Test
run: npm test
- name: Coverage check
run: npm run cov:check
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22
8 changes: 0 additions & 8 deletions .travis.yml

This file was deleted.

7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Http Error: rich http errors

[![Build Status](https://travis-ci.org/jdpnielsen/http-error.svg?branch=master)](https://travis-ci.org/jdpnielsen/http-error)
![NPM Version](https://img.shields.io/npm/v/%40jdpnielsen%2Fhttp-error)
[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org)

The Http Error module is an extension of [Contextual Error](https://github.com/jdpnielsen/contextual-error), inspired by Hapi's [Boom]([email protected]:hapijs/boom.git).
Expand Down Expand Up @@ -127,10 +127,11 @@ import HttpError from '@jdpnielsen/http-error';
import { Info } from '@jdpnielsen/contextual-error';
export function custom(message?: string, input?: { cause?: Error, info?: Info, publicInfo?: Info }): HttpError {
return new HttpError(500, 'Custom', message, input?.publicInfo, input?.cause, {
name: 'customError',
return new HttpError(500, 'Custom', message, input?.publicInfo, {
name: 'customError',
constructorOpt: custom,
info: input?.info,
cause: input?.cause,
});
}
```
48 changes: 28 additions & 20 deletions src/lib/http-error.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,23 +52,31 @@ test('should build an http friendly error', t => {

test('should accept an Error as cause', t => {
const parentError = new Error('ParentError');
const childError = new HttpError(500, 'Internal Server Error', 'ChildError', undefined, parentError);
const childError = new HttpError(500, 'Internal Server Error', 'ChildError', undefined, { cause: parentError });

t.is(childError.message, `ChildError`, 'builds correct message');
t.is((childError as any).cause, parentError, 'has expected cause');
});

test('should accept a CError as cause', t => {
const parentError = new CError('ParentError');
const childError = new HttpError(500, 'Internal Server Error', 'ChildError', undefined, parentError);
const childError = new HttpError(500, 'Internal Server Error', 'ChildError', undefined, { cause: parentError });

t.is(childError.message, `ChildError`, 'builds correct message');
t.is((childError as any).cause, parentError, 'has expected cause');
});

test('should accept a HttpError as cause', t => {
const parentError = new HttpError(400, 'ParentError');
const childError = new HttpError(500, 'Internal Server Error', 'ChildError', undefined, { cause: parentError });

t.is(childError.message, `ChildError`, 'builds correct message');
t.is((childError as any).cause, parentError, 'has expected cause');
});

test('should accept an options object', t => {
const uncausedErr = new CError('uncausedErr', undefined, { info: { text: '_uncausedErr_', fromUncaused: true }, name: 'UncausedError' });
const causedErr = new HttpError(500, 'Internal Server Error', 'causedErr', undefined, uncausedErr, { info: { text: '_causedErr_', fromCaused: true }, name: 'CausedError' });
const uncausedErr = new CError('uncausedErr', { info: { text: '_uncausedErr_', fromUncaused: true }, name: 'UncausedError' });
const causedErr = new HttpError(500, 'Internal Server Error', 'causedErr', undefined, { cause: uncausedErr, info: { text: '_causedErr_', fromCaused: true }, name: 'CausedError' });

t.is(uncausedErr.name, `UncausedError`, 'sets correct name');
t.is(causedErr.name, `CausedError`, 'sets correct name');
Expand All @@ -85,14 +93,14 @@ test('should accept an options object', t => {

test('should stringify correctly', t => {
const parentError = new CError('ParentError');
const childError = new HttpError(500, 'Internal Server Error', 'ChildError', undefined, parentError, { info: { foo: 'bar' } });
const childError = new HttpError(500, 'Internal Server Error', 'ChildError', undefined, { cause: parentError, info: { foo: 'bar' } });

t.is(childError.toString(), 'HttpError: ChildError');
});

test('should JSON.stringify correctly', t => {
const parentError = new CError('ParentError');
const childError = new HttpError(500, 'Internal Server Error', 'ChildError', { bar: 'baz' }, parentError, { info: { foo: 'bar' } });
const childError = new HttpError(500, 'Internal Server Error', 'ChildError', { bar: 'baz' }, { cause: parentError, info: { foo: 'bar' } });

t.is(JSON.stringify(childError), '{"statusCode":500,"error":"Internal Server Error","message":"ChildError","info":{"bar":"baz"}}');
});
Expand All @@ -119,18 +127,18 @@ test('should have a static .isHttpError method which returns true when given a C
t.is(HttpError.isHttpError(httpError), true, 'handles httpError');
});

test('should have a static .cause which returns the expected cause', t => {
test('should have a static .getCause which returns the expected cause', t => {
const parentError = new Error('ParentError');
const childError = new HttpError(500, 'Internal Server Error', 'ChildError', undefined, parentError);
const childError = new HttpError(500, 'Internal Server Error', 'ChildError', undefined, { cause: parentError});

t.is(HttpError.cause(parentError), null, 'handles regular errors');
t.is(HttpError.cause(childError), parentError, 'returns cause');
t.is(HttpError.getCause(parentError), null, 'handles regular errors');
t.is(HttpError.getCause(childError), parentError, 'returns cause');
});

test('should have a static .info which returns the info object', t => {
const parentError = new Error('ParentError');
const childError = new CError('ChildError', parentError, { info: { foo: 'bar' } });
const grandChildError = new HttpError(500, 'Internal Server Error', 'GrandChildError', undefined, childError, { info: { bar: 'baz' } });
const childError = new CError('ChildError', { cause: parentError, info: { foo: 'bar' } });
const grandChildError = new HttpError(500, 'Internal Server Error', 'GrandChildError', undefined, { cause: childError, info: { bar: 'baz' } });

t.deepEqual(HttpError.info(parentError), {}, 'handles regular errors');
t.deepEqual(HttpError.info(childError), { foo: 'bar' }, 'returns info');
Expand All @@ -139,8 +147,8 @@ test('should have a static .info which returns the info object', t => {

test('should have a static .fullStack which returns the combined stack trace', t => {
const parentError = new Error('ParentError');
const childError = new CError('ChildError', parentError, { info: { foo: 'bar' } });
const grandChildError = new HttpError(500, 'Internal Server Error', 'GrandChildError', undefined, childError, { info: { bar: 'baz' } });
const childError = new CError('ChildError', { cause: parentError, info: { foo: 'bar' } });
const grandChildError = new HttpError(500, 'Internal Server Error', 'GrandChildError', undefined, { cause: childError, info: { bar: 'baz' } });

const expectedParentStack = helperStack('ParentError', 'Error');
const expectedChildStack = helperStack('ChildError: ParentError', 'CError') + '\ncaused by: ' + expectedParentStack;
Expand All @@ -153,9 +161,9 @@ test('should have a static .fullStack which returns the combined stack trace', t

test('should have a static .findCauseByName', t => {
const parentError = new Error('ParentError');
const childError = new CError('ChildError', parentError);
const grandChildError = new CError('GrandChildError', childError, { name: 'CustomErrorName' });
const finalError = new HttpError(500, 'Internal Server Error', 'finalError', undefined, grandChildError);
const childError = new CError('ChildError', { cause: parentError });
const grandChildError = new CError('GrandChildError', { cause: childError, name: 'CustomErrorName' });
const finalError = new HttpError(500, 'Internal Server Error', 'finalError', undefined, { cause: grandChildError });

t.is(HttpError.findCauseByName(grandChildError, 'Error'), parentError, 'finds regular Error');
t.is(HttpError.findCauseByName(grandChildError, 'CError'), childError, 'finds CError');
Expand All @@ -166,9 +174,9 @@ test('should have a static .findCauseByName', t => {

test('should have a static .hasCauseWithName', t => {
const parentError = new Error('ParentError');
const childError = new CError('ChildError', parentError);
const grandChildError = new CError('GrandChildError', childError, { name: 'CustomErrorName' });
const finalError = new HttpError(500, 'Internal Server Error', 'finalError', undefined, grandChildError);
const childError = new CError('ChildError', { cause: parentError });
const grandChildError = new CError('GrandChildError', { cause: childError, name: 'CustomErrorName' });
const finalError = new HttpError(500, 'Internal Server Error', 'finalError', undefined, { cause: grandChildError });

t.is(HttpError.hasCauseWithName(grandChildError, 'Error'), true, 'finds regular Error');
t.is(HttpError.hasCauseWithName(grandChildError, 'CError'), true, 'finds CError');
Expand Down
4 changes: 2 additions & 2 deletions src/lib/http-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export class HttpError extends WError {
public readonly statusCode: number;
private response: HttpErrorResponse;

constructor(statusCode: number, error: string, message?: string, publicInfo?: Info, cause?: Error, options?: Options) {
super(message || error, cause, options);
constructor(statusCode: number, error: string, message?: string, publicInfo?: Info, options?: Options) {
super(message || error, options);
Object.defineProperty(this, HTTPERROR_SYMBOL, { value: true });

if (options?.name) {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/http-errors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ function causeMacro(t: ExecutionContext, input: typeof factories[number] & { cau
const error = input.factory(input.errorMessage, {
cause: input.cause,
});
t.deepEqual(HttpError.cause(error), expected);
t.deepEqual(HttpError.getCause(error), expected);
}
causeMacro.title = titleBuilder;

Expand Down
Loading

0 comments on commit 157fff6

Please sign in to comment.