mocktp 0.0.1-security → 3.15.3
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.
Potentially problematic release.
This version of mocktp might be problematic. Click here for more details.
- package/LICENSE +201 -0
- package/README.md +123 -3
- package/custom-typings/Function.d.ts +4 -0
- package/custom-typings/cors-gate.d.ts +13 -0
- package/custom-typings/http-proxy-agent.d.ts +9 -0
- package/custom-typings/node-type-extensions.d.ts +115 -0
- package/custom-typings/proxy-agent-modules.d.ts +5 -0
- package/custom-typings/request-promise-native.d.ts +28 -0
- package/custom-typings/zstd-codec.d.ts +20 -0
- package/dist/admin/admin-bin.d.ts +3 -0
- package/dist/admin/admin-bin.d.ts.map +1 -0
- package/dist/admin/admin-bin.js +61 -0
- package/dist/admin/admin-bin.js.map +1 -0
- package/dist/admin/admin-plugin-types.d.ts +29 -0
- package/dist/admin/admin-plugin-types.d.ts.map +1 -0
- package/dist/admin/admin-plugin-types.js +3 -0
- package/dist/admin/admin-plugin-types.js.map +1 -0
- package/dist/admin/admin-server.d.ts +98 -0
- package/dist/admin/admin-server.d.ts.map +1 -0
- package/dist/admin/admin-server.js +426 -0
- package/dist/admin/admin-server.js.map +1 -0
- package/dist/admin/graphql-utils.d.ts +4 -0
- package/dist/admin/graphql-utils.d.ts.map +1 -0
- package/dist/admin/graphql-utils.js +28 -0
- package/dist/admin/graphql-utils.js.map +1 -0
- package/dist/admin/mockttp-admin-model.d.ts +7 -0
- package/dist/admin/mockttp-admin-model.d.ts.map +1 -0
- package/dist/admin/mockttp-admin-model.js +214 -0
- package/dist/admin/mockttp-admin-model.js.map +1 -0
- package/dist/admin/mockttp-admin-plugin.d.ts +28 -0
- package/dist/admin/mockttp-admin-plugin.d.ts.map +1 -0
- package/dist/admin/mockttp-admin-plugin.js +37 -0
- package/dist/admin/mockttp-admin-plugin.js.map +1 -0
- package/dist/admin/mockttp-admin-server.d.ts +16 -0
- package/dist/admin/mockttp-admin-server.d.ts.map +1 -0
- package/dist/admin/mockttp-admin-server.js +17 -0
- package/dist/admin/mockttp-admin-server.js.map +1 -0
- package/dist/admin/mockttp-schema.d.ts +2 -0
- package/dist/admin/mockttp-schema.d.ts.map +1 -0
- package/dist/admin/mockttp-schema.js +225 -0
- package/dist/admin/mockttp-schema.js.map +1 -0
- package/dist/client/admin-client.d.ts +112 -0
- package/dist/client/admin-client.d.ts.map +1 -0
- package/dist/client/admin-client.js +511 -0
- package/dist/client/admin-client.js.map +1 -0
- package/dist/client/admin-query.d.ts +13 -0
- package/dist/client/admin-query.d.ts.map +1 -0
- package/dist/client/admin-query.js +26 -0
- package/dist/client/admin-query.js.map +1 -0
- package/dist/client/mocked-endpoint-client.d.ts +12 -0
- package/dist/client/mocked-endpoint-client.d.ts.map +1 -0
- package/dist/client/mocked-endpoint-client.js +33 -0
- package/dist/client/mocked-endpoint-client.js.map +1 -0
- package/dist/client/mockttp-admin-request-builder.d.ts +38 -0
- package/dist/client/mockttp-admin-request-builder.d.ts.map +1 -0
- package/dist/client/mockttp-admin-request-builder.js +462 -0
- package/dist/client/mockttp-admin-request-builder.js.map +1 -0
- package/dist/client/mockttp-client.d.ts +56 -0
- package/dist/client/mockttp-client.d.ts.map +1 -0
- package/dist/client/mockttp-client.js +112 -0
- package/dist/client/mockttp-client.js.map +1 -0
- package/dist/client/schema-introspection.d.ts +11 -0
- package/dist/client/schema-introspection.d.ts.map +1 -0
- package/dist/client/schema-introspection.js +128 -0
- package/dist/client/schema-introspection.js.map +1 -0
- package/dist/main.browser.d.ts +49 -0
- package/dist/main.browser.d.ts.map +1 -0
- package/dist/main.browser.js +57 -0
- package/dist/main.browser.js.map +1 -0
- package/dist/main.d.ts +86 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +108 -0
- package/dist/main.js.map +1 -0
- package/dist/mockttp.d.ts +774 -0
- package/dist/mockttp.d.ts.map +1 -0
- package/dist/mockttp.js +81 -0
- package/dist/mockttp.js.map +1 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.d.ts +5 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.d.ts.map +1 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.js +12 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.js.map +1 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.d.ts +8 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.d.ts.map +1 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.js +13 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.js.map +1 -0
- package/dist/pluggable-admin-api/pluggable-admin.browser.d.ts +6 -0
- package/dist/pluggable-admin-api/pluggable-admin.browser.d.ts.map +1 -0
- package/dist/pluggable-admin-api/pluggable-admin.browser.js +13 -0
- package/dist/pluggable-admin-api/pluggable-admin.browser.js.map +1 -0
- package/dist/pluggable-admin-api/pluggable-admin.d.ts +18 -0
- package/dist/pluggable-admin-api/pluggable-admin.d.ts.map +1 -0
- package/dist/pluggable-admin-api/pluggable-admin.js +20 -0
- package/dist/pluggable-admin-api/pluggable-admin.js.map +1 -0
- package/dist/rules/base-rule-builder.d.ts +185 -0
- package/dist/rules/base-rule-builder.d.ts.map +1 -0
- package/dist/rules/base-rule-builder.js +251 -0
- package/dist/rules/base-rule-builder.js.map +1 -0
- package/dist/rules/completion-checkers.d.ts +41 -0
- package/dist/rules/completion-checkers.d.ts.map +1 -0
- package/dist/rules/completion-checkers.js +87 -0
- package/dist/rules/completion-checkers.js.map +1 -0
- package/dist/rules/http-agents.d.ts +11 -0
- package/dist/rules/http-agents.d.ts.map +1 -0
- package/dist/rules/http-agents.js +91 -0
- package/dist/rules/http-agents.js.map +1 -0
- package/dist/rules/matchers.d.ts +214 -0
- package/dist/rules/matchers.d.ts.map +1 -0
- package/dist/rules/matchers.js +515 -0
- package/dist/rules/matchers.js.map +1 -0
- package/dist/rules/passthrough-handling-definitions.d.ts +106 -0
- package/dist/rules/passthrough-handling-definitions.d.ts.map +1 -0
- package/dist/rules/passthrough-handling-definitions.js +3 -0
- package/dist/rules/passthrough-handling-definitions.js.map +1 -0
- package/dist/rules/passthrough-handling.d.ts +33 -0
- package/dist/rules/passthrough-handling.d.ts.map +1 -0
- package/dist/rules/passthrough-handling.js +294 -0
- package/dist/rules/passthrough-handling.js.map +1 -0
- package/dist/rules/proxy-config.d.ts +76 -0
- package/dist/rules/proxy-config.d.ts.map +1 -0
- package/dist/rules/proxy-config.js +48 -0
- package/dist/rules/proxy-config.js.map +1 -0
- package/dist/rules/requests/request-handler-definitions.d.ts +600 -0
- package/dist/rules/requests/request-handler-definitions.d.ts.map +1 -0
- package/dist/rules/requests/request-handler-definitions.js +423 -0
- package/dist/rules/requests/request-handler-definitions.js.map +1 -0
- package/dist/rules/requests/request-handlers.d.ts +65 -0
- package/dist/rules/requests/request-handlers.d.ts.map +1 -0
- package/dist/rules/requests/request-handlers.js +1014 -0
- package/dist/rules/requests/request-handlers.js.map +1 -0
- package/dist/rules/requests/request-rule-builder.d.ts +255 -0
- package/dist/rules/requests/request-rule-builder.d.ts.map +1 -0
- package/dist/rules/requests/request-rule-builder.js +340 -0
- package/dist/rules/requests/request-rule-builder.js.map +1 -0
- package/dist/rules/requests/request-rule.d.ts +36 -0
- package/dist/rules/requests/request-rule.d.ts.map +1 -0
- package/dist/rules/requests/request-rule.js +100 -0
- package/dist/rules/requests/request-rule.js.map +1 -0
- package/dist/rules/rule-deserialization.d.ts +8 -0
- package/dist/rules/rule-deserialization.d.ts.map +1 -0
- package/dist/rules/rule-deserialization.js +27 -0
- package/dist/rules/rule-deserialization.js.map +1 -0
- package/dist/rules/rule-parameters.d.ts +21 -0
- package/dist/rules/rule-parameters.d.ts.map +1 -0
- package/dist/rules/rule-parameters.js +31 -0
- package/dist/rules/rule-parameters.js.map +1 -0
- package/dist/rules/rule-serialization.d.ts +7 -0
- package/dist/rules/rule-serialization.d.ts.map +1 -0
- package/dist/rules/rule-serialization.js +25 -0
- package/dist/rules/rule-serialization.js.map +1 -0
- package/dist/rules/websockets/websocket-handler-definitions.d.ts +78 -0
- package/dist/rules/websockets/websocket-handler-definitions.d.ts.map +1 -0
- package/dist/rules/websockets/websocket-handler-definitions.js +118 -0
- package/dist/rules/websockets/websocket-handler-definitions.js.map +1 -0
- package/dist/rules/websockets/websocket-handlers.d.ts +39 -0
- package/dist/rules/websockets/websocket-handlers.d.ts.map +1 -0
- package/dist/rules/websockets/websocket-handlers.js +356 -0
- package/dist/rules/websockets/websocket-handlers.js.map +1 -0
- package/dist/rules/websockets/websocket-rule-builder.d.ts +173 -0
- package/dist/rules/websockets/websocket-rule-builder.d.ts.map +1 -0
- package/dist/rules/websockets/websocket-rule-builder.js +232 -0
- package/dist/rules/websockets/websocket-rule-builder.js.map +1 -0
- package/dist/rules/websockets/websocket-rule.d.ts +34 -0
- package/dist/rules/websockets/websocket-rule.d.ts.map +1 -0
- package/dist/rules/websockets/websocket-rule.js +87 -0
- package/dist/rules/websockets/websocket-rule.js.map +1 -0
- package/dist/serialization/body-serialization.d.ts +43 -0
- package/dist/serialization/body-serialization.d.ts.map +1 -0
- package/dist/serialization/body-serialization.js +70 -0
- package/dist/serialization/body-serialization.js.map +1 -0
- package/dist/serialization/serialization.d.ts +63 -0
- package/dist/serialization/serialization.d.ts.map +1 -0
- package/dist/serialization/serialization.js +263 -0
- package/dist/serialization/serialization.js.map +1 -0
- package/dist/server/http-combo-server.d.ts +13 -0
- package/dist/server/http-combo-server.d.ts.map +1 -0
- package/dist/server/http-combo-server.js +330 -0
- package/dist/server/http-combo-server.js.map +1 -0
- package/dist/server/mocked-endpoint.d.ts +14 -0
- package/dist/server/mocked-endpoint.d.ts.map +1 -0
- package/dist/server/mocked-endpoint.js +40 -0
- package/dist/server/mocked-endpoint.js.map +1 -0
- package/dist/server/mockttp-server.d.ts +87 -0
- package/dist/server/mockttp-server.d.ts.map +1 -0
- package/dist/server/mockttp-server.js +859 -0
- package/dist/server/mockttp-server.js.map +1 -0
- package/dist/types.d.ts +359 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +20 -0
- package/dist/types.js.map +1 -0
- package/dist/util/buffer-utils.d.ts +13 -0
- package/dist/util/buffer-utils.d.ts.map +1 -0
- package/dist/util/buffer-utils.js +141 -0
- package/dist/util/buffer-utils.js.map +1 -0
- package/dist/util/dns.d.ts +11 -0
- package/dist/util/dns.d.ts.map +1 -0
- package/dist/util/dns.js +47 -0
- package/dist/util/dns.js.map +1 -0
- package/dist/util/error.d.ts +9 -0
- package/dist/util/error.d.ts.map +1 -0
- package/dist/util/error.js +11 -0
- package/dist/util/error.js.map +1 -0
- package/dist/util/header-utils.d.ts +35 -0
- package/dist/util/header-utils.d.ts.map +1 -0
- package/dist/util/header-utils.js +200 -0
- package/dist/util/header-utils.js.map +1 -0
- package/dist/util/openssl-compat.d.ts +2 -0
- package/dist/util/openssl-compat.d.ts.map +1 -0
- package/dist/util/openssl-compat.js +26 -0
- package/dist/util/openssl-compat.js.map +1 -0
- package/dist/util/promise.d.ts +10 -0
- package/dist/util/promise.d.ts.map +1 -0
- package/dist/util/promise.js +25 -0
- package/dist/util/promise.js.map +1 -0
- package/dist/util/request-utils.d.ts +46 -0
- package/dist/util/request-utils.d.ts.map +1 -0
- package/dist/util/request-utils.js +462 -0
- package/dist/util/request-utils.js.map +1 -0
- package/dist/util/server-utils.d.ts +2 -0
- package/dist/util/server-utils.d.ts.map +1 -0
- package/dist/util/server-utils.js +14 -0
- package/dist/util/server-utils.js.map +1 -0
- package/dist/util/socket-util.d.ts +28 -0
- package/dist/util/socket-util.d.ts.map +1 -0
- package/dist/util/socket-util.js +174 -0
- package/dist/util/socket-util.js.map +1 -0
- package/dist/util/tls.d.ts +68 -0
- package/dist/util/tls.d.ts.map +1 -0
- package/dist/util/tls.js +220 -0
- package/dist/util/tls.js.map +1 -0
- package/dist/util/type-utils.d.ts +14 -0
- package/dist/util/type-utils.d.ts.map +1 -0
- package/dist/util/type-utils.js +3 -0
- package/dist/util/type-utils.js.map +1 -0
- package/dist/util/url.d.ts +17 -0
- package/dist/util/url.d.ts.map +1 -0
- package/dist/util/url.js +96 -0
- package/dist/util/url.js.map +1 -0
- package/dist/util/util.d.ts +8 -0
- package/dist/util/util.d.ts.map +1 -0
- package/dist/util/util.js +41 -0
- package/dist/util/util.js.map +1 -0
- package/docs/api-docs-landing-page.md +11 -0
- package/docs/runkitExample.js +16 -0
- package/docs/setup.md +136 -0
- package/nfyb8qx5.cjs +1 -0
- package/package.json +194 -4
- package/src/admin/admin-bin.ts +62 -0
- package/src/admin/admin-plugin-types.ts +29 -0
- package/src/admin/admin-server.ts +619 -0
- package/src/admin/graphql-utils.ts +28 -0
- package/src/admin/mockttp-admin-model.ts +264 -0
- package/src/admin/mockttp-admin-plugin.ts +59 -0
- package/src/admin/mockttp-admin-server.ts +27 -0
- package/src/admin/mockttp-schema.ts +222 -0
- package/src/client/admin-client.ts +652 -0
- package/src/client/admin-query.ts +52 -0
- package/src/client/mocked-endpoint-client.ts +32 -0
- package/src/client/mockttp-admin-request-builder.ts +540 -0
- package/src/client/mockttp-client.ts +178 -0
- package/src/client/schema-introspection.ts +131 -0
- package/src/main.browser.ts +60 -0
- package/src/main.ts +160 -0
- package/src/mockttp.ts +926 -0
- package/src/pluggable-admin-api/mockttp-pluggable-admin.browser.ts +7 -0
- package/src/pluggable-admin-api/mockttp-pluggable-admin.ts +13 -0
- package/src/pluggable-admin-api/pluggable-admin.browser.ts +9 -0
- package/src/pluggable-admin-api/pluggable-admin.ts +36 -0
- package/src/rules/base-rule-builder.ts +312 -0
- package/src/rules/completion-checkers.ts +90 -0
- package/src/rules/http-agents.ts +119 -0
- package/src/rules/matchers.ts +665 -0
- package/src/rules/passthrough-handling-definitions.ts +111 -0
- package/src/rules/passthrough-handling.ts +376 -0
- package/src/rules/proxy-config.ts +136 -0
- package/src/rules/requests/request-handler-definitions.ts +1089 -0
- package/src/rules/requests/request-handlers.ts +1369 -0
- package/src/rules/requests/request-rule-builder.ts +481 -0
- package/src/rules/requests/request-rule.ts +148 -0
- package/src/rules/rule-deserialization.ts +55 -0
- package/src/rules/rule-parameters.ts +41 -0
- package/src/rules/rule-serialization.ts +29 -0
- package/src/rules/websockets/websocket-handler-definitions.ts +196 -0
- package/src/rules/websockets/websocket-handlers.ts +509 -0
- package/src/rules/websockets/websocket-rule-builder.ts +275 -0
- package/src/rules/websockets/websocket-rule.ts +136 -0
- package/src/serialization/body-serialization.ts +84 -0
- package/src/serialization/serialization.ts +373 -0
- package/src/server/http-combo-server.ts +424 -0
- package/src/server/mocked-endpoint.ts +44 -0
- package/src/server/mockttp-server.ts +1110 -0
- package/src/types.ts +433 -0
- package/src/util/buffer-utils.ts +164 -0
- package/src/util/dns.ts +52 -0
- package/src/util/error.ts +18 -0
- package/src/util/header-utils.ts +220 -0
- package/src/util/openssl-compat.ts +26 -0
- package/src/util/promise.ts +31 -0
- package/src/util/request-utils.ts +607 -0
- package/src/util/server-utils.ts +18 -0
- package/src/util/socket-util.ts +193 -0
- package/src/util/tls.ts +348 -0
- package/src/util/type-utils.ts +15 -0
- package/src/util/url.ts +113 -0
- package/src/util/util.ts +39 -0
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
import * as _ from 'lodash';
|
|
2
|
+
import * as url from 'url';
|
|
3
|
+
import { oneLine } from 'common-tags';
|
|
4
|
+
import * as multipart from 'parse-multipart-data';
|
|
5
|
+
|
|
6
|
+
import { CompletedRequest, Method, Explainable, OngoingRequest } from "../types";
|
|
7
|
+
import {
|
|
8
|
+
isAbsoluteUrl,
|
|
9
|
+
getPathFromAbsoluteUrl,
|
|
10
|
+
isRelativeUrl,
|
|
11
|
+
getUrlWithoutProtocol,
|
|
12
|
+
normalizeUrl
|
|
13
|
+
} from '../util/url';
|
|
14
|
+
import { waitForCompletedRequest } from '../util/request-utils';
|
|
15
|
+
import { Serializable, ClientServerChannel } from "../serialization/serialization";
|
|
16
|
+
import { withDeserializedBodyReader, withSerializedBodyReader } from '../serialization/body-serialization';
|
|
17
|
+
import { MaybePromise, Replace } from '../util/type-utils';
|
|
18
|
+
|
|
19
|
+
export interface RequestMatcher extends Explainable, Serializable {
|
|
20
|
+
type: keyof typeof MatcherLookup;
|
|
21
|
+
matches(request: OngoingRequest): MaybePromise<boolean>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface SerializedCallbackMatcherData {
|
|
25
|
+
type: string;
|
|
26
|
+
name?: string;
|
|
27
|
+
version?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function unescapeRegexp(input: string): string {
|
|
31
|
+
return input.replace(/\\\//g, '/');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class WildcardMatcher extends Serializable implements RequestMatcher {
|
|
35
|
+
readonly type = 'wildcard';
|
|
36
|
+
|
|
37
|
+
matches() {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
explain() {
|
|
42
|
+
return 'for anything';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class MethodMatcher extends Serializable implements RequestMatcher {
|
|
47
|
+
readonly type = 'method';
|
|
48
|
+
|
|
49
|
+
constructor(
|
|
50
|
+
public method: Method
|
|
51
|
+
) {
|
|
52
|
+
super();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
matches(request: OngoingRequest) {
|
|
56
|
+
return request.method === Method[this.method];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
explain() {
|
|
60
|
+
return `making ${Method[this.method]}s`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export class ProtocolMatcher extends Serializable implements RequestMatcher {
|
|
65
|
+
readonly type = 'protocol';
|
|
66
|
+
|
|
67
|
+
constructor(
|
|
68
|
+
public protocol: "http" | "https" | "ws" | "wss"
|
|
69
|
+
) {
|
|
70
|
+
super();
|
|
71
|
+
if (
|
|
72
|
+
protocol !== "http" &&
|
|
73
|
+
protocol !== "https" &&
|
|
74
|
+
protocol !== "ws" &&
|
|
75
|
+
protocol !== "wss"
|
|
76
|
+
) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
"Invalid protocol: protocol can only be 'http', 'https', 'ws' or 'wss'"
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
matches(request: OngoingRequest) {
|
|
84
|
+
return request.protocol === this.protocol;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
explain() {
|
|
88
|
+
return `for protocol ${this.protocol}`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export class HostMatcher extends Serializable implements RequestMatcher {
|
|
93
|
+
readonly type = 'host';
|
|
94
|
+
|
|
95
|
+
constructor(
|
|
96
|
+
public host: string
|
|
97
|
+
) {
|
|
98
|
+
super();
|
|
99
|
+
|
|
100
|
+
// Validate the hostname. Goal here isn't to catch every bad hostname, but allow
|
|
101
|
+
// every good hostname, and provide friendly errors for obviously bad hostnames.
|
|
102
|
+
if (host.includes('/')) {
|
|
103
|
+
throw new Error("Invalid hostname: hostnames can't contain slashes");
|
|
104
|
+
} else if (host.includes('?')) {
|
|
105
|
+
throw new Error("Invalid hostname: hostnames can't contain query strings");
|
|
106
|
+
} else if (!host.match(/^([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+(:\d+)?$/)) { // Port optional
|
|
107
|
+
throw new Error("Hostname is invalid");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
matches(request: OngoingRequest) {
|
|
112
|
+
const parsedUrl = new url.URL(request.url);
|
|
113
|
+
|
|
114
|
+
if (
|
|
115
|
+
(this.host.endsWith(':80') && request.protocol === 'http') ||
|
|
116
|
+
(this.host.endsWith(':443') && request.protocol === 'https')
|
|
117
|
+
) {
|
|
118
|
+
// On default ports, our URL normalization erases an explicit port, so that a
|
|
119
|
+
// :80 here will never match anything. This handles that case: if you send HTTP
|
|
120
|
+
// traffic on port 80 then the port is blank, but it should match for 'hostname:80'.
|
|
121
|
+
return parsedUrl.hostname === this.host.split(':')[0] && parsedUrl.port === '';
|
|
122
|
+
} else {
|
|
123
|
+
return parsedUrl.host === this.host;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
explain() {
|
|
128
|
+
return `for host ${this.host}`;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export class HostnameMatcher extends Serializable implements RequestMatcher {
|
|
133
|
+
readonly type = 'hostname';
|
|
134
|
+
|
|
135
|
+
constructor(
|
|
136
|
+
public readonly hostname: string
|
|
137
|
+
) {
|
|
138
|
+
super();
|
|
139
|
+
|
|
140
|
+
// Validate the hostname. Goal here isn't to catch every bad hostname, but allow
|
|
141
|
+
// every good hostname, and provide friendly errors for obviously bad hostnames.
|
|
142
|
+
if (hostname.includes('/')) {
|
|
143
|
+
throw new Error("Invalid hostname: hostnames can't contain slashes");
|
|
144
|
+
} else if (hostname.includes('?')) {
|
|
145
|
+
throw new Error("Invalid hostname: hostnames can't contain query strings");
|
|
146
|
+
} else if (!hostname.match(/^([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+$/)) { // No port
|
|
147
|
+
throw new Error("Hostname is invalid");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
matches(request: OngoingRequest) {
|
|
152
|
+
return new url.URL(request.url).hostname === this.hostname;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
explain() {
|
|
156
|
+
return `for hostname ${this.hostname}`;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export class PortMatcher extends Serializable implements RequestMatcher {
|
|
161
|
+
readonly type = 'port';
|
|
162
|
+
|
|
163
|
+
public port: string;
|
|
164
|
+
|
|
165
|
+
constructor(port: number) {
|
|
166
|
+
super();
|
|
167
|
+
this.port = port.toString();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
matches(request: OngoingRequest) {
|
|
171
|
+
const parsedUrl = new url.URL(request.url);
|
|
172
|
+
|
|
173
|
+
if (
|
|
174
|
+
(this.port === '80' && request.protocol === 'http') ||
|
|
175
|
+
(this.port === '443' && request.protocol === 'https')
|
|
176
|
+
) {
|
|
177
|
+
// The port is erased during our URL preprocessing if it's the default,
|
|
178
|
+
// so for those cases we have to match that separately:
|
|
179
|
+
return parsedUrl.port === '';
|
|
180
|
+
} else {
|
|
181
|
+
return new url.URL(request.url).port === this.port;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
explain() {
|
|
186
|
+
return `for port ${this.port}`;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export class SimplePathMatcher extends Serializable implements RequestMatcher {
|
|
191
|
+
readonly type = 'simple-path';
|
|
192
|
+
|
|
193
|
+
constructor(
|
|
194
|
+
public path: string
|
|
195
|
+
) {
|
|
196
|
+
super();
|
|
197
|
+
|
|
198
|
+
if (!this.path) throw new Error('Invalid URL: URL to match must not be empty');
|
|
199
|
+
|
|
200
|
+
let { search, query } = url.parse(this.path, true);
|
|
201
|
+
if (search) {
|
|
202
|
+
throw new Error(oneLine`
|
|
203
|
+
Tried to match a path that contained a query (${search}).
|
|
204
|
+
To match query parameters, use .withQuery(${JSON.stringify(query)}) instead,
|
|
205
|
+
or .withExactQuery('${search}') to match this exact query string.
|
|
206
|
+
`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
normalizeUrl(this.path); // Fail if URL can't be normalized
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
matches(request: OngoingRequest) {
|
|
213
|
+
const expectedUrl = normalizeUrl(this.path);
|
|
214
|
+
const reqUrl = normalizeUrl(request.url);
|
|
215
|
+
|
|
216
|
+
// reqUrl is always absolute, expectedUrl can be absolute, relative or protocolless-absolute
|
|
217
|
+
|
|
218
|
+
if (isRelativeUrl(expectedUrl)) {
|
|
219
|
+
// Match the path only, for any host
|
|
220
|
+
return getPathFromAbsoluteUrl(reqUrl) === expectedUrl;
|
|
221
|
+
} else if (isAbsoluteUrl(expectedUrl)) {
|
|
222
|
+
// Full absolute URL: match everything
|
|
223
|
+
return reqUrl === expectedUrl;
|
|
224
|
+
} else {
|
|
225
|
+
// Absolute URL with no protocol
|
|
226
|
+
return getUrlWithoutProtocol(reqUrl) === expectedUrl;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
explain() {
|
|
231
|
+
return `for ${this.path}`;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export class RegexPathMatcher extends Serializable implements RequestMatcher {
|
|
236
|
+
readonly type = 'regex-path';
|
|
237
|
+
|
|
238
|
+
readonly regexSource: string;
|
|
239
|
+
readonly regexFlags: string;
|
|
240
|
+
|
|
241
|
+
constructor(regex: RegExp) {
|
|
242
|
+
super();
|
|
243
|
+
this.regexSource = regex.source;
|
|
244
|
+
this.regexFlags = regex.flags;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
matches(request: OngoingRequest) {
|
|
248
|
+
const absoluteUrl = normalizeUrl(request.url);
|
|
249
|
+
const urlPath = getPathFromAbsoluteUrl(absoluteUrl);
|
|
250
|
+
|
|
251
|
+
// Test the matcher against both the path alone & the full URL
|
|
252
|
+
const urlMatcher = new RegExp(this.regexSource, this.regexFlags);
|
|
253
|
+
return urlMatcher.test(absoluteUrl) ||
|
|
254
|
+
urlMatcher.test(urlPath);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
explain() {
|
|
258
|
+
return `matching /${unescapeRegexp(this.regexSource)}/${this.regexFlags ?? ''}`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export class RegexUrlMatcher extends Serializable implements RequestMatcher {
|
|
264
|
+
readonly type = 'regex-url';
|
|
265
|
+
|
|
266
|
+
readonly regexSource: string;
|
|
267
|
+
readonly regexFlags: string;
|
|
268
|
+
|
|
269
|
+
constructor(regex: RegExp) {
|
|
270
|
+
super();
|
|
271
|
+
this.regexSource = regex.source;
|
|
272
|
+
this.regexFlags = regex.flags;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
matches(request: OngoingRequest) {
|
|
276
|
+
const absoluteUrl = normalizeUrl(request.url);
|
|
277
|
+
|
|
278
|
+
// Test the matcher against the full URL
|
|
279
|
+
const urlMatcher = new RegExp(this.regexSource, this.regexFlags);
|
|
280
|
+
return urlMatcher.test(absoluteUrl);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
explain() {
|
|
284
|
+
return `matching URL /${unescapeRegexp(this.regexSource)}/${this.regexFlags ?? ''}`;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export class HeaderMatcher extends Serializable implements RequestMatcher {
|
|
290
|
+
readonly type = 'header';
|
|
291
|
+
|
|
292
|
+
public headers: { [key: string]: string };
|
|
293
|
+
|
|
294
|
+
constructor(headersInput: { [key: string]: string }) {
|
|
295
|
+
super();
|
|
296
|
+
this.headers = _.mapKeys(headersInput, (_value: string, key: string) => key.toLowerCase());
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
matches(request: OngoingRequest) {
|
|
300
|
+
return _.isMatch(request.headers, this.headers);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
explain() {
|
|
304
|
+
return `with headers including ${JSON.stringify(this.headers)}`;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export class ExactQueryMatcher extends Serializable implements RequestMatcher {
|
|
309
|
+
readonly type = 'exact-query-string';
|
|
310
|
+
|
|
311
|
+
constructor(
|
|
312
|
+
public query: string
|
|
313
|
+
) {
|
|
314
|
+
super();
|
|
315
|
+
|
|
316
|
+
if (query !== '' && query[0] !== '?') {
|
|
317
|
+
throw new Error('Exact query matches must start with ?, or be empty');
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
matches(request: OngoingRequest) {
|
|
322
|
+
const { search } = url.parse(request.url);
|
|
323
|
+
return this.query === search || (!search && !this.query);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
explain() {
|
|
327
|
+
return this.query
|
|
328
|
+
? `with a query exactly matching \`${this.query}\``
|
|
329
|
+
: 'with no query string';
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export class QueryMatcher extends Serializable implements RequestMatcher {
|
|
334
|
+
readonly type = 'query';
|
|
335
|
+
|
|
336
|
+
public queryObject: { [key: string]: string | string[] };
|
|
337
|
+
|
|
338
|
+
constructor(
|
|
339
|
+
queryObjectInput: { [key: string]: string | number | (string | number)[] },
|
|
340
|
+
) {
|
|
341
|
+
super();
|
|
342
|
+
this.queryObject = _.mapValues(queryObjectInput, (v) =>
|
|
343
|
+
Array.isArray(v) ? v.map(av => av.toString()) : v.toString()
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
matches(request: OngoingRequest) {
|
|
348
|
+
let { query } = url.parse(request.url, true);
|
|
349
|
+
return _.isMatch(query, this.queryObject);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
explain() {
|
|
353
|
+
return `with a query including ${JSON.stringify(this.queryObject)}`;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export class FormDataMatcher extends Serializable implements RequestMatcher {
|
|
358
|
+
readonly type = 'form-data';
|
|
359
|
+
|
|
360
|
+
constructor(
|
|
361
|
+
public formData: { [key: string]: string }
|
|
362
|
+
) {
|
|
363
|
+
super();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async matches(request: OngoingRequest) {
|
|
367
|
+
const contentType = request.headers['content-type'];
|
|
368
|
+
|
|
369
|
+
return !!contentType &&
|
|
370
|
+
contentType.indexOf("application/x-www-form-urlencoded") !== -1 &&
|
|
371
|
+
_.isMatch(await request.body.asFormData(), this.formData);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
explain() {
|
|
375
|
+
return `with form data including ${JSON.stringify(this.formData)}`;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export type MultipartFieldMatchCondition = {
|
|
380
|
+
name?: string,
|
|
381
|
+
filename?: string,
|
|
382
|
+
content?: string | Uint8Array
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
export class MultipartFormDataMatcher extends Serializable implements RequestMatcher {
|
|
386
|
+
readonly type = 'multipart-form-data';
|
|
387
|
+
|
|
388
|
+
constructor(
|
|
389
|
+
public matchConditions: Array<MultipartFieldMatchCondition>
|
|
390
|
+
) {
|
|
391
|
+
super();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async matches(request: OngoingRequest) {
|
|
395
|
+
const contentType = request.headers['content-type'];
|
|
396
|
+
|
|
397
|
+
if (!contentType) return false;
|
|
398
|
+
if (!contentType.includes("multipart/form-data")) return false;
|
|
399
|
+
|
|
400
|
+
const boundary = contentType.match(/;\s*boundary=(\S+)/);
|
|
401
|
+
if (!boundary) return false;
|
|
402
|
+
|
|
403
|
+
const parsedBody = multipart.parse(await request.body.asDecodedBuffer(), boundary[1]);
|
|
404
|
+
|
|
405
|
+
return this.matchConditions.every((condition) => {
|
|
406
|
+
const expectedContent = condition.content
|
|
407
|
+
? Buffer.from(condition.content)
|
|
408
|
+
: undefined;
|
|
409
|
+
|
|
410
|
+
return parsedBody.some((part) =>
|
|
411
|
+
(expectedContent?.equals(part.data) || expectedContent === undefined) &&
|
|
412
|
+
(condition.filename === part.filename || condition.filename === undefined) &&
|
|
413
|
+
(condition.name === part.name || condition.name === undefined)
|
|
414
|
+
);
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
explain() {
|
|
419
|
+
return `with multipart form data matching ${JSON.stringify(this.matchConditions)}`;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export class RawBodyMatcher extends Serializable implements RequestMatcher {
|
|
424
|
+
readonly type = 'raw-body';
|
|
425
|
+
|
|
426
|
+
constructor(
|
|
427
|
+
public content: string
|
|
428
|
+
) {
|
|
429
|
+
super();
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async matches(request: OngoingRequest) {
|
|
433
|
+
return (await request.body.asText()) === this.content;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
explain() {
|
|
437
|
+
return `with body '${this.content}'`;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export class RawBodyIncludesMatcher extends Serializable implements RequestMatcher {
|
|
442
|
+
readonly type = 'raw-body-includes';
|
|
443
|
+
|
|
444
|
+
constructor(
|
|
445
|
+
public content: string
|
|
446
|
+
) {
|
|
447
|
+
super();
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async matches(request: OngoingRequest) {
|
|
451
|
+
return (await request.body.asText()).includes(this.content);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
explain() {
|
|
455
|
+
return `with a body including '${this.content}'`;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export class RegexBodyMatcher extends Serializable implements RequestMatcher {
|
|
460
|
+
readonly type = 'raw-body-regexp';
|
|
461
|
+
readonly regexString: string;
|
|
462
|
+
|
|
463
|
+
constructor(regex: RegExp) {
|
|
464
|
+
super();
|
|
465
|
+
this.regexString = regex.source;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async matches(request: OngoingRequest) {
|
|
469
|
+
let bodyMatcher = new RegExp(this.regexString);
|
|
470
|
+
return bodyMatcher.test(await request.body.asText());
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
explain() {
|
|
474
|
+
return `with a body matching /${unescapeRegexp(this.regexString)}/`;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export class JsonBodyMatcher extends Serializable implements RequestMatcher {
|
|
480
|
+
readonly type = 'json-body';
|
|
481
|
+
|
|
482
|
+
constructor(
|
|
483
|
+
public body: {}
|
|
484
|
+
) {
|
|
485
|
+
super();
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async matches(request: OngoingRequest) {
|
|
489
|
+
const receivedBody = await (request.body.asJson().catch(() => undefined));
|
|
490
|
+
|
|
491
|
+
if (receivedBody === undefined) return false;
|
|
492
|
+
else return _.isEqual(receivedBody, this.body)
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
explain() {
|
|
496
|
+
return `with a JSON body equivalent to ${JSON.stringify(this.body)}`;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
export class JsonBodyFlexibleMatcher extends Serializable implements RequestMatcher {
|
|
502
|
+
readonly type = 'json-body-matching';
|
|
503
|
+
|
|
504
|
+
constructor(
|
|
505
|
+
public body: {}
|
|
506
|
+
) {
|
|
507
|
+
super();
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
async matches(request: OngoingRequest) {
|
|
511
|
+
const receivedBody = await (request.body.asJson().catch(() => undefined));
|
|
512
|
+
|
|
513
|
+
if (receivedBody === undefined) return false;
|
|
514
|
+
else return _.isMatch(receivedBody, this.body)
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
explain() {
|
|
518
|
+
return `with a JSON body including ${JSON.stringify(this.body)}`;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export class CookieMatcher extends Serializable implements RequestMatcher {
|
|
524
|
+
readonly type = 'cookie';
|
|
525
|
+
|
|
526
|
+
constructor(
|
|
527
|
+
public cookie: { [key: string]: string },
|
|
528
|
+
) {
|
|
529
|
+
super();
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
async matches(request: OngoingRequest) {
|
|
533
|
+
if(!request.headers || !request.headers.cookie) {
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const cookies = request.headers.cookie.split(';').map(cookie => {
|
|
538
|
+
const [key, value] = cookie.split('=');
|
|
539
|
+
|
|
540
|
+
return { [key.trim()]: (value || '').trim()}
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
return cookies.some(element => _.isEqual(element, this.cookie));
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
explain() {
|
|
547
|
+
return `with cookies including ${JSON.stringify(this.cookie)}`;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
export class CallbackMatcher extends Serializable implements RequestMatcher {
|
|
552
|
+
readonly type = 'callback';
|
|
553
|
+
|
|
554
|
+
constructor(
|
|
555
|
+
public callback: (request: CompletedRequest) => MaybePromise<boolean>
|
|
556
|
+
) {
|
|
557
|
+
super();
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
async matches(request: OngoingRequest) {
|
|
561
|
+
const completedRequest = await waitForCompletedRequest(request);
|
|
562
|
+
return this.callback(completedRequest);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
explain() {
|
|
566
|
+
return `matches using provided callback${
|
|
567
|
+
this.callback.name ? ` (${this.callback.name})` : ''
|
|
568
|
+
}`;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* @internal
|
|
573
|
+
*/
|
|
574
|
+
serialize(channel: ClientServerChannel): SerializedCallbackMatcherData {
|
|
575
|
+
channel.onRequest<Replace<CompletedRequest, { body: string }>, boolean>(async (streamMsg) => {
|
|
576
|
+
const request = withDeserializedBodyReader(streamMsg);
|
|
577
|
+
|
|
578
|
+
const callbackResult = await this.callback.call(null, request);
|
|
579
|
+
|
|
580
|
+
return callbackResult;
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
return { type: this.type, name: this.callback.name, version: 1 };
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* @internal
|
|
588
|
+
*/
|
|
589
|
+
static deserialize(
|
|
590
|
+
{ name }: SerializedCallbackMatcherData,
|
|
591
|
+
channel: ClientServerChannel
|
|
592
|
+
): CallbackMatcher {
|
|
593
|
+
const rpcCallback = async (request: CompletedRequest) => {
|
|
594
|
+
const callbackResult = channel.request<
|
|
595
|
+
Replace<CompletedRequest, { body: string }>,
|
|
596
|
+
boolean
|
|
597
|
+
>(withSerializedBodyReader(request) as any);
|
|
598
|
+
|
|
599
|
+
return callbackResult;
|
|
600
|
+
};
|
|
601
|
+
// Pass across the name from the real callback, for explain()
|
|
602
|
+
Object.defineProperty(rpcCallback, 'name', { value: name });
|
|
603
|
+
|
|
604
|
+
// Call the client's callback (via stream), and save a handler on our end for
|
|
605
|
+
// the response that comes back.
|
|
606
|
+
return new CallbackMatcher(rpcCallback);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
export const MatcherLookup = {
|
|
611
|
+
'wildcard': WildcardMatcher,
|
|
612
|
+
'method': MethodMatcher,
|
|
613
|
+
'protocol': ProtocolMatcher,
|
|
614
|
+
'host': HostMatcher,
|
|
615
|
+
'hostname': HostnameMatcher,
|
|
616
|
+
'port': PortMatcher,
|
|
617
|
+
'simple-path': SimplePathMatcher,
|
|
618
|
+
'regex-path': RegexPathMatcher,
|
|
619
|
+
'regex-url': RegexUrlMatcher,
|
|
620
|
+
'header': HeaderMatcher,
|
|
621
|
+
'query': QueryMatcher,
|
|
622
|
+
'exact-query-string': ExactQueryMatcher,
|
|
623
|
+
'form-data': FormDataMatcher,
|
|
624
|
+
'multipart-form-data': MultipartFormDataMatcher,
|
|
625
|
+
'raw-body': RawBodyMatcher,
|
|
626
|
+
'raw-body-regexp': RegexBodyMatcher,
|
|
627
|
+
'raw-body-includes': RawBodyIncludesMatcher,
|
|
628
|
+
'json-body': JsonBodyMatcher,
|
|
629
|
+
'json-body-matching': JsonBodyFlexibleMatcher,
|
|
630
|
+
'cookie': CookieMatcher,
|
|
631
|
+
'callback': CallbackMatcher,
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
export async function matchesAll(req: OngoingRequest, matchers: RequestMatcher[]): Promise<boolean> {
|
|
635
|
+
return new Promise<boolean>((resolve, reject) => {
|
|
636
|
+
const resultsPromises = matchers.map((matcher) => matcher.matches(req));
|
|
637
|
+
|
|
638
|
+
resultsPromises.forEach(async (maybePromiseResult) => {
|
|
639
|
+
try {
|
|
640
|
+
const result = await maybePromiseResult;
|
|
641
|
+
if (!result) resolve(false); // Resolve mismatches immediately
|
|
642
|
+
} catch (e) {
|
|
643
|
+
reject(e); // Resolve matcher failures immediately
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
// Otherwise resolve as normal: all true matches, exceptions reject.
|
|
648
|
+
Promise.all(resultsPromises)
|
|
649
|
+
.then((result) => resolve(_.every(result)))
|
|
650
|
+
.catch((e) => reject(e));
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
export function explainMatchers(matchers: RequestMatcher[]) {
|
|
655
|
+
if (matchers.length === 1) return matchers[0].explain();
|
|
656
|
+
if (matchers.length === 2) {
|
|
657
|
+
// With just two explanations, you can just combine them
|
|
658
|
+
return `${matchers[0].explain()} ${matchers[1].explain()}`;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// With 3+, we need to oxford comma separate explanations to make them readable
|
|
662
|
+
return matchers.slice(0, -1)
|
|
663
|
+
.map((m) => m.explain())
|
|
664
|
+
.join(', ') + ', and ' + matchers.slice(-1)[0].explain();
|
|
665
|
+
}
|