Repository: lukeautry/tsoa (ts)

Issue:
ValidateError should extend Error
<!--- Provide a general summary of the issue in the Title above -->

## Sorting

* **I'm submitting a ...**
  - [x] bug report
  - [ ] feature request
  - [ ] support request

* I confirm that I
  - [x] used the [search](https://github.com/lukeautry/tsoa/search?type=Issues) to make sure that a similar issue hasn't already been submit

## Expected Behavior
- `ValidateError` should extend `Error`

## Current Behavior
- `ValidateError` does not extend `Error`

As a result, tools like [Sentry](https://sentry.io/) do not fully work with `ValidateError`. Sentry makes a [synthetic exception](https://docs.sentry.io/platforms/node/#hints-for-events) and does not include it's full exception info, it only reports:
```
Non-Error exception captured with keys: fields, message, name, status
```

## Possible Solution
- update `ValidateError` to extend `Error`

## Steps to Reproduce
1. Generate an express server
2. Make an API request that triggers a validation error, for example: https://github.com/lukeautry/tsoa/blob/313012a1214f0afa95756cf23bbc13f46277499e/tests/integration/express-server.spec.ts#L463-L470
3. `ValidateError` should be thrown, and it should extend `Error`

## Context (Environment)
Version of the library: 2.5.13
Version of NodeJS: 10.16.3
* Confirm you were using yarn not npm - I use npm, not yarn

## Detailed Description
The simplest non-breaking change would be to update this line: https://github.com/lukeautry/tsoa/blob/533fe059cc8373e31e8ab6d7698c18ae3ea3b709/src/routeGeneration/templateHelpers.ts#L746
to this:
```ts
export class ValidateError extends Error implements Exception {
```

## Breaking change?
- Yes

Pull request description:
ValidateError should extend Error
### All Submissions:

* [x] Have you followed the guidelines in our [Contributing](https://github.com/lukeautry/tsoa/tree/master/docs/CONTRIBUTING.md) document?
* [x] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/lukeautry/tsoa/pulls) for the same update/change?
* [x] Have you written unit tests?
* [ ] Have you written unit tests that cover the negative cases (i.e.: if bad data is submitted, does the library respond properly)?
* [x] This PR is associated with an existing issue?

**Closing issues**
Closes #658

### If this is a new feature submission:

* [x] Has the issue had a maintainer respond to the issue and clarify that the feature is something that aligns with the [goals](https://github.com/lukeautry/tsoa#goal) and [philosophy](https://github.com/lukeautry/tsoa#philosophy) of the project?

**Potential Problems With The Approach**
- not ideal to have 2 separate validation error classes
- when I initially updated `ValidateError` to extend `Error`, all server tests passed except hapi, which had this message:
```
  1) Hapi Server
       returns error if missing required query parameter:
     Error: the object {
  "error": "Error: expected 400 \"Bad Request\", got 500 \"Internal Server Error\""
  "response": "Error: cannot GET /v1/GetTest/1/true/test?booleanParam=true&stringParam=test1234 (500)"
} was thrown, throw an Error :)
      at process._tickCallback (internal/process/next_tick.js:68:7)
```
- and the corresponding server side error was:
```
Debug: internal, implementation, error 
    Error: Cannot wrap an error
    at new module.exports (/Users/alden/Desktop/alden-tsoa/tsoa/node_modules/@hapi/hoek/lib/error.js:23:19)
    at Object.module.exports [as assert] (/Users/alden/Desktop/alden-tsoa/tsoa/node_modules/@hapi/hoek/lib/assert.js:20:11)
    at internals.Toolkit.response (/Users/alden/Desktop/alden-tsoa/tsoa/node_modules/@hapi/hapi/lib/toolkit.js:174:14)
    at handler (/Users/alden/Desktop/alden-tsoa/tsoa/tests/fixtures/hapi/routes.ts:1091:14)
    at module.exports.internals.Manager.execute (/Users/alden/Desktop/alden-tsoa/tsoa/node_modules/@hapi/hapi/lib/toolkit.js:41:33)
    at Object.internals.handler (/Users/alden/Desktop/alden-tsoa/tsoa/node_modules/@hapi/hapi/lib/handler.js:46:48)
    at exports.execute (/Users/alden/Desktop/alden-tsoa/tsoa/node_modules/@hapi/hapi/lib/handler.js:31:36)
    at Request._lifecycle (/Users/alden/Desktop/alden-tsoa/tsoa/node_modules/@hapi/hapi/lib/request.js:312:68)
    at process._tickCallback (internal/process/next_tick.js:68:7)
```
- which comes from the template here: https://github.com/lukeautry/tsoa/blob/82d02735eacda1842cef2d9587df46c93483a72b/src/routeGeneration/templates/hapi.hbs#L81
- very open to better ideas for fixing this issue if you have them!
<!-- if there are areas of the solution that you think might have limitations or potential "gotchas", then discuss them here -->
<!-- if you have many questions, then please consider discussing them in the issue thread (unless you need to share code to illustrate the questions) -->

**Test plan**
- unit tests assert that ValidateError extends Error, which was the primary goal
<!-- explain why the unit tests that you added are demonstrating that the feature works -->

Patch:
diff --git a/src/routeGeneration/templateHelpers.ts b/src/routeGeneration/templateHelpers.ts
index e6beed82..7cfb5c93 100644
--- a/src/routeGeneration/templateHelpers.ts
+++ b/src/routeGeneration/templateHelpers.ts
@@ -750,10 +750,13 @@ export interface Exception extends Error {
   status: number;
 }
 
-export class ValidateError implements Exception {
+export class ValidateError extends Error implements Exception {
   public status = 400;
   public name = 'ValidateError';
 
-  constructor(public fields: FieldErrors, public message: string) {}
+  constructor(public fields: FieldErrors, public message: string) {
+    super(message);
+  }
 }
+
 export * from './tsoa-route';
diff --git a/src/routeGeneration/templates/hapi.hbs b/src/routeGeneration/templates/hapi.hbs
index f5fdb521..dfda3d89 100644
--- a/src/routeGeneration/templates/hapi.hbs
+++ b/src/routeGeneration/templates/hapi.hbs
@@ -16,6 +16,7 @@ import { {{name}} } from '{{modulePath}}';
 {{#if authenticationModule}}
 import { hapiAuthentication } from '{{authenticationModule}}';
 {{/if}}
+import { boomify, Payload } from '@hapi/boom';
 
 // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
 
@@ -77,9 +78,14 @@ export function RegisterRoutes(server: any) {
                     try {
                         validatedArgs = getValidatedArgs(args, request);
                     } catch (err) {
-                        return h
-                            .response(err)
-                            .code(err.status || 500);
+                        const boomErr = boomify(err);
+                        boomErr.output.statusCode = err.status || 500;
+                        boomErr.output.payload = {
+                            name: err.name,
+                            fields: err.fields,
+                            message: err.message,
+                        } as unknown as Payload;
+                        throw boomErr;
                     }
 
                     {{#if ../../iocModule}}


Test patch:
diff --git a/tests/unit/templating/templateHelpers.spec.ts b/tests/unit/templating/templateHelpers.spec.ts
index 826f1f81..3f084c46 100644
--- a/tests/unit/templating/templateHelpers.spec.ts
+++ b/tests/unit/templating/templateHelpers.spec.ts
@@ -2,6 +2,7 @@ import { expect } from 'chai';
 import 'mocha';
 import { FieldErrors, TsoaRoute, ValidationService } from '../../../src';
 import { AdditionalProps } from '../../../src/routeGeneration/routeGenerator';
+import { ValidateError } from '../../../src/routeGeneration/templateHelpers';
 import { TypeAliasModel1, TypeAliasModel2 } from '../../fixtures/testModel';
 
 it('should allow additionalProperties (on a union) if noImplicitAdditionalProperties is set to silently-remove-extras', () => {
@@ -613,3 +614,7 @@ it('should throw if properties on nOl are missing', () => {
     },
   });
 });
+
+it('should throw an Error', () => {
+  expect(new ValidateError({}, '')).to.be.an.instanceof(Error);
+});