@creator.co/wapi 1.1.7 → 1.2.0-alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.cjs +22 -22
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/package.json +48 -0
- package/dist/src/API/Request.d.ts +4 -5
- package/dist/src/API/Request.js +3 -4
- package/dist/src/API/Request.js.map +1 -1
- package/dist/src/API/Response.d.ts +1 -1
- package/dist/src/API/Response.js.map +1 -1
- package/dist/src/BaseEvent/EventProcessor.js +0 -1
- package/dist/src/BaseEvent/EventProcessor.js.map +1 -1
- package/dist/src/BaseEvent/Process.d.ts +0 -1
- package/dist/src/BaseEvent/Process.js +1 -2
- package/dist/src/BaseEvent/Process.js.map +1 -1
- package/dist/src/BaseEvent/Transaction.d.ts +6 -6
- package/dist/src/BaseEvent/Transaction.js +2 -4
- package/dist/src/BaseEvent/Transaction.js.map +1 -1
- package/dist/src/Config/EnvironmentVar.d.ts +17 -0
- package/dist/src/Config/EnvironmentVar.js +155 -0
- package/dist/src/Config/EnvironmentVar.js.map +1 -0
- package/dist/src/Globals.d.ts +11 -0
- package/dist/src/Globals.js +12 -0
- package/dist/src/Globals.js.map +1 -1
- package/dist/src/Logger/Logger.js +42 -11
- package/dist/src/Logger/Logger.js.map +1 -1
- package/dist/src/Mailer/Mailer.js +52 -26
- package/dist/src/Mailer/Mailer.js.map +1 -1
- package/dist/src/Server/Router.d.ts +30 -0
- package/dist/src/Server/Router.js +21 -0
- package/dist/src/Server/Router.js.map +1 -0
- package/dist/src/Server/lib/ContainerServer.d.ts +11 -0
- package/dist/src/Server/lib/ContainerServer.js +100 -0
- package/dist/src/Server/lib/ContainerServer.js.map +1 -0
- package/dist/src/Server/lib/Server.d.ts +9 -0
- package/dist/src/Server/lib/Server.js +137 -0
- package/dist/src/Server/lib/Server.js.map +1 -0
- package/dist/src/Server/lib/container/GenericHandler.d.ts +4 -0
- package/dist/src/Server/lib/container/GenericHandler.js +138 -0
- package/dist/src/Server/lib/container/GenericHandler.js.map +1 -0
- package/dist/src/Server/lib/container/GenericHandlerEvent.d.ts +14 -0
- package/dist/src/Server/lib/container/GenericHandlerEvent.js +164 -0
- package/dist/src/Server/lib/container/GenericHandlerEvent.js.map +1 -0
- package/dist/src/Server/lib/container/HealthHandler.d.ts +3 -0
- package/dist/src/Server/lib/container/HealthHandler.js +44 -0
- package/dist/src/Server/lib/container/HealthHandler.js.map +1 -0
- package/dist/src/Server/lib/container/Proxy.d.ts +15 -0
- package/dist/src/Server/lib/container/Proxy.js +153 -0
- package/dist/src/Server/lib/container/Proxy.js.map +1 -0
- package/dist/src/Server/lib/container/Utils.d.ts +6 -0
- package/dist/src/Server/lib/container/Utils.js +109 -0
- package/dist/src/Server/lib/container/Utils.js.map +1 -0
- package/dist/src/Validation/Validator.d.ts +5 -0
- package/dist/src/Validation/Validator.js +39 -0
- package/dist/src/Validation/Validator.js.map +1 -0
- package/index.ts +2 -0
- package/package.json +12 -3
- package/src/API/Request.ts +6 -12
- package/src/API/Response.ts +1 -1
- package/src/BaseEvent/EventProcessor.ts +3 -2
- package/src/BaseEvent/Process.ts +4 -4
- package/src/BaseEvent/Transaction.ts +24 -16
- package/src/Config/EnvironmentVar.ts +94 -0
- package/src/Globals.ts +15 -0
- package/src/Mailer/Mailer.ts +2 -0
- package/src/Server/Router.ts +51 -0
- package/src/Server/lib/ContainerServer.ts +27 -0
- package/src/Server/lib/Server.ts +66 -0
- package/src/Server/lib/container/GenericHandler.ts +63 -0
- package/src/Server/lib/container/GenericHandlerEvent.ts +133 -0
- package/src/Server/lib/container/HealthHandler.ts +5 -0
- package/src/Server/lib/container/Proxy.ts +107 -0
- package/src/Server/lib/container/Utils.ts +45 -0
- package/src/Validation/Validator.ts +35 -0
- package/tsconfig.json +2 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { APIGatewayProxyEvent, Context } from "aws-lambda"
|
|
2
|
+
import { pathToRegexp } from "path-to-regexp"
|
|
3
|
+
|
|
4
|
+
import Response from "../../API/Response"
|
|
5
|
+
import Transaction from "../../BaseEvent/Transaction"
|
|
6
|
+
import Validator from "../../Validation/Validator"
|
|
7
|
+
import { RouterConfig } from "../Router"
|
|
8
|
+
|
|
9
|
+
export default class Server {
|
|
10
|
+
protected readonly config: RouterConfig
|
|
11
|
+
constructor(config: RouterConfig) {
|
|
12
|
+
this.config = config
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public getExport(): CallableFunction {
|
|
16
|
+
return this.handleServerlessEvent
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public async handleServerlessEvent(
|
|
20
|
+
event: APIGatewayProxyEvent,
|
|
21
|
+
context: Context,
|
|
22
|
+
) {
|
|
23
|
+
// init transaction
|
|
24
|
+
await new Transaction(event, context, this.config).execute(
|
|
25
|
+
async (transaction) => {
|
|
26
|
+
for (const route of this.config.routes) {
|
|
27
|
+
// Check for possible paths
|
|
28
|
+
const handler = this.routeMatches(route.path, route.method, event)
|
|
29
|
+
if (handler) {
|
|
30
|
+
transaction.logger.log("Router accepted route:", route)
|
|
31
|
+
// Validate input?
|
|
32
|
+
if (route.inputValidation) {
|
|
33
|
+
const validationResp = Validator.validateSchema(
|
|
34
|
+
transaction.request.getBody(),
|
|
35
|
+
route.inputValidation,
|
|
36
|
+
)
|
|
37
|
+
if (validationResp && validationResp instanceof Response)
|
|
38
|
+
return validationResp
|
|
39
|
+
}
|
|
40
|
+
// Continue to route handler
|
|
41
|
+
return await route.handler(transaction)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//No route found :/
|
|
45
|
+
return new Response(404, { err: "Route not found!" })
|
|
46
|
+
},
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
// Helper function to match route
|
|
50
|
+
private routeMatches(
|
|
51
|
+
routePath: string,
|
|
52
|
+
routeMethod: string,
|
|
53
|
+
event: APIGatewayProxyEvent,
|
|
54
|
+
): boolean {
|
|
55
|
+
const path = event.path
|
|
56
|
+
const keys = []
|
|
57
|
+
const result = pathToRegexp(routePath, keys).exec(path)
|
|
58
|
+
if (result) {
|
|
59
|
+
const reqMethod = event.httpMethod
|
|
60
|
+
if (reqMethod.toLowerCase() == routeMethod.toLowerCase()) {
|
|
61
|
+
return true
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return false
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Request, Response } from "express"
|
|
2
|
+
|
|
3
|
+
import GenericHandlerEvent, {
|
|
4
|
+
GenericHandlerEventResponse,
|
|
5
|
+
} from "./GenericHandlerEvent"
|
|
6
|
+
import Globals from "../../../Globals"
|
|
7
|
+
import Server from "../Server"
|
|
8
|
+
|
|
9
|
+
export default (serverlessHandler: Server["handleServerlessEvent"]) => {
|
|
10
|
+
return async (request: Request, res: Response) => {
|
|
11
|
+
const startTime = Date.now()
|
|
12
|
+
let resp = null
|
|
13
|
+
try {
|
|
14
|
+
// Generate event with request stuff (http to serverless translation)
|
|
15
|
+
const event = new GenericHandlerEvent(request, serverlessHandler)
|
|
16
|
+
// Invoke
|
|
17
|
+
const invokationResp = await event.invoke()
|
|
18
|
+
// Respond
|
|
19
|
+
resp = processServerlessResponse(invokationResp, res)
|
|
20
|
+
} catch (e) {
|
|
21
|
+
console.error("[Proxy] - Exception during execution!", e)
|
|
22
|
+
resp = res
|
|
23
|
+
.json({
|
|
24
|
+
...e,
|
|
25
|
+
err: Globals.Resp_MSG_EXCEPTION,
|
|
26
|
+
errCode: Globals.Resp_CODE_EXCEPTION,
|
|
27
|
+
})
|
|
28
|
+
.status(Globals.Resp_STATUSCODE_EXCEPTION) //bad gateway
|
|
29
|
+
}
|
|
30
|
+
console.debug(`[Proxy] - Request took ${Date.now() - startTime}ms`)
|
|
31
|
+
return resp
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const processServerlessResponse = (
|
|
35
|
+
invokation: GenericHandlerEventResponse,
|
|
36
|
+
res: Response,
|
|
37
|
+
) => {
|
|
38
|
+
// translate answer to http layer
|
|
39
|
+
if (invokation && invokation.err) {
|
|
40
|
+
// err came
|
|
41
|
+
return res.json({ err: invokation.err }).status(400)
|
|
42
|
+
} else if (!invokation || !invokation.data) {
|
|
43
|
+
// invalid response came
|
|
44
|
+
return res
|
|
45
|
+
.json({
|
|
46
|
+
err: Globals.Resp_MSG_INVALIDRESP,
|
|
47
|
+
errCode: Globals.Resp_CODE_INVALIDRESP,
|
|
48
|
+
})
|
|
49
|
+
.status(Globals.Resp_STATUSCODE_INVALIDRESP)
|
|
50
|
+
} else {
|
|
51
|
+
// valid
|
|
52
|
+
const response = res
|
|
53
|
+
.json(invokation.data.body || {})
|
|
54
|
+
.status(invokation.data.statusCode)
|
|
55
|
+
// Check for headers
|
|
56
|
+
if (invokation?.data?.headers) {
|
|
57
|
+
for (const hKey of Object.keys(invokation.data.headers)) {
|
|
58
|
+
response.header(hKey, invokation.data.headers[hKey])
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return response
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { APIGatewayProxyEvent, Context } from "aws-lambda"
|
|
2
|
+
import * as cuid from "cuid"
|
|
3
|
+
import { Request } from "express"
|
|
4
|
+
import unflatten from "unflatten"
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
parseMultiValueHeaders,
|
|
8
|
+
parseMultiValueQueryStringParameters,
|
|
9
|
+
parseQueryStringParameters,
|
|
10
|
+
nullIfEmpty,
|
|
11
|
+
} from "./Utils"
|
|
12
|
+
import Globals from "../../../Globals"
|
|
13
|
+
import Server from "../Server"
|
|
14
|
+
//
|
|
15
|
+
export type GenericHandlerEventResponse = { err?: Error | string; data?: any }
|
|
16
|
+
//
|
|
17
|
+
export default class GenericHandlerEvent {
|
|
18
|
+
public request: Request
|
|
19
|
+
public serverlessHandler: Server["handleServerlessEvent"]
|
|
20
|
+
constructor(
|
|
21
|
+
request: Request,
|
|
22
|
+
serverlessHandler: Server["handleServerlessEvent"],
|
|
23
|
+
) {
|
|
24
|
+
this.request = request
|
|
25
|
+
this.serverlessHandler = serverlessHandler
|
|
26
|
+
}
|
|
27
|
+
public async invoke(): Promise<GenericHandlerEventResponse> {
|
|
28
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
29
|
+
return new Promise(async (resolve, reject) => {
|
|
30
|
+
try {
|
|
31
|
+
// Build event and context
|
|
32
|
+
const event = this.buildEvent()
|
|
33
|
+
const context = this.buildContext(
|
|
34
|
+
event,
|
|
35
|
+
(err?: Error | string, data?: any) => {
|
|
36
|
+
resolve({ err, data })
|
|
37
|
+
},
|
|
38
|
+
)
|
|
39
|
+
// Invoke
|
|
40
|
+
await this.serverlessHandler(event, context)
|
|
41
|
+
} catch (e) {
|
|
42
|
+
reject(e) // forward rejection
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
/* private */
|
|
47
|
+
private buildEvent(): APIGatewayProxyEvent {
|
|
48
|
+
return {
|
|
49
|
+
body: this.request.body || null, //enforce key
|
|
50
|
+
headers: this.request.headers ? unflatten(this.request.headers, 2) : [],
|
|
51
|
+
httpMethod: this.request.method
|
|
52
|
+
? this.request.method.toUpperCase()
|
|
53
|
+
: null,
|
|
54
|
+
isBase64Encoded: false,
|
|
55
|
+
multiValueHeaders: parseMultiValueHeaders(
|
|
56
|
+
this.request.headers ? this.request.headers || [] : [],
|
|
57
|
+
),
|
|
58
|
+
multiValueQueryStringParameters: parseMultiValueQueryStringParameters(
|
|
59
|
+
this.request.url,
|
|
60
|
+
),
|
|
61
|
+
path: this.request.path,
|
|
62
|
+
pathParameters: this.request.params
|
|
63
|
+
? nullIfEmpty(this.request.params)
|
|
64
|
+
: null,
|
|
65
|
+
queryStringParameters: this.request.query
|
|
66
|
+
? parseQueryStringParameters(this.request.query)
|
|
67
|
+
: null,
|
|
68
|
+
requestContext: {
|
|
69
|
+
accountId: process.env.AWS_ACCOUNT_ID || null,
|
|
70
|
+
apiId: null,
|
|
71
|
+
authorizer: null,
|
|
72
|
+
domainName: null,
|
|
73
|
+
domainPrefix: null,
|
|
74
|
+
extendedRequestId: cuid(),
|
|
75
|
+
httpMethod: this.request.method
|
|
76
|
+
? this.request.method.toUpperCase()
|
|
77
|
+
: null,
|
|
78
|
+
identity: {
|
|
79
|
+
accessKey: null,
|
|
80
|
+
accountId: process.env.AWS_ACCOUNT_ID || null,
|
|
81
|
+
caller: null,
|
|
82
|
+
apiKey: null,
|
|
83
|
+
apiKeyId: null,
|
|
84
|
+
clientCert: null,
|
|
85
|
+
cognitoAuthenticationProvider: null,
|
|
86
|
+
cognitoAuthenticationType: null,
|
|
87
|
+
cognitoIdentityId: null,
|
|
88
|
+
cognitoIdentityPoolId: null,
|
|
89
|
+
principalOrgId: null,
|
|
90
|
+
sourceIp:
|
|
91
|
+
<string>this.request.headers?.["x-forwarded-for"] ||
|
|
92
|
+
this.request.socket.remoteAddress,
|
|
93
|
+
user: null,
|
|
94
|
+
userAgent: this.request.headers
|
|
95
|
+
? this.request.headers["user-agent"]
|
|
96
|
+
: null,
|
|
97
|
+
userArn: null,
|
|
98
|
+
},
|
|
99
|
+
path: this.request.path,
|
|
100
|
+
protocol: "HTTP/1.1",
|
|
101
|
+
requestId: `${cuid()}-${cuid()}`,
|
|
102
|
+
requestTime: new Date().toISOString(),
|
|
103
|
+
requestTimeEpoch: Date.now(),
|
|
104
|
+
resourceId: null,
|
|
105
|
+
resourcePath: Globals.Listener_HTTP_ProxyRoute,
|
|
106
|
+
stage: process.env.STAGE,
|
|
107
|
+
},
|
|
108
|
+
resource: Globals.Listener_HTTP_ProxyRoute,
|
|
109
|
+
stageVariables: null,
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
private buildContext(
|
|
113
|
+
event: APIGatewayProxyEvent,
|
|
114
|
+
callback: (err?: Error | string, data?: any) => void,
|
|
115
|
+
): Context {
|
|
116
|
+
return {
|
|
117
|
+
awsRequestId: event.requestContext.requestId,
|
|
118
|
+
callbackWaitsForEmptyEventLoop: true,
|
|
119
|
+
getRemainingTimeInMillis: () => {
|
|
120
|
+
return 0
|
|
121
|
+
},
|
|
122
|
+
done: (err, data) => callback(err, data),
|
|
123
|
+
fail: (err) => callback(err),
|
|
124
|
+
succeed: (res) => callback(null, res),
|
|
125
|
+
functionName: null,
|
|
126
|
+
functionVersion: "LATEST",
|
|
127
|
+
invokedFunctionArn: null,
|
|
128
|
+
memoryLimitInMB: "-1",
|
|
129
|
+
logGroupName: null,
|
|
130
|
+
logStreamName: null,
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Server as HTTPServer, createServer } from "http"
|
|
2
|
+
|
|
3
|
+
import cors from "cors"
|
|
4
|
+
import * as express from "express"
|
|
5
|
+
|
|
6
|
+
import Server from "./../Server"
|
|
7
|
+
import GenericHandler from "./GenericHandler"
|
|
8
|
+
import HealthHandler from "./HealthHandler"
|
|
9
|
+
import { version as appVersion } from "../../../../package.json"
|
|
10
|
+
import Globals from "../../../Globals"
|
|
11
|
+
import { RouterConfig } from "../../Router"
|
|
12
|
+
|
|
13
|
+
export default class Proxy {
|
|
14
|
+
private stopping: boolean
|
|
15
|
+
private readonly config: RouterConfig
|
|
16
|
+
private readonly app: express.Express
|
|
17
|
+
private readonly serverlessHandler: Server["handleServerlessEvent"]
|
|
18
|
+
private listener: HTTPServer
|
|
19
|
+
constructor(
|
|
20
|
+
config: RouterConfig,
|
|
21
|
+
serverlessHandler: Server["handleServerlessEvent"],
|
|
22
|
+
) {
|
|
23
|
+
this.stopping = false
|
|
24
|
+
this.config = config
|
|
25
|
+
this.serverlessHandler = serverlessHandler
|
|
26
|
+
this.app = express()
|
|
27
|
+
this.app.use(
|
|
28
|
+
cors(
|
|
29
|
+
this.config.cors
|
|
30
|
+
? {
|
|
31
|
+
options: {
|
|
32
|
+
origin: this.config?.cors?.origin,
|
|
33
|
+
allowedHeaders: this.config?.cors?.headers,
|
|
34
|
+
credentials: !!this.config?.cors?.allowCredentials,
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
: {},
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
// //This supposedly fix some 502 codes where nodejs socket would hang during
|
|
42
|
+
// //a request and if behind ALB, it would cause 502 codes. Had experiencied this
|
|
43
|
+
// //and 502 codes reduced dramastically, but still some appearances. Maybe this
|
|
44
|
+
// //is just a palliative work-around for the real issue; TODO: need to investigate
|
|
45
|
+
// //in the future.
|
|
46
|
+
// this.listener.listener.keepAliveTimeout = 120e3
|
|
47
|
+
// this.listener.listener.headersTimeout = 120e3
|
|
48
|
+
}
|
|
49
|
+
async load() {
|
|
50
|
+
await this.startListeners()
|
|
51
|
+
this.installRoutes()
|
|
52
|
+
}
|
|
53
|
+
async unload(err) {
|
|
54
|
+
await this.stopListeners(err)
|
|
55
|
+
}
|
|
56
|
+
/* lifecycle */
|
|
57
|
+
private async startListeners(): Promise<void> {
|
|
58
|
+
return new Promise((resolve) => {
|
|
59
|
+
const port = this.config.port || Globals.Listener_HTTP_DefaultPort
|
|
60
|
+
console.log(`[Proxy] - [STARTING] - v.${appVersion} - :${port}`)
|
|
61
|
+
// Create Server
|
|
62
|
+
this.listener = createServer(this.app)
|
|
63
|
+
// Set defaults
|
|
64
|
+
this.listener.setTimeout(
|
|
65
|
+
this.config.timeout || Globals.Listener_HTTP_DefaultTimeout,
|
|
66
|
+
)
|
|
67
|
+
// Start Server
|
|
68
|
+
this.listener.listen(port, () => {
|
|
69
|
+
console.log(`[Proxy] - [STARTED]`)
|
|
70
|
+
resolve()
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
private async stopListeners(err) {
|
|
75
|
+
if (this.stopping) return
|
|
76
|
+
this.stopping = true
|
|
77
|
+
console.debug("[Proxy] - [STOPPING]")
|
|
78
|
+
this.listener.close((_err) => {
|
|
79
|
+
if (err || _err) console.log("[Proxy] - exit output:", err || _err)
|
|
80
|
+
console.log("[Proxy] - [STOPPED]")
|
|
81
|
+
process.exit(err || _err ? 1 : 0)
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
/* routing */
|
|
85
|
+
private installRoutes() {
|
|
86
|
+
//Health check route -- This is a bypass route to only check if
|
|
87
|
+
//runtime proxy is working and responding to calls.
|
|
88
|
+
console.log(
|
|
89
|
+
`[Proxy] - [HEALTH-ROUTE] - ${
|
|
90
|
+
this.config.healthCheckRoute ||
|
|
91
|
+
Globals.Listener_HTTP_DefaultHealthCheckRoute
|
|
92
|
+
}`,
|
|
93
|
+
)
|
|
94
|
+
this.app
|
|
95
|
+
.route(
|
|
96
|
+
this.config.healthCheckRoute ||
|
|
97
|
+
Globals.Listener_HTTP_DefaultHealthCheckRoute,
|
|
98
|
+
)
|
|
99
|
+
.get(HealthHandler)
|
|
100
|
+
//Main route -- We use a wildcard route because is not the job of the runtime and neither
|
|
101
|
+
//the task to deny/constrain routes that invoked this task; all the job is done by the
|
|
102
|
+
//load balancer and we just foward everything we have to the function.
|
|
103
|
+
this.app
|
|
104
|
+
.route(Globals.Listener_HTTP_ProxyRoute)
|
|
105
|
+
.all(GenericHandler(this.serverlessHandler))
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import unflatten from "unflatten"
|
|
2
|
+
// https://aws.amazon.com/blogs/compute/support-for-multi-value-parameters-in-amazon-api-gateway/
|
|
3
|
+
// (rawHeaders: Array<string>): { [string]: Array<string> }
|
|
4
|
+
export function parseMultiValueHeaders(rawHeaders) {
|
|
5
|
+
if (rawHeaders.length === 0) return null
|
|
6
|
+
//
|
|
7
|
+
const map = new Map()
|
|
8
|
+
const unflattened = unflatten(rawHeaders, 2)
|
|
9
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
10
|
+
for (const key of Object.keys(unflattened)) {
|
|
11
|
+
const item = map.get(key)
|
|
12
|
+
if (item) item.push(unflattened[key])
|
|
13
|
+
else map.set(key, [unflattened[key]])
|
|
14
|
+
}
|
|
15
|
+
return Object.fromEntries(map)
|
|
16
|
+
}
|
|
17
|
+
// https://aws.amazon.com/blogs/compute/support-for-multi-value-parameters-in-amazon-api-gateway/
|
|
18
|
+
// [ [ 'petType', 'dog' ], [ 'petType', 'fish' ] ]
|
|
19
|
+
// => { petType: [ 'dog', 'fish' ] },
|
|
20
|
+
export function parseMultiValueQueryStringParameters(url) {
|
|
21
|
+
// dummy placeholder url for the WHATWG URL constructor
|
|
22
|
+
// https://github.com/nodejs/node/issues/12682
|
|
23
|
+
const { searchParams } = new URL(url, "http://example")
|
|
24
|
+
//
|
|
25
|
+
if (Array.from(searchParams).length === 0) return null
|
|
26
|
+
const map = new Map()
|
|
27
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
28
|
+
for (const [key, value] of searchParams) {
|
|
29
|
+
const item = map.get(key)
|
|
30
|
+
if (item) item.push(value)
|
|
31
|
+
else map.set(key, [value])
|
|
32
|
+
}
|
|
33
|
+
return Object.fromEntries(map)
|
|
34
|
+
}
|
|
35
|
+
export function parseQueryStringParameters(url) {
|
|
36
|
+
// dummy placeholder url for the WHATWG URL constructor
|
|
37
|
+
// https://github.com/nodejs/node/issues/12682
|
|
38
|
+
const { searchParams } = new URL(url, "http://example")
|
|
39
|
+
if (Array.from(searchParams).length === 0) return null
|
|
40
|
+
return Object.fromEntries(searchParams)
|
|
41
|
+
}
|
|
42
|
+
//
|
|
43
|
+
export function nullIfEmpty(obj) {
|
|
44
|
+
return obj && (Object.keys(obj).length > 0 ? obj : null)
|
|
45
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
|
|
3
|
+
import Response from "../API/Response"
|
|
4
|
+
import Globals from "../Globals"
|
|
5
|
+
|
|
6
|
+
export default class Validator {
|
|
7
|
+
public static validateSchema(
|
|
8
|
+
data: any,
|
|
9
|
+
schema: z.ZodObject<any>,
|
|
10
|
+
): boolean | Response {
|
|
11
|
+
let error, validatedInput
|
|
12
|
+
// Validate body against known zod schema
|
|
13
|
+
try {
|
|
14
|
+
validatedInput = schema.parse(data) as typeof schema
|
|
15
|
+
} catch (err: z.ZodError | any) {
|
|
16
|
+
if (err instanceof z.ZodError) {
|
|
17
|
+
error = err.issues
|
|
18
|
+
.map((v) => {
|
|
19
|
+
return `${v.code} - ${v.message} ${v.path}`
|
|
20
|
+
})
|
|
21
|
+
.join(", ")
|
|
22
|
+
} else if (err.message) error = err.message
|
|
23
|
+
else error = "Unknown validation error!"
|
|
24
|
+
}
|
|
25
|
+
// Error validation
|
|
26
|
+
if (!validatedInput || error) {
|
|
27
|
+
return Response.BadRequestResponse(
|
|
28
|
+
Globals.ErrorResponseValidationFail,
|
|
29
|
+
Globals.ErrorCode_InvalidInput,
|
|
30
|
+
)
|
|
31
|
+
} else {
|
|
32
|
+
return true
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|