-
-
Notifications
You must be signed in to change notification settings - Fork 754
/
Copy pathrest.ts
113 lines (87 loc) · 3.64 KB
/
rest.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import { Request, Response, RequestHandler, Router } from 'express'
import { MethodNotAllowed } from '@feathersjs/errors'
import { createDebug } from '@feathersjs/commons'
import { http } from '@feathersjs/transport-commons'
import { createContext, defaultServiceMethods, getServiceOptions } from '@feathersjs/feathers'
import { AuthenticationSettings, parseAuthentication } from './authentication'
import { Application } from './declarations'
const debug = createDebug('@feathersjs/express/rest')
const toHandler = (
func: (req: Request, res: Response, next: () => void) => Promise<void>
): RequestHandler => {
return (req, res, next) => func(req, res, next).catch((error) => next(error))
}
const serviceMiddleware = (): RequestHandler => {
return toHandler(async (req, res, next) => {
const { query, headers, path, body: data, method: httpMethod } = req
const methodOverride = req.headers[http.METHOD_HEADER] as string | undefined
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const { service, params: { __id: id = null, ...route } = {} } = req.lookup!
const method = http.getServiceMethod(httpMethod, id, methodOverride)
const { methods } = getServiceOptions(service)
debug(`Found service for path ${path}, attempting to run '${method}' service method`)
if (!methods.includes(method) || defaultServiceMethods.includes(methodOverride)) {
const error = new MethodNotAllowed(`Method \`${method}\` is not supported by this endpoint.`)
res.statusCode = error.code
throw error
}
const createArguments = http.argumentsFor[method as 'get'] || http.argumentsFor.default
const params = { query, headers, route, ...req.feathers }
const args = createArguments({ id, data, params })
const contextBase = createContext(service, method, { http: {} })
res.hook = contextBase
const context = await (service as any)[method](...args, contextBase)
res.hook = context
const response = http.getResponse(context)
res.statusCode = response.status
res.set(response.headers)
res.data = response.body
return next()
})
}
const servicesMiddleware = (): RequestHandler => {
return toHandler(async (req, res, next) => {
const app = req.app as any as Application
const lookup = app.lookup(req.path)
if (!lookup) {
return next()
}
req.lookup = lookup
const options = getServiceOptions(lookup.service)
const middleware = options.express.composed
return middleware(req, res, next)
})
}
export const formatter: RequestHandler = (_req, res, next) => {
if (res.data === undefined) {
return next()
}
res.format({
'application/json'() {
res.json(res.data)
}
})
}
export type RestOptions = {
formatter?: RequestHandler
authentication?: AuthenticationSettings
}
export const rest = (options?: RestOptions | RequestHandler) => {
options = typeof options === 'function' ? { formatter: options } : options || {}
const formatterMiddleware = options.formatter || formatter
const authenticationOptions = options.authentication
return (app: Application) => {
if (typeof app.route !== 'function') {
throw new Error('@feathersjs/express/rest needs an Express compatible app.')
}
app.use(parseAuthentication(authenticationOptions))
app.use(servicesMiddleware())
app.mixins.push((_service, _path, options) => {
const { express: { before = [], after = [] } = {} } = options
const middlewares = [].concat(before, serviceMiddleware(), after, formatterMiddleware)
const middleware = Router().use(middlewares)
options.express ||= {}
options.express.composed = middleware
})
}
}