Skip to content

Commit 79483fe

Browse files
committed
Enforce that rule part types/uiTypes match the lookup keys
This includes adding uiType fields where they were previously implicit in serialization, and fixing up a few other types similarly.
1 parent 2e570cd commit 79483fe

File tree

3 files changed

+68
-33
lines changed

3 files changed

+68
-33
lines changed

src/model/rules/definitions/http-rule-definitions.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ import { CUSTOM_RULE_EQUALS } from '../rules-structure';
1919
// Create per-method classes (that all serialize to the same MethodMatcher class + param)
2020
// for each supported HTTP method, as a methodName -> methodClass lookup object.
2121
export const MethodMatchers = _.reduce<MethodName, {
22-
[key in MethodName]: { new(): httpMatchers.MethodMatcher }
22+
[K in MethodName]: { new(): TypedMethodMatcher<K> }
2323
}>(
2424
MethodNames,
2525
(result, method) => {
2626
result[method] = class SpecificMethodMatcher extends httpMatchers.MethodMatcher {
2727

28-
uiType = method;
28+
readonly uiType = method;
2929

3030
constructor() {
3131
super(Method[method]);
@@ -34,12 +34,17 @@ export const MethodMatchers = _.reduce<MethodName, {
3434
explain() {
3535
return `${Method[this.method]} requests`;
3636
}
37-
};
37+
} as any; // Difficult to get TS to infer or accept TypedMethodMatcher<typeof method>
3838
return result;
3939
},
4040
{} as any
4141
);
4242

43+
// Tiny interface to let us make the METHOD -> methodMatcher{ uiType: METHOD } mapping explicit
44+
interface TypedMethodMatcher<R extends MethodName> extends httpMatchers.MethodMatcher {
45+
readonly uiType: R;
46+
}
47+
4348
// Override various specific & actions, so we can inject our own specific
4449
// explanations for certain cases
4550

@@ -51,7 +56,7 @@ export class WildcardMatcher extends httpMatchers.WildcardMatcher {
5156

5257
export class DefaultWildcardMatcher extends httpMatchers.WildcardMatcher {
5358

54-
uiType = 'default-wildcard';
59+
readonly uiType = 'default-wildcard';
5560

5661
explain() {
5762
return 'Any other requests';
@@ -60,7 +65,7 @@ export class DefaultWildcardMatcher extends httpMatchers.WildcardMatcher {
6065

6166
export class AmIUsingMatcher extends httpMatchers.RegexPathMatcher {
6267

63-
uiType = 'am-i-using';
68+
readonly uiType = 'am-i-using';
6469

6570
constructor() {
6671
// Optional slash is for backward compat: for server 0.1.18+ it's always present
@@ -130,6 +135,8 @@ serializr.createModelSchema(PassThroughHandler, {
130135

131136
export class ForwardToHostHandler extends httpHandlers.PassThroughHandlerDefinition {
132137

138+
readonly uiType = 'forward-to-host';
139+
133140
constructor(forwardToLocation: string, updateHostHeader: boolean, rulesStore: RulesStore) {
134141
super({
135142
...rulesStore.activePassthroughOptions,
@@ -160,6 +167,8 @@ export type ResponseTransform = httpHandlers.ResponseTransform;
160167

161168
export class TransformingHandler extends httpHandlers.PassThroughHandlerDefinition {
162169

170+
readonly uiType = 'req-res-transformer';
171+
163172
constructor(
164173
rulesStore: RulesStore,
165174
transformRequest: RequestTransform,
@@ -225,6 +234,8 @@ serializr.createModelSchema(TransformingHandler, {
225234

226235
export class RequestBreakpointHandler extends httpHandlers.PassThroughHandlerDefinition {
227236

237+
readonly uiType = 'request-breakpoint';
238+
228239
constructor(rulesStore: RulesStore) {
229240
super({
230241
...rulesStore.activePassthroughOptions,
@@ -244,6 +255,8 @@ serializr.createModelSchema(RequestBreakpointHandler, {
244255

245256
export class ResponseBreakpointHandler extends httpHandlers.PassThroughHandlerDefinition {
246257

258+
readonly uiType = 'response-breakpoint';
259+
247260
constructor(rulesStore: RulesStore) {
248261
super({
249262
...rulesStore.activePassthroughOptions,
@@ -264,6 +277,8 @@ serializr.createModelSchema(ResponseBreakpointHandler, {
264277

265278
export class RequestAndResponseBreakpointHandler extends httpHandlers.PassThroughHandlerDefinition {
266279

280+
readonly uiType = 'request-and-response-breakpoint';
281+
267282
constructor(rulesStore: RulesStore) {
268283
super({
269284
...rulesStore.activePassthroughOptions,

src/model/rules/definitions/websocket-rule-definitions.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,17 @@ import {
1414

1515
export class WebSocketWildcardMatcher extends WildcardMatcher {
1616

17-
uiType = 'ws-wildcard';
17+
readonly uiType = 'ws-wildcard';
1818

1919
explain() {
2020
return 'Any WebSocket';
2121
}
2222

2323
}
2424

25-
export class DefaultWebSocketWildcardMatcher extends WebSocketWildcardMatcher {
25+
export class DefaultWebSocketWildcardMatcher extends WildcardMatcher {
2626

27-
uiType = 'default-ws-wildcard';
27+
readonly uiType = 'default-ws-wildcard';
2828

2929
explain() {
3030
return 'Any other WebSockets';
@@ -63,7 +63,9 @@ export interface WebSocketMockRule extends Omit<WebSocketRuleData, 'matchers'> {
6363
type: 'websocket';
6464
activated: boolean;
6565
// WebSockets use the same HTTP matchers, but require an initial WebSocket matcher:
66-
matchers: Array<HttpMatcher> & { 0?: WebSocketWildcardMatcher };
66+
matchers: Array<HttpMatcher> & {
67+
0?: WebSocketWildcardMatcher | DefaultWebSocketWildcardMatcher
68+
};
6769
handler: WebSocketHandler;
6870
completionChecker: completionCheckers.Always; // HTK rules all *always* match
6971
};

src/model/rules/rules.ts

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import {
2020
WebSocketMockRule
2121
} from './definitions/websocket-rule-definitions';
2222

23+
/// --- Matchers ---
24+
2325
// Define maps to/from matcher keys to matcher classes, and
2426
// types for the matchers & classes themselves; both the built-in
2527
// ones and our own extra additions & overrides.
@@ -30,14 +32,14 @@ export const MatcherLookup = {
3032

3133
export type MatcherClassKey = keyof typeof MatcherLookup;
3234
export type MatcherClass = typeof MatcherLookup[MatcherClassKey];
33-
export type Matcher = InstanceType<MatcherClass>;
35+
export type Matcher = typeof MatcherLookup extends {
36+
// Enforce that keys match .type or uiType for each matcher class:
37+
[K in keyof typeof MatcherLookup]: new (...args: any[]) => { type: K } | { uiType: K }
38+
}
39+
? InstanceType<MatcherClass>
40+
: never;
3441

35-
export const MatcherKeys = new Map<MatcherClass, MatcherClassKey>(
36-
Object.entries(MatcherLookup)
37-
.map(
38-
([key, matcher]) => [matcher, key]
39-
) as Array<[MatcherClass, MatcherClassKey]>
40-
);
42+
/// --- Handlers ---
4143

4244
// Define maps to/from handler keys to handler classes, and
4345
// types for the handlers & classes themselves; both the built-in
@@ -47,26 +49,21 @@ export const HandlerLookup = {
4749
...WebSocketHandlerLookup
4850
};
4951

50-
const PaidHandlerClasses: HandlerClass[] = [
51-
StaticResponseHandler,
52-
FromFileResponseHandler,
53-
ForwardToHostHandler,
54-
TransformingHandler,
55-
TimeoutHandler,
56-
CloseConnectionHandler
57-
];
58-
59-
export const isPaidHandler = (handler: Handler) => {
60-
return _.some(PaidHandlerClasses, (cls) => handler instanceof cls);
61-
}
62-
63-
export const isPaidHandlerClass = (handlerClass: HandlerClass) => {
64-
return PaidHandlerClasses.includes(handlerClass);
65-
}
52+
export const MatcherKeys = new Map<MatcherClass, MatcherClassKey>(
53+
Object.entries(MatcherLookup)
54+
.map(
55+
([key, matcher]) => [matcher, key]
56+
) as Array<[MatcherClass, MatcherClassKey]>
57+
);
6658

6759
export type HandlerClassKey = keyof typeof HandlerLookup;
6860
export type HandlerClass = typeof HandlerLookup[HandlerClassKey];
69-
export type Handler = InstanceType<HandlerClass>;
61+
export type Handler = typeof HandlerLookup extends {
62+
// Enforce that keys match .type or uiType for each handler class:
63+
[K in keyof typeof HandlerLookup]: new (...args: any[]) => { type: K } | { uiType: K }
64+
}
65+
? InstanceType<HandlerClass>
66+
: never;
7067

7168
export const HandlerKeys = new Map<HandlerClass, HandlerClassKey>(
7269
Object.entries(HandlerLookup)
@@ -75,13 +72,34 @@ export const HandlerKeys = new Map<HandlerClass, HandlerClassKey>(
7572
) as Array<[HandlerClass, HandlerClassKey]>
7673
);
7774

75+
/// --- Matcher/handler special categories ---
76+
7877
export const InitialMatcherClasses = [
7978
WildcardMatcher,
8079
...Object.values(MethodMatchers)
8180
];
8281
export type InitialMatcherClass = typeof InitialMatcherClasses[0];
8382
export type InitialMatcher = InstanceType<InitialMatcherClass>;
8483

84+
const PaidHandlerClasses: HandlerClass[] = [
85+
StaticResponseHandler,
86+
FromFileResponseHandler,
87+
ForwardToHostHandler,
88+
TransformingHandler,
89+
TimeoutHandler,
90+
CloseConnectionHandler
91+
];
92+
93+
export const isPaidHandler = (handler: Handler) => {
94+
return _.some(PaidHandlerClasses, (cls) => handler instanceof cls);
95+
}
96+
97+
export const isPaidHandlerClass = (handlerClass: HandlerClass) => {
98+
return PaidHandlerClasses.includes(handlerClass);
99+
}
100+
101+
/// --- Rules ---
102+
85103
export type HtkMockRule =
86104
| WebSocketMockRule
87105
| HttpMockRule;

0 commit comments

Comments
 (0)