@bitblit/ratchet-epsilon-common 6.0.146-alpha → 6.0.148-alpha
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/lib/config/cron/abstract-cron-entry.d.ts +1 -1
- package/package.json +11 -10
- package/src/background/background-dynamo-log-table-handler.ts +44 -0
- package/src/background/background-entry.ts +4 -0
- package/src/background/background-execution-event-type.ts +9 -0
- package/src/background/background-execution-event.ts +9 -0
- package/src/background/background-execution-listener.ts +6 -0
- package/src/background/background-handler.ts +352 -0
- package/src/background/background-http-adapter-handler.ts +166 -0
- package/src/background/background-meta-response-internal.ts +5 -0
- package/src/background/background-process-handling.ts +6 -0
- package/src/background/background-process-log-table-entry.ts +11 -0
- package/src/background/background-queue-response-internal.ts +9 -0
- package/src/background/background-validator.ts +105 -0
- package/src/background/epsilon-background-process-error.ts +110 -0
- package/src/background/internal-background-entry.ts +10 -0
- package/src/background/manager/abstract-background-manager.ts +120 -0
- package/src/background/manager/aws-large-payload-s3-sqs-sns-background-manager.ts +87 -0
- package/src/background/manager/aws-sqs-sns-background-manager.ts +201 -0
- package/src/background/manager/background-manager-like.ts +44 -0
- package/src/background/manager/background-manager.spec.ts +89 -0
- package/src/background/manager/single-thread-local-background-manager.ts +58 -0
- package/src/background/s3-background-transaction-logger.ts +65 -0
- package/src/build/ratchet-epsilon-common-info.ts +19 -0
- package/src/built-in/background/echo-processor.ts +17 -0
- package/src/built-in/background/log-and-enqueue-echo-processor.ts +14 -0
- package/src/built-in/background/log-message-background-error-processor.ts +10 -0
- package/src/built-in/background/no-op-processor.ts +12 -0
- package/src/built-in/background/retry-processor.ts +51 -0
- package/src/built-in/background/sample-delay-processor.ts +15 -0
- package/src/built-in/background/sample-input-validated-processor-data.ts +4 -0
- package/src/built-in/background/sample-input-validated-processor.ts +14 -0
- package/src/built-in/built-in-trace-id-generators.ts +22 -0
- package/src/built-in/daemon/daemon-authorizer-function.ts +4 -0
- package/src/built-in/daemon/daemon-config.ts +9 -0
- package/src/built-in/daemon/daemon-group-selection-function.ts +3 -0
- package/src/built-in/daemon/daemon-handler.ts +87 -0
- package/src/built-in/daemon/daemon-process-state-list.ts +9 -0
- package/src/built-in/http/apollo/apollo-util.ts +43 -0
- package/src/built-in/http/apollo/default-epsilon-apollo-context.ts +11 -0
- package/src/built-in/http/apollo/epsilon-apollo-context-builder-options.ts +5 -0
- package/src/built-in/http/apollo/epsilon-lambda-apollo-context-function-argument.ts +6 -0
- package/src/built-in/http/apollo/epsilon-lambda-apollo-options.ts +11 -0
- package/src/built-in/http/apollo-filter.ts +151 -0
- package/src/built-in/http/built-in-auth-filters.ts +73 -0
- package/src/built-in/http/built-in-authorizers.ts +22 -0
- package/src/built-in/http/built-in-filters.spec.ts +26 -0
- package/src/built-in/http/built-in-filters.ts +300 -0
- package/src/built-in/http/built-in-handlers.ts +85 -0
- package/src/built-in/http/log-level-manipulation-filter.ts +26 -0
- package/src/built-in/http/run-handler-as-filter.spec.ts +67 -0
- package/src/built-in/http/run-handler-as-filter.ts +102 -0
- package/src/cli/ratchet-cli-handler.ts +23 -0
- package/src/cli/run-background-process-from-command-line.ts +32 -0
- package/src/config/background/background-aws-config.ts +8 -0
- package/src/config/background/background-config.ts +15 -0
- package/src/config/background/background-error-processor.ts +5 -0
- package/src/config/background/background-processor.ts +14 -0
- package/src/config/background/background-transaction-log.ts +9 -0
- package/src/config/background/background-transaction-logger.ts +6 -0
- package/src/config/cron/abstract-cron-entry.ts +17 -0
- package/src/config/cron/cron-background-entry.ts +17 -0
- package/src/config/cron/cron-config.ts +10 -0
- package/src/config/dynamo-db-config.ts +6 -0
- package/src/config/epsilon-config.ts +30 -0
- package/src/config/epsilon-lambda-event-handler.ts +12 -0
- package/src/config/epsilon-logger-config.ts +23 -0
- package/src/config/espilon-server-mode.ts +10 -0
- package/src/config/generic-aws-event-handler-function.ts +1 -0
- package/src/config/http/authorizer-function.ts +9 -0
- package/src/config/http/epsilon-authorization-context.ts +5 -0
- package/src/config/http/epsilon-cors-approach.ts +7 -0
- package/src/config/http/extended-api-gateway-event.ts +8 -0
- package/src/config/http/filter-chain-context.ts +15 -0
- package/src/config/http/filter-function.ts +3 -0
- package/src/config/http/handler-function.ts +4 -0
- package/src/config/http/http-config.ts +27 -0
- package/src/config/http/http-processing-config.ts +23 -0
- package/src/config/http/mapped-http-processing-config.ts +12 -0
- package/src/config/http/null-returned-object-handling.ts +7 -0
- package/src/config/inter-api/inter-api-aws-config.ts +5 -0
- package/src/config/inter-api/inter-api-config.ts +7 -0
- package/src/config/inter-api/inter-api-process-mapping.ts +11 -0
- package/src/config/local-server/local-server-event-logging-style.ts +8 -0
- package/src/config/local-server/local-server-http-method-handling.ts +7 -0
- package/src/config/local-server/local-server-options.ts +12 -0
- package/src/config/logging-trace-id-generator.ts +3 -0
- package/src/config/no-handlers-found-error.ts +6 -0
- package/src/config/open-api/open-api-document-components.ts +4 -0
- package/src/config/open-api/open-api-document.ts +7 -0
- package/src/config/s3-config.ts +8 -0
- package/src/config/sns-config.ts +7 -0
- package/src/config/sqs-config.ts +7 -0
- package/src/epsilon-build-properties.ts +21 -0
- package/src/epsilon-constants.ts +62 -0
- package/src/epsilon-global-handler.ts +238 -0
- package/src/epsilon-instance.ts +20 -0
- package/src/epsilon-logging-extension-processor.ts +19 -0
- package/src/http/auth/api-gateway-adapter-authentication-handler.ts +95 -0
- package/src/http/auth/auth0-web-token-manipulator.ts +69 -0
- package/src/http/auth/basic-auth-token.ts +7 -0
- package/src/http/auth/google-web-token-manipulator.spec.ts +15 -0
- package/src/http/auth/google-web-token-manipulator.ts +80 -0
- package/src/http/auth/jwt-ratchet-local-web-token-manipulator.ts +37 -0
- package/src/http/auth/local-web-token-manipulator.spec.ts +34 -0
- package/src/http/auth/local-web-token-manipulator.ts +114 -0
- package/src/http/auth/web-token-manipulator.ts +9 -0
- package/src/http/error/bad-gateway.ts +11 -0
- package/src/http/error/bad-request-error.ts +11 -0
- package/src/http/error/conflict-error.ts +12 -0
- package/src/http/error/forbidden-error.ts +12 -0
- package/src/http/error/gateway-timeout.ts +12 -0
- package/src/http/error/method-not-allowed-error.ts +12 -0
- package/src/http/error/misconfigured-error.ts +12 -0
- package/src/http/error/not-found-error.ts +12 -0
- package/src/http/error/not-implemented.ts +12 -0
- package/src/http/error/request-timeout-error.ts +12 -0
- package/src/http/error/service-unavailable.ts +12 -0
- package/src/http/error/too-many-requests-error.ts +12 -0
- package/src/http/error/unauthorized-error.ts +12 -0
- package/src/http/event-util.spec.ts +190 -0
- package/src/http/event-util.ts +272 -0
- package/src/http/response-util.spec.ts +117 -0
- package/src/http/response-util.ts +164 -0
- package/src/http/route/epsilon-router.ts +9 -0
- package/src/http/route/extended-auth-response-context.ts +7 -0
- package/src/http/route/route-and-parse.ts +8 -0
- package/src/http/route/route-mapping.ts +21 -0
- package/src/http/route/route-validator-config.ts +5 -0
- package/src/http/route/router-util.spec.ts +33 -0
- package/src/http/route/router-util.ts +314 -0
- package/src/http/web-handler.spec.ts +99 -0
- package/src/http/web-handler.ts +157 -0
- package/src/http/web-v2-handler.ts +34 -0
- package/src/inter-api/inter-api-entry.ts +8 -0
- package/src/inter-api/inter-api-util.spec.ts +77 -0
- package/src/inter-api/inter-api-util.ts +71 -0
- package/src/inter-api-manager.ts +75 -0
- package/src/lambda-event-handler/cron-epsilon-lambda-event-handler.spec.ts +130 -0
- package/src/lambda-event-handler/cron-epsilon-lambda-event-handler.ts +132 -0
- package/src/lambda-event-handler/dynamo-epsilon-lambda-event-handler.ts +42 -0
- package/src/lambda-event-handler/generic-sns-epsilon-lambda-event-handler.ts +38 -0
- package/src/lambda-event-handler/generic-sqs-epsilon-lambda-event-handler.ts +43 -0
- package/src/lambda-event-handler/inter-api-epsilon-lambda-event-handler.ts +33 -0
- package/src/lambda-event-handler/s3-epsilon-lambda-event-handler.ts +50 -0
- package/src/local-container-server.ts +128 -0
- package/src/local-server.spec.ts +16 -0
- package/src/local-server.ts +426 -0
- package/src/open-api-util/open-api-doc-modifications.ts +9 -0
- package/src/open-api-util/open-api-doc-modifier.spec.ts +22 -0
- package/src/open-api-util/open-api-doc-modifier.ts +90 -0
- package/src/open-api-util/yaml-combiner.spec.ts +26 -0
- package/src/open-api-util/yaml-combiner.ts +35 -0
- package/src/sample/sample-server-components-with-apollo.ts +87 -0
- package/src/sample/sample-server-components.ts +183 -0
- package/src/sample/sample-server-static-files.ts +614 -0
- package/src/sample/test-error-server.ts +140 -0
- package/src/util/aws-util.ts +89 -0
- package/src/util/context-global-data.ts +13 -0
- package/src/util/context-util.ts +156 -0
- package/src/util/cron-util.spec.ts +190 -0
- package/src/util/cron-util.ts +86 -0
- package/src/util/epsilon-config-parser.ts +90 -0
- package/src/util/epsilon-server-util.spec.ts +18 -0
- package/src/util/epsilon-server-util.ts +16 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { APIGatewayEvent, APIGatewayEventRequestContext, AuthResponseContext } from 'aws-lambda';
|
|
2
|
+
import { UnauthorizedError } from './error/unauthorized-error.js';
|
|
3
|
+
import { BadRequestError } from './error/bad-request-error.js';
|
|
4
|
+
import { EpsilonLoggerConfig } from '../config/epsilon-logger-config.js';
|
|
5
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
6
|
+
|
|
7
|
+
import jwt from 'jsonwebtoken';
|
|
8
|
+
import { ExtendedAuthResponseContext } from './route/extended-auth-response-context.js';
|
|
9
|
+
import { BasicAuthToken } from './auth/basic-auth-token.js';
|
|
10
|
+
import { EpsilonConstants } from '../epsilon-constants.js';
|
|
11
|
+
import { StringRatchet } from '@bitblit/ratchet-common/lang/string-ratchet';
|
|
12
|
+
import { LoggerLevelName } from '@bitblit/ratchet-common/logger/logger-level-name';
|
|
13
|
+
import { Base64Ratchet } from '@bitblit/ratchet-common/lang/base64-ratchet';
|
|
14
|
+
import { MapRatchet } from '@bitblit/ratchet-common/lang/map-ratchet';
|
|
15
|
+
import { EnumRatchet } from '@bitblit/ratchet-common/lang/enum-ratchet';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Endpoints about the api itself
|
|
19
|
+
*/
|
|
20
|
+
export class EventUtil {
|
|
21
|
+
public static readonly LOCAL_REGEX: RegExp[] = [new RegExp('^127\\.0\\.0\\.1(:\\d+)?$'), new RegExp('^localhost(:\\d+)?$')];
|
|
22
|
+
public static readonly NON_ROUTABLE_REGEX: RegExp[] = EventUtil.LOCAL_REGEX.concat([
|
|
23
|
+
new RegExp('^192\\.168\\.\\d+\\.\\d+(:\\d+)?$'),
|
|
24
|
+
new RegExp('^10\\.\\d+\\.\\d+\\.\\d+(:\\d+)?$'),
|
|
25
|
+
new RegExp('^172\\.16\\.\\d+\\.\\d+(:\\d+)?$'),
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
// Prevent instantiation
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
30
|
+
private constructor() {}
|
|
31
|
+
|
|
32
|
+
public static extractStage(event: APIGatewayEvent): string {
|
|
33
|
+
// This differs from extractApiGatewayStage in that the "real" stage can be
|
|
34
|
+
// mapped differently than the gateway stage. This extracts the "real" stage
|
|
35
|
+
// as just being the first part of the path. If they are the same, no harm no
|
|
36
|
+
// foul
|
|
37
|
+
if (!event.path.startsWith('/')) {
|
|
38
|
+
throw new BadRequestError('Path should start with / but does not : ' + event.path);
|
|
39
|
+
}
|
|
40
|
+
const idx = event.path.indexOf('/', 1);
|
|
41
|
+
if (idx == -1) {
|
|
42
|
+
throw new BadRequestError('No second / found in the path : ' + event.path);
|
|
43
|
+
}
|
|
44
|
+
return event.path.substring(1, idx);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public static extractHostHeader(event: APIGatewayEvent): string {
|
|
48
|
+
return MapRatchet.extractValueFromMapIgnoreCase(event.headers, 'Host');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public static extractProtocol(event: APIGatewayEvent): string {
|
|
52
|
+
// Since API gateway / ALB ALWAYS sets this
|
|
53
|
+
return MapRatchet.extractValueFromMapIgnoreCase(event.headers, 'X-Forwarded-Proto');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public static extractApiGatewayStage(event: APIGatewayEvent): string {
|
|
57
|
+
const rc: APIGatewayEventRequestContext = EventUtil.extractRequestContext(event);
|
|
58
|
+
return rc ? rc.stage : null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public static extractRequestContext(event: APIGatewayEvent): APIGatewayEventRequestContext {
|
|
62
|
+
return event.requestContext;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public static extractAuthorizer(event: APIGatewayEvent): AuthResponseContext {
|
|
66
|
+
const rc: APIGatewayEventRequestContext = EventUtil.extractRequestContext(event);
|
|
67
|
+
return rc ? rc.authorizer : null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public static ipAddressChain(event: APIGatewayEvent): string[] {
|
|
71
|
+
const headerVal: string = event && event.headers ? MapRatchet.extractValueFromMapIgnoreCase(event.headers, 'X-Forwarded-For') : null;
|
|
72
|
+
let headerList: string[] = headerVal ? String(headerVal).split(',') : [];
|
|
73
|
+
headerList = headerList.map((s) => s.trim());
|
|
74
|
+
return headerList;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public static ipAddress(event: APIGatewayEvent): string {
|
|
78
|
+
const list: string[] = EventUtil.ipAddressChain(event);
|
|
79
|
+
return list && list.length > 0 ? list[0] : null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public static extractFullPath(event: APIGatewayEvent, overrideProtocol: string = null): string {
|
|
83
|
+
const protocol: string = overrideProtocol || EventUtil.extractProtocol(event) || 'https';
|
|
84
|
+
return protocol + '://' + event.requestContext['domainName'] + event.requestContext.path;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public static extractFullPrefix(event: APIGatewayEvent, overrideProtocol: string = null): string {
|
|
88
|
+
const protocol: string = overrideProtocol || EventUtil.extractProtocol(event) || 'https';
|
|
89
|
+
const prefix: string = event.requestContext.path.substring(0, event.requestContext.path.indexOf('/', 1));
|
|
90
|
+
return protocol + '://' + event.requestContext['domainName'] + prefix;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public static jsonBodyToObject(evt: APIGatewayEvent): any {
|
|
94
|
+
let rval: any = null;
|
|
95
|
+
if (evt.body) {
|
|
96
|
+
const contentType = MapRatchet.extractValueFromMapIgnoreCase(evt.headers, 'Content-Type') || 'application/octet-stream';
|
|
97
|
+
rval = evt.body;
|
|
98
|
+
|
|
99
|
+
if (evt.isBase64Encoded) {
|
|
100
|
+
rval = Base64Ratchet.base64StringToString(rval); //Buffer.from(rval, 'base64');
|
|
101
|
+
}
|
|
102
|
+
if (contentType.startsWith('application/json')) {
|
|
103
|
+
// to handle cases where the charset is specified
|
|
104
|
+
rval = JSON.parse(rval.toString('ascii'));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return rval;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public static calcLogLevelViaEventOrEnvParam(
|
|
111
|
+
curLevel: LoggerLevelName,
|
|
112
|
+
event: APIGatewayEvent,
|
|
113
|
+
rConfig: EpsilonLoggerConfig,
|
|
114
|
+
): LoggerLevelName {
|
|
115
|
+
let rval: LoggerLevelName = curLevel;
|
|
116
|
+
if (rConfig?.envParamLogLevelName && process.env[rConfig.envParamLogLevelName]) {
|
|
117
|
+
rval = EnumRatchet.keyToEnum<LoggerLevelName>(LoggerLevelName, process.env[rConfig.envParamLogLevelName]);
|
|
118
|
+
Logger.silly('Found env log level : %s', rval);
|
|
119
|
+
}
|
|
120
|
+
if (
|
|
121
|
+
rConfig &&
|
|
122
|
+
rConfig.queryParamLogLevelName &&
|
|
123
|
+
event &&
|
|
124
|
+
event.queryStringParameters &&
|
|
125
|
+
event.queryStringParameters[rConfig.queryParamLogLevelName]
|
|
126
|
+
) {
|
|
127
|
+
rval = EnumRatchet.keyToEnum<LoggerLevelName>(LoggerLevelName, event.queryStringParameters[rConfig.queryParamLogLevelName]);
|
|
128
|
+
Logger.silly('Found query log level : %s', rval);
|
|
129
|
+
}
|
|
130
|
+
return rval;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* This is a weird function - sometimes your customers will not decode their query params and it
|
|
135
|
+
* results in query params that look like 'amp;SOMETHING' instead of 'SOMETHING'. This function
|
|
136
|
+
* looks for params that look like that, and strips the amp; from them. If you have any
|
|
137
|
+
* params you are expecting that have 'amp;' in front of them, DON'T use this function.
|
|
138
|
+
*
|
|
139
|
+
* Also, you are a moron for having a param that looks like that
|
|
140
|
+
*
|
|
141
|
+
* Yes, it would be better to fix this on the client side, but that is not always an option
|
|
142
|
+
* in production
|
|
143
|
+
* @param event
|
|
144
|
+
*/
|
|
145
|
+
public static fixStillEncodedQueryParams(event: APIGatewayEvent): void {
|
|
146
|
+
if (event?.queryStringParameters) {
|
|
147
|
+
const newParams: any = {};
|
|
148
|
+
Object.keys(event.queryStringParameters).forEach((k) => {
|
|
149
|
+
const val: string = event.queryStringParameters[k];
|
|
150
|
+
if (k.toLowerCase().startsWith('amp;')) {
|
|
151
|
+
newParams[k.substring(4)] = val;
|
|
152
|
+
} else {
|
|
153
|
+
newParams[k] = val;
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
event.queryStringParameters = newParams;
|
|
157
|
+
}
|
|
158
|
+
if (event?.multiValueQueryStringParameters) {
|
|
159
|
+
const newParams: any = {};
|
|
160
|
+
Object.keys(event.multiValueQueryStringParameters).forEach((k) => {
|
|
161
|
+
const val: string[] = event.multiValueQueryStringParameters[k];
|
|
162
|
+
if (k.toLowerCase().startsWith('amp;')) {
|
|
163
|
+
newParams[k.substring(4)] = val;
|
|
164
|
+
} else {
|
|
165
|
+
newParams[k] = val;
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
event.multiValueQueryStringParameters = newParams;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Allows you to force in a token for an arbitrary event without having to pass the whole thing
|
|
174
|
+
* through Epsilon. Useful for when you need to test a handler that needs authorization,
|
|
175
|
+
* and don't need to instantiate all of epsilon, just the handler
|
|
176
|
+
* @param event Event to decorate
|
|
177
|
+
* @param jwtToken String containing a valid JWT token
|
|
178
|
+
*/
|
|
179
|
+
public static applyTokenToEventForTesting(event: APIGatewayEvent, jwtToken: string): void {
|
|
180
|
+
const jwtFullData: any = jwt.decode(jwtToken, { complete: true });
|
|
181
|
+
if (!jwtFullData['payload']) {
|
|
182
|
+
throw new Error('No payload found in passed token');
|
|
183
|
+
}
|
|
184
|
+
// CAW 2020-05-03 : Have to strip the payload layer to match behavior of WebTokenManipulator in live
|
|
185
|
+
const jwtData: any = jwtFullData['payload'];
|
|
186
|
+
|
|
187
|
+
// Make the header consistent with the authorizer
|
|
188
|
+
event.headers = event.headers || {};
|
|
189
|
+
event.headers[EpsilonConstants.AUTH_HEADER_NAME.toLowerCase()] = 'Bearer ' + jwtToken;
|
|
190
|
+
|
|
191
|
+
event.requestContext = event.requestContext || ({} as APIGatewayEventRequestContext);
|
|
192
|
+
const newAuth: ExtendedAuthResponseContext = Object.assign({}, event.requestContext.authorizer) as ExtendedAuthResponseContext;
|
|
193
|
+
newAuth.userData = jwtData;
|
|
194
|
+
newAuth.userDataJSON = jwtData ? JSON.stringify(jwtData) : null;
|
|
195
|
+
newAuth.srcData = jwtToken;
|
|
196
|
+
event.requestContext.authorizer = newAuth;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
public static extractBasicAuthenticationToken(event: APIGatewayEvent, throwErrorOnMissingBad: boolean = false): BasicAuthToken {
|
|
200
|
+
let rval: BasicAuthToken = null;
|
|
201
|
+
if (!!event && !!event.headers) {
|
|
202
|
+
const headerVal: string = EventUtil.extractAuthorizationHeaderCaseInsensitive(event);
|
|
203
|
+
if (!!headerVal && headerVal.startsWith('Basic ')) {
|
|
204
|
+
const parsed: string = Base64Ratchet.base64StringToString(headerVal.substring(6));
|
|
205
|
+
const sp: string[] = parsed.split(':');
|
|
206
|
+
Logger.silly('Parsed to %j', sp);
|
|
207
|
+
if (!!sp && sp.length === 2) {
|
|
208
|
+
rval = {
|
|
209
|
+
username: sp[0],
|
|
210
|
+
password: sp[1],
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!rval && throwErrorOnMissingBad) {
|
|
217
|
+
throw new UnauthorizedError('Could not find valid basic authentication header');
|
|
218
|
+
}
|
|
219
|
+
return rval;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
public static eventIsAGraphQLIntrospection(event: APIGatewayEvent): boolean {
|
|
223
|
+
let rval: boolean = false;
|
|
224
|
+
if (event) {
|
|
225
|
+
if (!!event.httpMethod && 'post' === event.httpMethod.toLowerCase()) {
|
|
226
|
+
if (!!event.path && event.path.endsWith('/graphql')) {
|
|
227
|
+
try {
|
|
228
|
+
const body: any = EventUtil.jsonBodyToObject(event);
|
|
229
|
+
rval = !!body && !!body['operationName'] && body['operationName'] === 'IntrospectionQuery';
|
|
230
|
+
} catch (err) {
|
|
231
|
+
Logger.error('Failed to parse body - treating as non-graphql : %s : %s', event?.body, err, err);
|
|
232
|
+
rval = false;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return rval;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
public static extractAuthorizationHeaderCaseInsensitive(evt: APIGatewayEvent): string {
|
|
241
|
+
return MapRatchet.caseInsensitiveAccess<string>(evt?.headers || {}, EpsilonConstants.AUTH_HEADER_NAME);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
public static extractBearerTokenFromEvent(evt: APIGatewayEvent): string {
|
|
245
|
+
let rval: string = null;
|
|
246
|
+
const authHeader: string = StringRatchet.trimToEmpty(EventUtil.extractAuthorizationHeaderCaseInsensitive(evt));
|
|
247
|
+
if (authHeader.toLowerCase().startsWith('bearer ')) {
|
|
248
|
+
rval = authHeader.substring(7);
|
|
249
|
+
}
|
|
250
|
+
return rval;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
public static hostMatchesRegexInList(host: string, list: RegExp[], caseSensitive?: boolean): boolean {
|
|
254
|
+
let rval: boolean = false;
|
|
255
|
+
if (StringRatchet.trimToNull(host) && list?.length) {
|
|
256
|
+
// If not, cannot match by definition
|
|
257
|
+
const test: string = StringRatchet.trimToEmpty(caseSensitive ? host : host.toLowerCase());
|
|
258
|
+
rval = !!list.find((l) => test.match(l));
|
|
259
|
+
} else {
|
|
260
|
+
Logger.warn('Not matching regex - either host or list is misconfigured : %s : %j', host, list);
|
|
261
|
+
}
|
|
262
|
+
return rval;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
public static hostIsLocal(host: string): boolean {
|
|
266
|
+
return EventUtil.hostMatchesRegexInList(host, EventUtil.LOCAL_REGEX);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
public static hostIsLocalOrNotRoutableIP4(host: string): boolean {
|
|
270
|
+
return EventUtil.hostMatchesRegexInList(host, EventUtil.NON_ROUTABLE_REGEX);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { APIGatewayEvent, APIGatewayEventRequestContext, ProxyResult } from 'aws-lambda';
|
|
2
|
+
import { ResponseUtil } from './response-util.js';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { FilterChainContext } from '../config/http/filter-chain-context.js';
|
|
6
|
+
import { ExtendedAPIGatewayEvent } from '../config/http/extended-api-gateway-event.js';
|
|
7
|
+
import { BuiltInFilters } from '../built-in/http/built-in-filters.js';
|
|
8
|
+
import { EpsilonConstants } from '../epsilon-constants.js';
|
|
9
|
+
import { EsmRatchet } from '@bitblit/ratchet-common/lang/esm-ratchet';
|
|
10
|
+
import { describe, expect, test } from 'vitest';
|
|
11
|
+
|
|
12
|
+
describe('#responseUtil', function () {
|
|
13
|
+
test('should correctly combine a redirect url and query params', function () {
|
|
14
|
+
const evt: APIGatewayEvent = {
|
|
15
|
+
httpMethod: 'get',
|
|
16
|
+
multiValueHeaders: {},
|
|
17
|
+
multiValueQueryStringParameters: {},
|
|
18
|
+
path: '/v0/meta/server',
|
|
19
|
+
body: null,
|
|
20
|
+
headers: null,
|
|
21
|
+
isBase64Encoded: false,
|
|
22
|
+
pathParameters: null,
|
|
23
|
+
stageVariables: null,
|
|
24
|
+
resource: null,
|
|
25
|
+
queryStringParameters: {
|
|
26
|
+
a: 'b',
|
|
27
|
+
c: 'd',
|
|
28
|
+
},
|
|
29
|
+
requestContext: {
|
|
30
|
+
stage: 'v0',
|
|
31
|
+
} as APIGatewayEventRequestContext,
|
|
32
|
+
} as APIGatewayEvent;
|
|
33
|
+
|
|
34
|
+
const out1: ProxyResult = ResponseUtil.redirect('myTarget?e=f', 301, evt.queryStringParameters);
|
|
35
|
+
expect(out1).toBeTruthy();
|
|
36
|
+
expect(out1.headers).toBeTruthy();
|
|
37
|
+
expect(out1.headers.Location).toEqual('myTarget?e=f&a=b&c=d');
|
|
38
|
+
|
|
39
|
+
const out2: ProxyResult = ResponseUtil.redirect('myTarget', 301, evt.queryStringParameters);
|
|
40
|
+
expect(out2).toBeTruthy();
|
|
41
|
+
expect(out2.headers).toBeTruthy();
|
|
42
|
+
expect(out2.headers.Location).toEqual('myTarget?a=b&c=d');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('should leave already encoded stuff alone', async () => {
|
|
46
|
+
const singlePixel: string = fs
|
|
47
|
+
.readFileSync(path.join(EsmRatchet.fetchDirName(import.meta.url), '../../../../test-data/epsilon/test.png'))
|
|
48
|
+
.toString('base64');
|
|
49
|
+
|
|
50
|
+
const temp: ProxyResult = {
|
|
51
|
+
body: singlePixel,
|
|
52
|
+
statusCode: 200,
|
|
53
|
+
isBase64Encoded: true,
|
|
54
|
+
headers: {
|
|
55
|
+
'content-type': 'application/zip',
|
|
56
|
+
'content-disposition': 'attachment; filename="adomni_bs_' + new Date().getTime() + '.zip"',
|
|
57
|
+
},
|
|
58
|
+
} as ProxyResult;
|
|
59
|
+
|
|
60
|
+
const cast: ProxyResult = ResponseUtil.coerceToProxyResult(temp);
|
|
61
|
+
expect(cast.body).toEqual(temp.body);
|
|
62
|
+
|
|
63
|
+
const gzip: ProxyResult = await ResponseUtil.applyGzipIfPossible('gzip', cast);
|
|
64
|
+
expect(cast.body).toEqual(gzip.body);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('should add cors to proxy result MATCH 1', async () => {
|
|
68
|
+
const evt: ExtendedAPIGatewayEvent = JSON.parse(
|
|
69
|
+
fs
|
|
70
|
+
.readFileSync(
|
|
71
|
+
path.join(EsmRatchet.fetchDirName(import.meta.url), '../../../../test-data/epsilon/sample-json/sample-request-1.json'),
|
|
72
|
+
)
|
|
73
|
+
.toString(),
|
|
74
|
+
);
|
|
75
|
+
const proxy: ProxyResult = {} as ProxyResult;
|
|
76
|
+
const fCtx: FilterChainContext = {
|
|
77
|
+
event: evt,
|
|
78
|
+
rawResult: null,
|
|
79
|
+
context: null,
|
|
80
|
+
result: proxy,
|
|
81
|
+
routeAndParse: null,
|
|
82
|
+
modelValidator: null,
|
|
83
|
+
authenticators: null,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
await BuiltInFilters.addAllowReflectionCORSHeaders(fCtx);
|
|
87
|
+
|
|
88
|
+
expect(proxy.headers).toBeTruthy();
|
|
89
|
+
expect(proxy.headers['Access-Control-Allow-Origin']).toEqual('http://localhost:4200');
|
|
90
|
+
expect(proxy.headers['Access-Control-Allow-Methods']).toEqual('GET');
|
|
91
|
+
expect(proxy.headers['Access-Control-Allow-Headers']).toEqual(EpsilonConstants.AUTH_HEADER_NAME.toLowerCase());
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('should add cors to proxy result MATCH 2', async () => {
|
|
95
|
+
const evt: ExtendedAPIGatewayEvent = JSON.parse(
|
|
96
|
+
fs
|
|
97
|
+
.readFileSync(
|
|
98
|
+
path.join(EsmRatchet.fetchDirName(import.meta.url), '../../../../test-data/epsilon/sample-json/sample-request-2.json'),
|
|
99
|
+
)
|
|
100
|
+
.toString(),
|
|
101
|
+
);
|
|
102
|
+
const proxy: ProxyResult = {} as ProxyResult;
|
|
103
|
+
const fCtx: FilterChainContext = {
|
|
104
|
+
event: evt,
|
|
105
|
+
rawResult: null,
|
|
106
|
+
context: null,
|
|
107
|
+
result: proxy,
|
|
108
|
+
routeAndParse: null,
|
|
109
|
+
modelValidator: null,
|
|
110
|
+
authenticators: null,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
await BuiltInFilters.addAllowReflectionCORSHeaders(fCtx);
|
|
114
|
+
|
|
115
|
+
expect(proxy.headers).toBeTruthy();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { ProxyResult } from 'aws-lambda';
|
|
2
|
+
import { Logger } from '@bitblit/ratchet-common/logger/logger';
|
|
3
|
+
import { RestfulApiHttpError } from '@bitblit/ratchet-common/network/restful-api-http-error';
|
|
4
|
+
import { MapRatchet } from '@bitblit/ratchet-common/lang/map-ratchet';
|
|
5
|
+
import zlib from 'zlib';
|
|
6
|
+
|
|
7
|
+
export class ResponseUtil {
|
|
8
|
+
// Prevent instantiation
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
10
|
+
private constructor() {}
|
|
11
|
+
|
|
12
|
+
// Because of some variance in how browsers encode spaces
|
|
13
|
+
public static decodeUriComponentAndReplacePlus(val: string): string {
|
|
14
|
+
return decodeURIComponent(val.replace(/\+/g, ' '));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public static errorResponse<T>(err: RestfulApiHttpError<T>): ProxyResult {
|
|
18
|
+
const body: any = {
|
|
19
|
+
errors: err.errors,
|
|
20
|
+
httpStatusCode: err.httpStatusCode,
|
|
21
|
+
requestId: err.requestId,
|
|
22
|
+
};
|
|
23
|
+
if (err.detailErrorCode) {
|
|
24
|
+
body['detailErrorCode'] = err.detailErrorCode;
|
|
25
|
+
}
|
|
26
|
+
if (err.endUserErrors && err.endUserErrors.length > 0) {
|
|
27
|
+
body['endUserErrors'] = err.endUserErrors;
|
|
28
|
+
}
|
|
29
|
+
if (err.details) {
|
|
30
|
+
body['details'] = err.details;
|
|
31
|
+
}
|
|
32
|
+
if (err.wrappedError) {
|
|
33
|
+
body['wrappedError'] = err.wrappedError.name + ' : ' + err.wrappedError.message;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// No wrapped error since its already copied
|
|
37
|
+
|
|
38
|
+
const errorResponse: ProxyResult = {
|
|
39
|
+
statusCode: err.httpStatusCode,
|
|
40
|
+
isBase64Encoded: false,
|
|
41
|
+
headers: {
|
|
42
|
+
'Content-Type': 'application/json',
|
|
43
|
+
},
|
|
44
|
+
body: JSON.stringify(body),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return errorResponse;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public static redirect(target: string, code: number = 301, queryParams: any = null): ProxyResult {
|
|
51
|
+
if (code !== 301 && code !== 302 && code !== 307) {
|
|
52
|
+
throw new Error('Code must be 301 or 302 or 307 for a redirect');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let redirectTarget: string = target;
|
|
56
|
+
if (queryParams) {
|
|
57
|
+
const keys: string[] = Object.keys(queryParams);
|
|
58
|
+
if (keys.length > 0) {
|
|
59
|
+
Logger.silly('Applying params to input target : %j', queryParams);
|
|
60
|
+
redirectTarget += redirectTarget.indexOf('?') === -1 ? '?' : '&';
|
|
61
|
+
for (let i = 0; i < keys.length; i++) {
|
|
62
|
+
const k: string = keys[i];
|
|
63
|
+
// TODO: make sure not double encoding
|
|
64
|
+
redirectTarget += k + '=' + encodeURIComponent(queryParams[k]);
|
|
65
|
+
if (i < keys.length - 1) {
|
|
66
|
+
redirectTarget += '&';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
statusCode: code,
|
|
74
|
+
body: '{"redirect-target":"' + redirectTarget + '}',
|
|
75
|
+
headers: {
|
|
76
|
+
'Content-Type': 'application/json',
|
|
77
|
+
Location: redirectTarget,
|
|
78
|
+
},
|
|
79
|
+
} as ProxyResult;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public static coerceToProxyResult(input: any): ProxyResult {
|
|
83
|
+
let rval: ProxyResult = null;
|
|
84
|
+
|
|
85
|
+
if (input != null) {
|
|
86
|
+
if (typeof input === 'object') {
|
|
87
|
+
if (input.statusCode && input.body !== undefined) {
|
|
88
|
+
rval = Object.assign({}, input) as ProxyResult;
|
|
89
|
+
if (typeof input.body === 'string') {
|
|
90
|
+
// Do Nothing
|
|
91
|
+
} else if (Buffer.isBuffer(input.body)) {
|
|
92
|
+
rval.body = input.body.toString('base64');
|
|
93
|
+
rval.headers = input.headers || {};
|
|
94
|
+
rval.headers['Content-Type'] = input.body.contentType; // TODO: Does this work?
|
|
95
|
+
rval.isBase64Encoded = true;
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
// Its a generic object
|
|
99
|
+
const headers: any = input.headers || {};
|
|
100
|
+
headers['Content-Type'] = 'application/json';
|
|
101
|
+
rval = ResponseUtil.coerceToProxyResult({
|
|
102
|
+
statusCode: 200,
|
|
103
|
+
body: JSON.stringify(input),
|
|
104
|
+
headers: headers,
|
|
105
|
+
isBase64Encoded: false,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
} else if (typeof input === 'string' || Buffer.isBuffer(input)) {
|
|
109
|
+
rval = ResponseUtil.coerceToProxyResult({ statusCode: 200, body: input });
|
|
110
|
+
} else {
|
|
111
|
+
// boolean , number, etc - other top level types
|
|
112
|
+
// See : https://stackoverflow.com/questions/18419428/what-is-the-minimum-valid-json
|
|
113
|
+
const headers: any = input.headers || {};
|
|
114
|
+
headers['Content-Type'] = 'application/json';
|
|
115
|
+
rval = ResponseUtil.coerceToProxyResult({
|
|
116
|
+
statusCode: 200,
|
|
117
|
+
body: JSON.stringify(input),
|
|
118
|
+
headers: headers,
|
|
119
|
+
isBase64Encoded: false,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return rval;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
public static async applyGzipIfPossible(encodingHeader: string, proxyResult: ProxyResult): Promise<ProxyResult> {
|
|
128
|
+
const rval: ProxyResult = proxyResult;
|
|
129
|
+
if (encodingHeader && encodingHeader.toLowerCase().indexOf('gzip') > -1) {
|
|
130
|
+
const bigEnough: boolean = proxyResult.body.length > 1400; // MTU packet is 1400 bytes
|
|
131
|
+
let contentType: string = MapRatchet.extractValueFromMapIgnoreCase(proxyResult.headers, 'content-type') || '';
|
|
132
|
+
contentType = contentType.toLowerCase();
|
|
133
|
+
const exemptContent: boolean =
|
|
134
|
+
contentType === 'application/pdf' || contentType === 'application/zip' || contentType.startsWith('image/');
|
|
135
|
+
if (bigEnough && !exemptContent) {
|
|
136
|
+
const asBuffer: Buffer = proxyResult.isBase64Encoded ? Buffer.from(proxyResult.body, 'base64') : Buffer.from(proxyResult.body);
|
|
137
|
+
const zipped: Buffer = await this.gzip(asBuffer);
|
|
138
|
+
Logger.debug('Comp from %s to %d bytes', asBuffer.length, zipped.length);
|
|
139
|
+
const zipped64: string = zipped.toString('base64');
|
|
140
|
+
|
|
141
|
+
rval.body = zipped64;
|
|
142
|
+
rval.isBase64Encoded = true;
|
|
143
|
+
rval.headers = rval.headers || {};
|
|
144
|
+
rval.headers['Content-Encoding'] = 'gzip';
|
|
145
|
+
} else {
|
|
146
|
+
Logger.silly('Not gzipping, too small or exempt content');
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
Logger.silly('Not gzipping, not an accepted encoding');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return rval;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
public static gzip(input: Buffer): Promise<Buffer> {
|
|
156
|
+
const promise = new Promise<Buffer>(function (resolve, reject) {
|
|
157
|
+
zlib.gzip(input, function (error, result) {
|
|
158
|
+
if (!error) resolve(result);
|
|
159
|
+
else reject(error);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
return promise;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { RouteMapping } from './route-mapping.js';
|
|
2
|
+
import { ModelValidator } from '@bitblit/ratchet-misc/model-validator/model-validator';
|
|
3
|
+
import { HttpConfig } from '../../config/http/http-config.js';
|
|
4
|
+
|
|
5
|
+
export interface EpsilonRouter {
|
|
6
|
+
routes: RouteMapping[];
|
|
7
|
+
openApiModelValidator: ModelValidator; // Must be set to use model validation in your route mappings
|
|
8
|
+
config: HttpConfig;
|
|
9
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { AuthResponseContext } from 'aws-lambda';
|
|
2
|
+
|
|
3
|
+
export interface ExtendedAuthResponseContext extends AuthResponseContext {
|
|
4
|
+
userData: any; // The parsed data in the JWT token (ALB/API gateway auth won't populate this since it only allows strings
|
|
5
|
+
userDataJSON: string; // The data in the JWT token as a JSON string (for the API authorizer)
|
|
6
|
+
srcData: string; // The original JWT token
|
|
7
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { RouteValidatorConfig } from './route-validator-config.js';
|
|
2
|
+
import { HandlerFunction } from '../../config/http/handler-function.js';
|
|
3
|
+
import { HttpProcessingConfig } from '../../config/http/http-processing-config.js';
|
|
4
|
+
|
|
5
|
+
export interface RouteMapping {
|
|
6
|
+
method: string;
|
|
7
|
+
path: string;
|
|
8
|
+
function: HandlerFunction<any>;
|
|
9
|
+
|
|
10
|
+
// This will always be set, either pointing at a specific one or the default one
|
|
11
|
+
metaProcessingConfig: HttpProcessingConfig;
|
|
12
|
+
// If this is set, and fails, then it will 400
|
|
13
|
+
validation: RouteValidatorConfig;
|
|
14
|
+
// If this is set, enabled, and fails, then it will 500
|
|
15
|
+
outboundValidation: RouteValidatorConfig;
|
|
16
|
+
|
|
17
|
+
// If this is set, then :
|
|
18
|
+
// If there is no token / bad token in the request, this will 401
|
|
19
|
+
// If there is a required role that isn't found it will 403
|
|
20
|
+
authorizerName: string;
|
|
21
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export interface RouteValidatorConfig {
|
|
2
|
+
modelName: string; // Must be a valid entry in the model validator
|
|
3
|
+
emptyAllowed: boolean; // If true, an empty body passes validation, otherwise fails
|
|
4
|
+
extraPropertiesAllowed: boolean; // If true, extra properties in the body don't cause failure
|
|
5
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { RouterUtil } from './router-util.js';
|
|
2
|
+
import { RouteAndParse } from '../web-handler.js';
|
|
3
|
+
import { APIGatewayEvent } from 'aws-lambda';
|
|
4
|
+
import { EpsilonGlobalHandler } from '../../epsilon-global-handler.js';
|
|
5
|
+
import { SampleServerComponents } from '../../sample/sample-server-components.js';
|
|
6
|
+
import { describe, expect, test } from 'vitest';
|
|
7
|
+
|
|
8
|
+
describe('#routerUtilApplyOpenApiDoc', function () {
|
|
9
|
+
test('should create a router config from a yaml file', async () => {
|
|
10
|
+
const inst: EpsilonGlobalHandler = await SampleServerComponents.createSampleEpsilonGlobalHandler('routerUtilApplyOpenApiDoc-jest');
|
|
11
|
+
|
|
12
|
+
expect(inst.epsilon.modelValidator).toBeTruthy();
|
|
13
|
+
expect(inst.epsilon.modelValidator.fetchModel('AccessTokenRequest')).toBeTruthy();
|
|
14
|
+
|
|
15
|
+
// TODO: move this to its own test
|
|
16
|
+
const evt: APIGatewayEvent = {
|
|
17
|
+
httpMethod: 'get',
|
|
18
|
+
path: '/v0/meta/server',
|
|
19
|
+
requestContext: {
|
|
20
|
+
stage: 'v0',
|
|
21
|
+
},
|
|
22
|
+
} as APIGatewayEvent;
|
|
23
|
+
|
|
24
|
+
const find: RouteAndParse = await inst.epsilon.webHandler.findBestMatchingRoute(evt);
|
|
25
|
+
expect(find).toBeTruthy();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('should reformat a path to match the other library', function () {
|
|
29
|
+
const inString: string = '/meta/item/{itemId}';
|
|
30
|
+
const outString: string = RouterUtil.openApiPathToRouteParserPath(inString);
|
|
31
|
+
expect(outString).toEqual('/meta/item/:itemId');
|
|
32
|
+
});
|
|
33
|
+
});
|