Skip to content

Commit

Permalink
feat(lib): exception api
Browse files Browse the repository at this point in the history
  • Loading branch information
unicornware committed Apr 23, 2021
1 parent 3585dde commit e60dae9
Show file tree
Hide file tree
Showing 17 changed files with 498 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ module.exports = {
'jsdoc/no-undefined-types': [
1,
{
definedTypes: ['AppException']
definedTypes: ['Exception', 'NodeJS']
}
],
'jsdoc/require-hyphen-before-param-description': 1,
Expand Down
2 changes: 1 addition & 1 deletion docs/spec/v1.0.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ allow for productive discussion and comments from readers.
- Database
- `process.env.FIREBASE_DATABASE_URL`
- Optional
- `process.env.FIREBASE_RTD_REPOS_VALIDATE_MODELS="true"`
- `process.env.FIREBASE_RTD_REPOS_VALIDATE="true"`
- **Repository API — Inspired by the
[TypeORM Repository API](https://github.com/typeorm/typeorm/blob/master/docs/repository-api.md)**
- `RTDRepository<E extends IEntity = IEntity, P extends Params = Params>`
Expand Down
11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@
"@types/faker": "latest",
"@types/jest": "latest",
"@types/lodash": "latest",
"@types/node": "latest",
"@types/urijs": "latest",
"@types/validator": "latest",
"@typescript-eslint/eslint-plugin": "latest",
"@typescript-eslint/parser": "latest",
"@zerollup/ts-transform-paths": "latest",
Expand Down Expand Up @@ -93,16 +96,22 @@
"ttypescript": "latest",
"typescript": "4.3.0-dev.20210306",
"underscore-cli": "latest",
"urijs": "latest",
"validator": "latest",
"yarn": "latest"
},
"peerDependencies": {
"@types/lodash": "latest",
"@types/node": "latest",
"simplytyped": "latest"
},
"peerDependenciesMeta": {
"@types/lodash": {
"optional": true
},
"@types/node": {
"optional": true
},
"simplytyped": {
"optional": true
}
Expand All @@ -118,6 +127,6 @@
"FIREBASE_DATABASE_URL",
"FIREBASE_PRIVATE_KEY",
"FIREBASE_PROJECT_ID",
"FIREBASE_RTD_REPOS_VALIDATE_MODELS"
"FIREBASE_RTD_REPOS_VALIDATE"
]
}
Empty file removed src/.gitkeep
Empty file.
34 changes: 34 additions & 0 deletions src/lib/enums/exception-class-name.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* @file Global Enums - ExceptionClassName
* @module lib/enums/ExceptionClassName
*/

export enum ExceptionClassName {
BAD_REQUEST = 'bad-request',
UNAUTHORIZED = 'not-authenticated',
PAYMENT_REQUIRED = 'payment-error',
FORBIDDEN = 'forbidden',
NOT_FOUND = 'not-found',
METHOD_NOT_ALLOWED = 'method-not-allowed',
NOT_ACCEPTABLE = 'not-acceptable',
PROXY_AUTHENTICATION_REQUIRED = 'proxy-auth-required',
REQUEST_TIMEOUT = 'timeout',
CONFLICT = 'conflict',
GONE = 'gone',
LENGTH_REQUIRED = 'length-required',
PRECONDITION_FAILED = 'precondition-failed',
PAYLOAD_TOO_LARGE = 'payload-too-large',
URI_TOO_LONG = 'uri-too-long',
UNSUPPORTED_MEDIA_TYPE = 'unsupported-media-type',
REQUESTED_RANGE_NOT_SATISFIABLE = 'range-not-satisfiable',
EXPECTATION_FAILED = 'expectation-failed',
I_AM_A_TEAPOT = 'teapot',
MISDIRECTED = 'misdirected',
UNPROCESSABLE_ENTITY = 'unprocessable-entity',
FAILED_DEPENDENCY = 'failed-dependency',
TOO_MANY_REQUESTS = 'too-many-requests',
INTERNAL_SERVER_ERROR = 'internal-server-error',
NOT_IMPLEMENTED = 'not-implemeneted',
BAD_GATEWAY = 'bad-gateway',
SERVICE_UNAVAILABLE = 'unavailable'
}
36 changes: 36 additions & 0 deletions src/lib/enums/exception-status.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { HttpStatus } from './http-status.enum'

/**
* @file Global Enums - ExceptionStatus
* @module lib/enums/ExceptionStatus
*/

export enum ExceptionStatus {
BAD_REQUEST = HttpStatus.BAD_REQUEST,
UNAUTHORIZED = HttpStatus.UNAUTHORIZED,
PAYMENT_REQUIRED = HttpStatus.PAYMENT_REQUIRED,
FORBIDDEN = HttpStatus.FORBIDDEN,
NOT_FOUND = HttpStatus.NOT_FOUND,
METHOD_NOT_ALLOWED = HttpStatus.METHOD_NOT_ALLOWED,
NOT_ACCEPTABLE = HttpStatus.NOT_ACCEPTABLE,
PROXY_AUTHENTICATION_REQUIRED = HttpStatus.PROXY_AUTHENTICATION_REQUIRED,
REQUEST_TIMEOUT = HttpStatus.REQUEST_TIMEOUT,
CONFLICT = HttpStatus.CONFLICT,
GONE = HttpStatus.GONE,
LENGTH_REQUIRED = HttpStatus.LENGTH_REQUIRED,
PRECONDITION_FAILED = HttpStatus.PRECONDITION_FAILED,
PAYLOAD_TOO_LARGE = HttpStatus.PAYLOAD_TOO_LARGE,
URI_TOO_LONG = HttpStatus.URI_TOO_LONG,
UNSUPPORTED_MEDIA_TYPE = HttpStatus.UNSUPPORTED_MEDIA_TYPE,
REQUESTED_RANGE_NOT_SATISFIABLE = HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE,
EXPECTATION_FAILED = HttpStatus.EXPECTATION_FAILED,
I_AM_A_TEAPOT = HttpStatus.I_AM_A_TEAPOT,
MISDIRECTED = HttpStatus.MISDIRECTED,
UNPROCESSABLE_ENTITY = HttpStatus.UNPROCESSABLE_ENTITY,
FAILED_DEPENDENCY = HttpStatus.FAILED_DEPENDENCY,
TOO_MANY_REQUESTS = HttpStatus.TOO_MANY_REQUESTS,
INTERNAL_SERVER_ERROR = HttpStatus.INTERNAL_SERVER_ERROR,
NOT_IMPLEMENTED = HttpStatus.NOT_IMPLEMENTED,
BAD_GATEWAY = HttpStatus.BAD_GATEWAY,
SERVICE_UNAVAILABLE = HttpStatus.SERVICE_UNAVAILABLE
}
54 changes: 54 additions & 0 deletions src/lib/enums/http-status.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* @file Global Enums - HttpStatus
* @module lib/enums/HttpStatus
*/

export enum HttpStatus {
CONTINUE = 100,
SWITCHING_PROTOCOLS = 101,
PROCESSING = 102,
EARLYHINTS = 103,
OK = 200,
CREATED = 201,
ACCEPTED = 202,
NON_AUTHORITATIVE_INFORMATION = 203,
NO_CONTENT = 204,
RESET_CONTENT = 205,
PARTIAL_CONTENT = 206,
AMBIGUOUS = 300,
MOVED_PERMANENTLY = 301,
FOUND = 302,
SEE_OTHER = 303,
NOT_MODIFIED = 304,
TEMPORARY_REDIRECT = 307,
PERMANENT_REDIRECT = 308,
BAD_REQUEST = 400,
UNAUTHORIZED = 401,
PAYMENT_REQUIRED = 402,
FORBIDDEN = 403,
NOT_FOUND = 404,
METHOD_NOT_ALLOWED = 405,
NOT_ACCEPTABLE = 406,
PROXY_AUTHENTICATION_REQUIRED = 407,
REQUEST_TIMEOUT = 408,
CONFLICT = 409,
GONE = 410,
LENGTH_REQUIRED = 411,
PRECONDITION_FAILED = 412,
PAYLOAD_TOO_LARGE = 413,
URI_TOO_LONG = 414,
UNSUPPORTED_MEDIA_TYPE = 415,
REQUESTED_RANGE_NOT_SATISFIABLE = 416,
EXPECTATION_FAILED = 417,
I_AM_A_TEAPOT = 418,
MISDIRECTED = 421,
UNPROCESSABLE_ENTITY = 422,
FAILED_DEPENDENCY = 424,
TOO_MANY_REQUESTS = 429,
INTERNAL_SERVER_ERROR = 500,
NOT_IMPLEMENTED = 501,
BAD_GATEWAY = 502,
SERVICE_UNAVAILABLE = 503,
GATEWAY_TIMEOUT = 504,
HTTP_VERSION_NOT_SUPPORTED = 505
}
10 changes: 10 additions & 0 deletions src/lib/enums/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* @file Entry Point - Global Enums
* @module lib/enums
*/

export { ExceptionClassName } from './exception-class-name.enum'
export { ExceptionStatus } from './exception-status.enum'
export { HttpStatus } from './http-status.enum'

/* eslint-disable prettier/prettier */
118 changes: 118 additions & 0 deletions src/lib/exceptions/__tests__/base.exception.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import {
ExceptionClassName as ClassName,
ExceptionStatus as Status
} from '@rtd-repos/lib/enums'
import TestSubject from '../base.exception'
import { DEM } from '../constants.exceptions'

/**
* @file Unit Tests - Exception
* @module lib/exceptions/tests/Exception
*/

describe('lib/exceptions/Exception', () => {
describe('exports', () => {
it('should export class by default', () => {
expect(TestSubject).toBeDefined()
expect(TestSubject.constructor.name).toBe('Function')
})
})

describe('constructor', () => {
describe('#errors', () => {
it('should === data.errors if array', () => {
const data = { errors: [] }
const Subject = new TestSubject(Status.BAD_REQUEST, undefined, data)

expect(Subject.data).toMatchObject({})
expect({ errors: Subject.errors }).toMatchObject(data)
})

it('should === data.errors if plain object', () => {
const data = { errors: { test: true } }
const Subject = new TestSubject(Status.LENGTH_REQUIRED, undefined, data)

expect(Subject.data).toMatchObject({})
expect({ errors: Subject.errors }).toMatchObject(data)
})

it('should === null if data.errors schema is invalid', () => {
const data = { errors: 5 }
const Subject = new TestSubject(Status.NOT_ACCEPTABLE, undefined, data)

expect(Subject.data).toMatchObject({})
expect(Subject.errors).toBe(null)
})
})

describe('#data', () => {
const properties = ['errors', 'message']

it(`should omit properties: ${properties}`, () => {
const data = { errors: { test: true }, foo: true, message: 'Test' }
const Subject = new TestSubject(Status.NOT_FOUND, undefined, data)

expect(Subject.data.errors).not.toBeDefined()
expect(Subject.data.message).not.toBeDefined()
expect(Subject.data).toMatchObject({ foo: data.foo })
})
})

describe('#message', () => {
it('should === default exception message', () => {
const Subject = new TestSubject()

expect(Subject.message).toBe(DEM)
})

it('should === data.message', () => {
const data = { message: 'Test override message' }
const Subject = new TestSubject(Status.MISDIRECTED, undefined, data)

expect(Subject.message).toBe(data.message)
expect(Subject.data.message).not.toBeDefined()
})
})
})

describe('.findNameByCode', () => {
it('should return name of exception', () => {
expect(TestSubject.findNameByCode(Status.GONE)).toBe('GONE')
})

it('should return empty string', () => {
expect(TestSubject.findNameByCode(-1)).toBe('')
})
})

describe('.formatCode', () => {
const code = Status.UNAUTHORIZED

it('should return 500 if exception status code is invalid', () => {
expect(TestSubject.formatCode(-1 * code)).toBe(500)
})

it('should return exception status code if valid', () => {
expect(TestSubject.formatCode(code)).toBe(code)
})
})

describe('#toJSON', () => {
it('should return json representation of exception', () => {
const code = Status.I_AM_A_TEAPOT
const data = { errors: { test: true }, foo: '' }
const message = 'Test error message'

const Subject = new TestSubject(code, message, data)

expect(Subject.toJSON()).toMatchObject({
className: ClassName.I_AM_A_TEAPOT,
code,
data: { foo: data.foo },
errors: data.errors,
message,
name: 'I_AM_A_TEAPOT'
})
})
})
})
Loading

0 comments on commit e60dae9

Please sign in to comment.