-
Notifications
You must be signed in to change notification settings - Fork 391
/
Copy pathsynchronous.ts
179 lines (148 loc) · 5.91 KB
/
synchronous.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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import { Buffer } from 'buffer'
import { chalk, logPadded, NETLIFYDEVERR } from '../../utils/command-helpers.js'
import { isStream } from '../../utils/is-stream.js'
import renderErrorTemplate from '../render-error-template.js'
import { detectAwsSdkError } from './utils.js'
/**
* @typedef InvocationError
* @property {string} errorType
* @property {string} errorMessage
* @property {Array<string>} stackTrace
*/
// @ts-expect-error TS(7006) FIXME: Parameter 'headers' implicitly has an 'any' type.
const addHeaders = (headers, response) => {
if (!headers) {
return
}
Object.entries(headers).forEach(([key, value]) => {
response.setHeader(key, value)
})
}
export const handleSynchronousFunction = function ({
// @ts-expect-error TS(7031) FIXME: Binding element 'invocationError' implicitly has a... Remove this comment to see the full error message
error: invocationError,
// @ts-expect-error TS(7031) FIXME: Binding element 'functionName' implicitly has an '... Remove this comment to see the full error message
functionName,
// @ts-expect-error TS(7031) FIXME: Binding element 'request' implicitly has an 'any' ... Remove this comment to see the full error message
request,
// @ts-expect-error TS(7031) FIXME: Binding element 'response' implicitly has an 'any'... Remove this comment to see the full error message
response,
// @ts-expect-error TS(7031) FIXME: Binding element 'result' implicitly has an 'any' t... Remove this comment to see the full error message
result,
}) {
if (invocationError) {
const error = getNormalizedError(invocationError)
logPadded(
`${NETLIFYDEVERR} Function ${chalk.yellow(functionName)} has returned an error: ${
error.errorMessage
}\n${chalk.dim(error.stackTrace.join('\n'))}`,
)
return handleErr(invocationError, request, response)
}
const { error } = validateLambdaResponse(result)
if (error) {
logPadded(`${NETLIFYDEVERR} ${error}`)
return handleErr(error, request, response)
}
response.statusCode = result.statusCode
try {
addHeaders(result.headers, response)
addHeaders(result.multiValueHeaders, response)
} catch (headersError) {
const normalizedError = getNormalizedError(headersError)
logPadded(
`${NETLIFYDEVERR} Failed to set header in function ${chalk.yellow(functionName)}: ${
normalizedError.errorMessage
}`,
)
return handleErr(headersError, request, response)
}
if (result.body) {
if (isStream(result.body)) {
result.body.pipe(response)
return
}
response.write(result.isBase64Encoded ? Buffer.from(result.body, 'base64') : result.body)
}
response.end()
}
/**
* Accepts an error generated by `lambda-local` or an instance of `Error` and
* returns a normalized error that we can treat in the same way.
*
* @param {InvocationError|Error} error
* @returns {InvocationError}
*/
// @ts-expect-error TS(7006) FIXME: Parameter 'error' implicitly has an 'any' type.
const getNormalizedError = (error) => {
if (error instanceof Error) {
const normalizedError = {
errorMessage: error.message,
errorType: error.name,
stackTrace: error.stack ? error.stack.split('\n') : [],
}
if ('code' in error && error.code === 'ERR_REQUIRE_ESM') {
return {
...normalizedError,
errorMessage:
'a CommonJS file cannot import ES modules. Consider switching your function to ES modules. For more information, refer to https://ntl.fyi/functions-runtime.',
}
}
return normalizedError
}
// Formatting stack trace lines in the same way that Node.js formats native
// errors.
// @ts-expect-error TS(7006) FIXME: Parameter 'line' implicitly has an 'any' type.
const stackTrace = error.stackTrace.map((line) => ` at ${line}`)
return {
errorType: error.errorType,
errorMessage: error.errorMessage,
stackTrace,
}
}
// @ts-expect-error TS(7006) FIXME: Parameter 'rawError' implicitly has an 'any' type.
const formatLambdaLocalError = (rawError, acceptsHTML) => {
const error = getNormalizedError(rawError)
if (acceptsHTML) {
return JSON.stringify({
...error,
stackTrace: undefined,
trace: error.stackTrace,
})
}
return `${error.errorType}: ${error.errorMessage}\n ${error.stackTrace.join('\n')}`
}
// @ts-expect-error TS(7006) FIXME: Parameter 'err' implicitly has an 'any' type.
const handleErr = async (err, request, response) => {
// @ts-expect-error TS(2345) FIXME: Argument of type '{ err: any; }' is not assignable... Remove this comment to see the full error message
detectAwsSdkError({ err })
const acceptsHtml = request.headers && request.headers.accept && request.headers.accept.includes('text/html')
const errorString = typeof err === 'string' ? err : formatLambdaLocalError(err, acceptsHtml)
response.statusCode = 500
if (acceptsHtml) {
response.setHeader('Content-Type', 'text/html')
response.end(await renderErrorTemplate(errorString, '../../src/lib/templates/function-error.html', 'function'))
} else {
response.end(errorString)
}
}
// @ts-expect-error TS(7006) FIXME: Parameter 'lambdaResponse' implicitly has an 'any'... Remove this comment to see the full error message
const validateLambdaResponse = (lambdaResponse) => {
if (lambdaResponse === undefined) {
return { error: 'lambda response was undefined. check your function code again' }
}
if (lambdaResponse === null) {
return {
error: 'no lambda response. check your function code again. make sure to return a promise or use the callback.',
}
}
if (!Number(lambdaResponse.statusCode)) {
return {
error: `Your function response must have a numerical statusCode. You gave: ${lambdaResponse.statusCode}`,
}
}
if (lambdaResponse.body && typeof lambdaResponse.body !== 'string' && !isStream(lambdaResponse.body)) {
return { error: `Your function response must have a string or a stream body. You gave: ${lambdaResponse.body}` }
}
return {}
}