Skip to content

Commit

Permalink
Fix errors not bubbling
Browse files Browse the repository at this point in the history
  • Loading branch information
flaviouk committed Oct 14, 2020
1 parent 2174570 commit b6e63f2
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 77 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "api-next",
"version": "1.1.0",
"version": "1.2.0",
"repository": "https://github.com/imflavio/api-next",
"author": "Flávio Carvalho <[email protected]>",
"license": "MIT",
Expand Down
1 change: 1 addition & 0 deletions src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const connectToDatabase = ({
cachedDb[name] = true
}
} catch (err) {
console.error(err)
throw new DatabaseConnectionError()
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/mongoose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import { NotFoundError } from './errors/not-found-error'
export const createMongooseMethods = (
Model: ReturnType<typeof mongoose.model>,
): ServiceMethods => ({
find: async () => Model.find(),
find: async (query) => Model.find(query),
create: async (body) => Model.create(body),
get: async (pk) => Model.findById(pk),
update: async (pk, body) => {
const data = await Model.findById(pk)
get: async (pk, query) => Model.findOne({ _id: pk, ...query }),
update: async (pk, body, query) => {
const data = await Model.findOne({ _id: pk, ...query })
if (!data) throw new NotFoundError()

data.set(body)
Expand Down
152 changes: 82 additions & 70 deletions src/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,94 +39,106 @@ const getPk = (pk: Pk = {}, query: NextApiRequest['query']): string | null => {
return pk?.cast && value ? pk.cast(value!) : value
}

const getStatusCode = (type: keyof ServiceMethods) => {
switch (type) {
case 'create':
return Status.HTTP_201_CREATED
default:
return Status.HTTP_200_OK
}
}

const runHandler = async (
type: keyof ServiceMethods,
options: ServiceOptions,
req: NextApiRequest,
pk: string,
) => {
switch (type) {
case 'find':
return options.find!(req.query)
case 'create':
return options.create!(req.body)
case 'get':
return options.get!(pk, req.query)
case 'update':
return options.update!(pk, req.body, req.query)
case 'patch':
return options.patch!(pk, req.body, req.query)
case 'remove':
return options.remove!(pk)
}
}

const handleService = async (
type: keyof ServiceMethods,
options: ServiceOptions,
export const createService = (options: ServiceOptions) => async (
req: NextApiRequest,
res: NextApiResponse,
) => {
const pk = getPk(options.pk, req.query)
logger(req, res, () => {})

const runHooks = async (hooks: Hook[]) => {
for (let index = 0; index < hooks.length; index++) {
await hooks[index](req, res, () => {})
try {
const pk = getPk(options.pk, req.query)
const method = req.method?.toUpperCase()

const runHooks = async (hooks: Hook[]) => {
for (let index = 0; index < hooks.length; index++) {
await hooks[index](req, res, () => {})
}
}
}

const allBeforeHooks = options.hooks?.before?.all ?? []
const beforeHooks = options.hooks?.before?.[type] ?? []
await runHooks([...allBeforeHooks, ...beforeHooks])
switch (true) {
case 'get' in options && pk && method === Method.GET: {
const allBeforeHooks = options.hooks?.before?.all ?? []
const beforeHooks = options.hooks?.before?.get ?? []
await runHooks([...allBeforeHooks, ...beforeHooks])

const result = await runHandler(type, options, req, pk!)
const result = await options.get!(pk!, req.query)

const allAfterHooks = options.hooks?.after?.all ?? []
const afterHooks = options.hooks?.after?.[type] ?? []
await runHooks([...allAfterHooks, ...afterHooks])
const allAfterHooks = options.hooks?.after?.all ?? []
const afterHooks = options.hooks?.after?.get ?? []
await runHooks([...allAfterHooks, ...afterHooks])

const statusCode = getStatusCode(type)
return res.status(Status.HTTP_200_OK).json(result)
}

return res.status(statusCode).json(result)
}
case 'update' in options && pk && method === Method.PUT: {
const allBeforeHooks = options.hooks?.before?.all ?? []
const beforeHooks = options.hooks?.before?.update ?? []
await runHooks([...allBeforeHooks, ...beforeHooks])

export const createService = (options: ServiceOptions) => async (
req: NextApiRequest,
res: NextApiResponse,
) => {
logger(req, res, () => {})
const result = await options.update!(pk!, req.body, req.query)

try {
const pk = getPk(options.pk, req.query)
const { method } = req
const allAfterHooks = options.hooks?.after?.all ?? []
const afterHooks = options.hooks?.after?.update ?? []
await runHooks([...allAfterHooks, ...afterHooks])

switch (true) {
case options.get && pk && method === Method.GET:
return handleService('get', options, req, res)
return res.status(Status.HTTP_200_OK).json(result)
}

case 'patch' in options && pk && method === Method.PATCH: {
const allBeforeHooks = options.hooks?.before?.all ?? []
const beforeHooks = options.hooks?.before?.patch ?? []
await runHooks([...allBeforeHooks, ...beforeHooks])

const result = await options.patch!(pk!, req.body, req.query)

const allAfterHooks = options.hooks?.after?.all ?? []
const afterHooks = options.hooks?.after?.patch ?? []
await runHooks([...allAfterHooks, ...afterHooks])

return res.status(Status.HTTP_200_OK).json(result)
}

case 'remove' in options && pk && method === Method.DELETE: {
const allBeforeHooks = options.hooks?.before?.all ?? []
const beforeHooks = options.hooks?.before?.remove ?? []
await runHooks([...allBeforeHooks, ...beforeHooks])

const result = await options.remove!(pk!)

const allAfterHooks = options.hooks?.after?.all ?? []
const afterHooks = options.hooks?.after?.remove ?? []
await runHooks([...allAfterHooks, ...afterHooks])

return res.status(Status.HTTP_200_OK).json(result)
}

case 'find' in options && !pk && method === Method.GET: {
const allBeforeHooks = options.hooks?.before?.all ?? []
const beforeHooks = options.hooks?.before?.find ?? []
await runHooks([...allBeforeHooks, ...beforeHooks])

const result = await options.find!(req.query)

const allAfterHooks = options.hooks?.after?.all ?? []
const afterHooks = options.hooks?.after?.find ?? []
await runHooks([...allAfterHooks, ...afterHooks])

case options.update && pk && method === Method.PUT:
return handleService('update', options, req, res)
return res.status(Status.HTTP_200_OK).json(result)
}

case options.patch && pk && method === Method.PATCH:
return handleService('patch', options, req, res)
case 'create' in options && !pk && method === Method.POST: {
const allBeforeHooks = options.hooks?.before?.all ?? []
const beforeHooks = options.hooks?.before?.create ?? []
await runHooks([...allBeforeHooks, ...beforeHooks])

case options.remove && pk && method === Method.DELETE:
return handleService('remove', options, req, res)
const result = await options.create!(req.body)

case options.find && !pk && method === Method.GET:
return handleService('find', options, req, res)
const allAfterHooks = options.hooks?.after?.all ?? []
const afterHooks = options.hooks?.after?.create ?? []
await runHooks([...allAfterHooks, ...afterHooks])

case options.create && !pk && method === Method.POST:
return handleService('create', options, req, res)
return res.status(Status.HTTP_201_CREATED).json(result)
}

default:
throw new NotFoundError()
Expand Down
4 changes: 2 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ export type ApiNextQuery = NextApiRequest['query']
export type ApiNextBody = NextApiRequest['body']

export interface ServiceMethods {
find?(query: ApiNextQuery): Promise<any>
create?: (body: ApiNextBody) => Promise<any>
get?: (pk: string, query: ApiNextQuery) => Promise<any>
update?: (pk: string, body: ApiNextBody, query: ApiNextQuery) => Promise<any>
patch?: (pk: string, body: ApiNextBody, query: ApiNextQuery) => Promise<any>
remove?: (pk: string) => Promise<any>
find?(query: ApiNextQuery): Promise<any>
create?: (body: ApiNextBody) => Promise<any>
}

export type Hook = (
Expand Down
28 changes: 28 additions & 0 deletions test/errors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Method, Status } from 'simple-http-status'

import { createService, testService, NotAuthorised } from '../src'

describe('[errors/generic]', () => {
test('Should return error object', async () => {
const service = createService({
hooks: {
before: {
get: [
async () => {
throw new NotAuthorised()
},
],
},
},
get: async () => ({
hello: 'there',
}),
})
const { statusCode, data } = await testService(service, {
method: Method.GET,
query: { id: '1' },
})
expect(statusCode).toBe(Status.HTTP_401_UNAUTHORIZED)
expect(data).toEqual({ errors: [{ message: 'Not authorised' }] })
})
})

0 comments on commit b6e63f2

Please sign in to comment.