Skip to content

Commit

Permalink
feat(http): add getHttpErrorConstructorForStatusCode
Browse files Browse the repository at this point in the history
Allows you to get the constructor for a specified HTTP status code.
  • Loading branch information
jeffijoe committed Dec 26, 2019
1 parent f8391c8 commit 9652921
Show file tree
Hide file tree
Showing 9 changed files with 1,294 additions and 768 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Expand Up @@ -9,8 +9,8 @@ notifications:
# Add additional versions here as appropriate.
node_js:
- 'stable'
- '12'
- '10'
- '8'

# Lint errors should trigger a failure.
before_script:
Expand Down
1 change: 0 additions & 1 deletion .vscode/settings.json
@@ -1,7 +1,6 @@
// Place your settings in this file to overwrite default and user settings.
{
"prettier.bracketSpacing": true,
"prettier.eslintIntegration": true,
"prettier.singleQuote": true,
"prettier.semi": false,
"eslint.enable": true,
Expand Down
12 changes: 12 additions & 0 deletions README.md
Expand Up @@ -239,6 +239,18 @@ const price = await getSomeRemotePriceThatMayOrMayNotExist().catch(
)
```

## `getHttpErrorConstructorForStatusCode(statusCode: number): HttpErrorConstructor`

Given a status code, returns the proper error to throw.

```ts
import { getHttpErrorConstructorForStatusCode, BadRequest } from 'fejl'

const ErrorCtor = getHttpErrorConstructorForStatusCode(400)

ErrorCtor === BadRequest // true
```

# What's in a name?

"fejl" _[fɑjl]_ is danish for _"error"_, and when pronounced in English also sounds like the word "fail".
Expand Down
32 changes: 16 additions & 16 deletions package.json
Expand Up @@ -38,28 +38,28 @@
"homepage": "https://github.com/jeffijoe/fejl#readme",
"dependencies": {
"make-error": "^1.3.5",
"tslib": "^1.9.3"
"tslib": "^1.10.0"
},
"devDependencies": {
"@semantic-release/condition-codeship": "^1.1.0",
"@semantic-release/release-notes-generator": "^7.1.4",
"@types/jest": "^24.0.13",
"@types/node": "^12.0.2",
"@types/rimraf": "^2.0.2",
"coveralls": "^3.0.3",
"husky": "^2.3.0",
"jest": "^24.8.0",
"lint-staged": "^8.1.7",
"prettier": "^1.17.1",
"rimraf": "^2.6.3",
"semantic-release": "^15.13.12",
"@semantic-release/release-notes-generator": "^7.3.5",
"@types/jest": "^24.0.24",
"@types/node": "^13.1.0",
"@types/rimraf": "^2.0.3",
"coveralls": "^3.0.9",
"husky": "^3.1.0",
"jest": "^24.9.0",
"lint-staged": "^9.5.0",
"prettier": "^1.19.1",
"rimraf": "^3.0.0",
"semantic-release": "^15.14.0",
"semantic-release-conventional-commits": "^2.0.1",
"smid": "^0.1.1",
"ts-jest": "^24.0.2",
"tslint": "^5.16.0",
"ts-jest": "^24.2.0",
"tslint": "^5.20.1",
"tslint-config-prettier": "^1.18.0",
"tslint-config-standard": "^8.0.1",
"typescript": "^3.4.5"
"tslint-config-standard": "^9.0.0",
"typescript": "^3.7.4"
},
"prettier": {
"semi": false,
Expand Down
9 changes: 9 additions & 0 deletions src/__tests__/http.test.ts
Expand Up @@ -28,3 +28,12 @@ describe('http', () => {
expect(() => ProxyIsDown.assert(false, 'Oh well')).toThrowError(ProxyIsDown)
})
})

describe('getHttpErrorConstructorForStatusCode', () => {
it('returns the correct error constructor', () => {
expect(http.getHttpErrorConstructorForStatusCode(403)).toBe(http.Forbidden)
expect(http.getHttpErrorConstructorForStatusCode(888)).toBe(
http.GeneralError
)
})
})
33 changes: 19 additions & 14 deletions src/__tests__/retry.test.ts
@@ -1,4 +1,5 @@
import { retry, computeNextWaitTime } from '../retry'
import { throws } from 'smid'

test('basic', async () => {
let i = 0
Expand All @@ -14,13 +15,15 @@ test('basic', async () => {

test('exhaust', async () => {
let count = 0
const err = await retry(
again => {
count++
throw again(new Error('Nah'))
},
{ tries: 2, minTimeout: 5, factor: 1 }
).catch(err => err)
const err = await throws(
retry(
again => {
count++
throw again(new Error('Nah'))
},
{ tries: 2, minTimeout: 5, factor: 1 }
)
)
expect(err.message).toBe('Nah')
expect(count).toBe(2)
})
Expand All @@ -36,13 +39,15 @@ test('wait time calculation', () => {

test('tries = 0', async () => {
let count = 0
const err = await retry(
again => {
count++
throw again(new Error('Nah'))
},
{ tries: 0, minTimeout: 5, factor: 1 }
).catch(err => err)
const err = await throws(
retry(
again => {
count++
throw again(new Error('Nah'))
},
{ tries: 0, minTimeout: 5, factor: 1 }
)
)
expect(err.message).toBe('Nah')
expect(count).toBe(1)
})
100 changes: 99 additions & 1 deletion src/http.ts
Expand Up @@ -261,6 +261,104 @@ export class NetworkAuthenticationRequired extends MakeHttpError(
'Network Authentication Required'
) {}

/**
* HTTP error constructor.
*/
export type HttpErrorConstructor = BaseErrorConstructor<{ statusCode: number }>

/**
* Gets an error constructor for a HTTP status code.
*
* @param statusCode
*/
export function getHttpErrorConstructorForStatusCode(
statusCode: number
): HttpErrorConstructor {
/* istanbul ignore next */
switch (statusCode) {
case 400:
return BadRequest
case 401:
return NotAuthenticated
case 402:
return PaymentRequired
case 403:
return Forbidden
case 404:
return NotFound
case 405:
return MethodNotAllowed
case 406:
return NotAcceptable
case 407:
return ProxyAuthenticationRequired
case 408:
return Timeout
case 409:
return Conflict
case 410:
return Gone
case 411:
return LengthRequired
case 412:
return PreconditionFailed
case 413:
return PayloadTooLarge
case 414:
return UriTooLong
case 415:
return UnsupportedMediaType
case 416:
return RequestRangeNotSatisfiable
case 417:
return ExpectationFailed
case 418:
return IAmATeapot
case 421:
return MisdirectedRequest
case 422:
return Unprocessable
case 423:
return Locked
case 424:
return FailedDependency
case 425:
return TooEarly
case 426:
return UpgradeRequired
case 428:
return PreconditionRequired
case 429:
return TooManyRequests
case 431:
return RequestHeaderFieldsTooLarge
case 451:
return UnavailableForLegalReasons
case 501:
return NotImplemented
case 502:
return BadGateway
case 503:
return ServiceUnavailable
case 504:
return GatewayTimeout
case 505:
return HttpVersionNotSupported
case 506:
return VariantAlsoNegotiates
case 507:
return InsufficientStorage
case 508:
return LoopDetected
case 510:
return NotExtended
case 511:
return NetworkAuthenticationRequired
default:
return GeneralError
}
}

/**
* Builds a HTTP status code based error class.
*
Expand All @@ -271,6 +369,6 @@ export class NetworkAuthenticationRequired extends MakeHttpError(
function MakeHttpError(
statusCode: number,
message: string
): BaseErrorConstructor<{ statusCode: number }> {
): HttpErrorConstructor {
return MakeErrorClass(message, { statusCode })
}
13 changes: 9 additions & 4 deletions src/make-error-class.ts
Expand Up @@ -44,12 +44,12 @@ export class BaseError<A> extends CustomError {
data: T,
message?: string,
attrs?: Partial<ErrorAttributes<A>>
): T | never {
): NonFalsy<T> {
if (!data) {
throw new this(message, attrs)
}

return data
return data as NonFalsy<T>
}

/**
Expand Down Expand Up @@ -114,7 +114,12 @@ export class BaseError<A> extends CustomError {
/**
* Asserter function.
*/
export type Asserter<T> = (value: any) => T | never
export type Asserter<T> = (value: T) => NonFalsy<T>

/**
* Non-falsy value.
*/
export type NonFalsy<T> = Exclude<T, false | 0 | undefined | null | ''>

/**
* Error attributes.
Expand Down Expand Up @@ -144,7 +149,7 @@ export interface BaseErrorConstructor<A> {
data: T,
message?: string,
attrs?: Partial<ErrorAttributes<A>>
): T | never
): NonFalsy<T>
/**
* Retries the function until it does not throw the error type.
*/
Expand Down

0 comments on commit 9652921

Please sign in to comment.