Skip to content

Commit 1da20d1

Browse files
committed
Scaffold support for multistep rules & rename all 'handlers' to 'steps'
This is a breaking change for the manual rule building APIs (server.addRule({ handler: new XYZ })) but not for the most common & standard chaining (server.forX().thenY()) rule definition APIs. Internally this includes backward compatibility on both client & server side, so should be safe to deploy either side independently, as long as only one-step rules are used. As yet there are no multi-step rules in place, so this is just scaffolding & future planning for now (watch this space...)
1 parent f37f78e commit 1da20d1

21 files changed

+421
-367
lines changed

src/admin/mockttp-schema.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,17 @@ export const MockttpSchema = gql`
4848
id: String
4949
priority: Int
5050
matchers: [Raw!]!
51-
handler: Raw!
51+
handler: Raw # Backward compat, deprecated
52+
steps: [Raw!] # Can only be unset if handler is set
5253
completionChecker: Raw
5354
}
5455
5556
input WebSocketMockRule {
5657
id: String
5758
priority: Int
5859
matchers: [Raw!]!
59-
handler: Raw!
60+
handler: Raw # Backward compat, deprecated
61+
steps: [Raw!] # Can only be unset if handler is set
6062
completionChecker: Raw
6163
}
6264

src/client/mockttp-admin-request-builder.ts

Lines changed: 16 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { MockedEndpointClient } from "./mocked-endpoint-client";
1717
import { AdminClient } from './admin-client';
1818
import { serializeRuleData } from '../rules/rule-serialization';
1919
import { deserializeBodyReader } from '../serialization/body-serialization';
20+
import { unreachableCheck } from '@httptoolkit/util';
2021

2122
function normalizeHttpMessage(message: any, event?: SubscribableEvent) {
2223
if (message.timingEvents) {
@@ -95,67 +96,30 @@ export class MockttpAdminRequestBuilder {
9596
this.messageBodyDecoding = options.messageBodyDecoding;
9697
}
9798

98-
buildAddRequestRulesQuery(
99-
rules: Array<RequestRuleData>,
99+
buildAddRulesQuery(
100+
type: 'http' | 'ws',
101+
rules: Array<RequestRuleData | WebSocketRuleData>,
100102
reset: boolean,
101103
adminStream: stream.Duplex
102104
): AdminQuery<
103105
{ endpoints: Array<{ id: string, explanation?: string }> },
104106
MockedEndpoint[]
105107
> {
106-
const requestName = (reset ? 'Set' : 'Add') + 'Rules';
107-
const mutationName = (reset ? 'set' : 'add') + 'Rules';
108-
109-
const serializedRules = rules.map((rule) => {
110-
const serializedRule = serializeRuleData(rule, adminStream)
111-
if (!this.schema.typeHasInputField('MockRule', 'id')) {
112-
delete serializedRule.id;
113-
}
114-
return serializedRule;
115-
});
116-
117-
return {
118-
query: gql`
119-
mutation ${requestName}($newRules: [MockRule!]!) {
120-
endpoints: ${mutationName}(input: $newRules) {
121-
id,
122-
${this.schema.asOptionalField('MockedEndpoint', 'explanation')}
123-
}
124-
}
125-
`,
126-
variables: {
127-
newRules: serializedRules
128-
},
129-
transformResponse: (response, { adminClient }) => {
130-
return response.endpoints.map(({ id, explanation }) =>
131-
new MockedEndpointClient(
132-
id,
133-
explanation,
134-
this.getEndpointDataGetter(adminClient, id)
135-
)
136-
)
137-
}
138-
};
139-
}
140-
141-
buildAddWebSocketRulesQuery(
142-
rules: Array<WebSocketRuleData>,
143-
reset: boolean,
144-
adminStream: stream.Duplex
145-
): AdminQuery<
146-
{ endpoints: Array<{ id: string, explanation?: string }> },
147-
MockedEndpoint[]
148-
> {
149-
// Seperate and simpler than buildAddRequestRulesQuery, because it doesn't have to
150-
// deal with backward compatibility.
151-
const requestName = (reset ? 'Set' : 'Add') + 'WebSocketRules';
152-
const mutationName = (reset ? 'set' : 'add') + 'WebSocketRules';
153-
154-
const serializedRules = rules.map((rule) => serializeRuleData(rule, adminStream));
108+
const ruleTypeName = type === 'http'
109+
? ''
110+
: type === 'ws'
111+
? 'WebSocket'
112+
: unreachableCheck(type);
113+
const requestName = (reset ? 'Set' : 'Add') + ruleTypeName + 'Rules';
114+
const mutationName = (reset ? 'set' : 'add') + ruleTypeName + 'Rules';
115+
116+
// Backward compatibility for old servers that don't support steps:
117+
const supportsSteps = this.schema.typeHasInputField('MockRule', 'steps');
118+
const serializedRules = rules.map((rule) => serializeRuleData(rule, adminStream, { supportsSteps }));
155119

156120
return {
157121
query: gql`
158-
mutation ${requestName}($newRules: [WebSocketMockRule!]!) {
122+
mutation ${requestName}($newRules: [${ruleTypeName}MockRule!]!) {
159123
endpoints: ${mutationName}(input: $newRules) {
160124
id,
161125
explanation

src/client/mockttp-client.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ export class MockttpClient extends AbstractMockttp implements Mockttp {
143143

144144
const { adminStream } = this.adminClient;
145145
return this.adminClient.sendQuery(
146-
this.requestBuilder.buildAddRequestRulesQuery(rules, reset, adminStream)
146+
this.requestBuilder.buildAddRulesQuery('http', rules, reset, adminStream)
147147
);
148148
}
149149

@@ -156,7 +156,7 @@ export class MockttpClient extends AbstractMockttp implements Mockttp {
156156
const { adminStream } = this.adminClient;
157157

158158
return this.adminClient.sendQuery(
159-
this.requestBuilder.buildAddWebSocketRulesQuery(rules, reset, adminStream)
159+
this.requestBuilder.buildAddRulesQuery('ws', rules, reset, adminStream)
160160
);
161161
}
162162

src/main.browser.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ export { Method, RulePriority } from "./types";
55

66
// Export rule data builders:
77
import * as matchers from './rules/matchers';
8-
import * as requestHandlerDefinitions from './rules/requests/request-handler-definitions';
9-
import * as webSocketHandlerDefinitions from './rules/websockets/websocket-handler-definitions';
8+
import * as requestStepDefinitions from './rules/requests/request-step-definitions';
9+
import * as webSocketStepDefinitions from './rules/websockets/websocket-step-definitions';
1010
import * as completionCheckers from './rules/completion-checkers';
1111

1212
export {
1313
matchers,
14-
requestHandlerDefinitions as requestHandlers,
15-
webSocketHandlerDefinitions as webSocketHandlers,
14+
requestStepDefinitions as requestSteps,
15+
webSocketStepDefinitions as webSocketSteps,
1616
completionCheckers
1717
};
1818

src/main.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,19 @@ export type {
2626

2727
// Export rule data builders & type definitions:
2828
import * as matchers from './rules/matchers';
29-
import type { RequestHandler, RequestHandlerOptions } from './rules/requests/request-handlers';
30-
import * as requestHandlerDefinitions from './rules/requests/request-handler-definitions';
31-
import type { WebSocketHandler } from './rules/websockets/websocket-handlers';
32-
import * as webSocketHandlerDefinitions from './rules/websockets/websocket-handler-definitions';
29+
import type { RequestStep, RequestStepOptions } from './rules/requests/request-steps';
30+
import * as requestStepDefinitions from './rules/requests/request-step-definitions';
31+
import type { WebSocketStep } from './rules/websockets/websocket-steps';
32+
import * as webSocketStepDefinitions from './rules/websockets/websocket-step-definitions';
3333
import * as completionCheckers from './rules/completion-checkers';
3434

3535
export {
3636
matchers,
37-
RequestHandler,
38-
RequestHandlerOptions,
39-
requestHandlerDefinitions as requestHandlers,
40-
WebSocketHandler,
41-
webSocketHandlerDefinitions as webSocketHandlers,
37+
RequestStep,
38+
RequestStepOptions,
39+
requestStepDefinitions as requestSteps,
40+
WebSocketStep,
41+
webSocketStepDefinitions as webSocketSteps,
4242
completionCheckers
4343
};
4444

@@ -57,7 +57,7 @@ export type {
5757
CADefinition,
5858
ForwardingOptions,
5959
PassThroughLookupOptions,
60-
PassThroughHandlerConnectionOptions
60+
PassThroughStepConnectionOptions
6161
} from './rules/passthrough-handling-definitions';
6262

6363
export type { RequestRuleBuilder } from "./rules/requests/request-rule-builder";

src/rules/base-rule-builder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { isString } from "lodash";
22
import { MaybePromise } from "../main";
33

4-
import { CompletedRequest, Method, RulePriority } from "../types";
4+
import { CompletedRequest, RulePriority } from "../types";
55

66
import {
77
RuleCompletionChecker,

src/rules/passthrough-handling-definitions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export type CADefinition =
3636
* This defines the upstream connection parameters. These passthrough parameters
3737
* are shared between both WebSocket & Request passthrough rules.
3838
*/
39-
export interface PassThroughHandlerConnectionOptions {
39+
export interface PassThroughStepConnectionOptions {
4040
/**
4141
* The forwarding configuration for the passthrough rule.
4242
* This generally shouldn't be used explicitly unless you're

src/rules/passthrough-handling.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import { getHeaderValue } from '../util/header-utils';
1919
import {
2020
CallbackRequestResult,
2121
CallbackResponseMessageResult
22-
} from './requests/request-handler-definitions';
23-
import { AbortError } from './requests/request-handlers';
22+
} from './requests/request-step-definitions';
23+
import { AbortError } from './requests/request-steps';
2424
import {
2525
CADefinition,
2626
PassThroughLookupOptions

src/rules/requests/request-rule-builder.ts

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@ import { Headers, CompletedRequest, Method, MockedEndpoint, Trailers } from "../
66
import type { RequestRuleData } from "./request-rule";
77

88
import {
9-
SimpleHandlerDefinition,
10-
PassThroughHandlerDefinition,
11-
CallbackHandlerDefinition,
9+
SimpleStepDefinition,
10+
PassThroughStepDefinition,
11+
CallbackStepDefinition,
1212
CallbackResponseResult,
13-
StreamHandlerDefinition,
14-
CloseConnectionHandlerDefinition,
15-
TimeoutHandlerDefinition,
16-
PassThroughHandlerOptions,
17-
FileHandlerDefinition,
18-
JsonRpcResponseHandlerDefinition,
19-
ResetConnectionHandlerDefinition,
13+
StreamStepDefinition,
14+
CloseConnectionStepDefinition,
15+
TimeoutStepDefinition,
16+
PassThroughStepOptions,
17+
FileStepDefinition,
18+
JsonRpcResponseStepDefinition,
19+
ResetConnectionStepDefinition,
2020
CallbackResponseMessageResult
21-
} from "./request-handler-definitions";
21+
} from "./request-step-definitions";
2222
import { byteLength } from "../../util/util";
2323
import { BaseRuleBuilder } from "../base-rule-builder";
2424
import { MethodMatcher, RegexPathMatcher, SimplePathMatcher, WildcardMatcher } from "../matchers";
@@ -141,13 +141,13 @@ export class RequestRuleBuilder extends BaseRuleBuilder {
141141

142142
const rule: RequestRuleData = {
143143
...this.buildBaseRuleData(),
144-
handler: new SimpleHandlerDefinition(
144+
steps: [new SimpleStepDefinition(
145145
status,
146146
statusMessage,
147147
data,
148148
headers,
149149
trailers
150-
)
150+
)]
151151
};
152152

153153
return this.addRule(rule);
@@ -184,7 +184,7 @@ export class RequestRuleBuilder extends BaseRuleBuilder {
184184

185185
const rule: RequestRuleData = {
186186
...this.buildBaseRuleData(),
187-
handler: new SimpleHandlerDefinition(status, undefined, jsonData, headers)
187+
steps: [new SimpleStepDefinition(status, undefined, jsonData, headers)]
188188
};
189189

190190
return this.addRule(rule);
@@ -218,7 +218,7 @@ export class RequestRuleBuilder extends BaseRuleBuilder {
218218
): Promise<MockedEndpoint> {
219219
const rule: RequestRuleData = {
220220
...this.buildBaseRuleData(),
221-
handler: new CallbackHandlerDefinition(callback)
221+
steps: [new CallbackStepDefinition(callback)]
222222
}
223223

224224
return this.addRule(rule);
@@ -247,7 +247,7 @@ export class RequestRuleBuilder extends BaseRuleBuilder {
247247
thenStream(status: number, stream: Readable, headers?: Headers): Promise<MockedEndpoint> {
248248
const rule: RequestRuleData = {
249249
...this.buildBaseRuleData(),
250-
handler: new StreamHandlerDefinition(status, stream, headers)
250+
steps: [new StreamStepDefinition(status, stream, headers)]
251251
}
252252

253253
return this.addRule(rule);
@@ -296,7 +296,7 @@ export class RequestRuleBuilder extends BaseRuleBuilder {
296296

297297
const rule: RequestRuleData = {
298298
...this.buildBaseRuleData(),
299-
handler: new FileHandlerDefinition(status, statusMessage, path, headers)
299+
steps: [new FileStepDefinition(status, statusMessage, path, headers)]
300300
};
301301

302302
return this.addRule(rule);
@@ -308,7 +308,7 @@ export class RequestRuleBuilder extends BaseRuleBuilder {
308308
* an error.
309309
*
310310
* This method takes options to configure how the request is passed
311-
* through. See {@link PassThroughHandlerOptions} for the full details
311+
* through. See {@link PassThroughStepOptions} for the full details
312312
* of the options available.
313313
*
314314
* Calling this method registers the rule with the server, so it
@@ -321,10 +321,10 @@ export class RequestRuleBuilder extends BaseRuleBuilder {
321321
*
322322
* @category Responses
323323
*/
324-
thenPassThrough(options?: PassThroughHandlerOptions): Promise<MockedEndpoint> {
324+
thenPassThrough(options?: PassThroughStepOptions): Promise<MockedEndpoint> {
325325
const rule: RequestRuleData = {
326326
...this.buildBaseRuleData(),
327-
handler: new PassThroughHandlerDefinition(options)
327+
steps: [new PassThroughStepDefinition(options)]
328328
};
329329

330330
return this.addRule(rule);
@@ -341,7 +341,7 @@ export class RequestRuleBuilder extends BaseRuleBuilder {
341341
* of the original request URL will be used instead.
342342
*
343343
* This method takes options to configure how the request is passed
344-
* through. See {@link PassThroughHandlerOptions} for the full details
344+
* through. See {@link PassThroughStepOptions} for the full details
345345
* of the options available.
346346
*
347347
* Calling this method registers the rule with the server, so it
@@ -356,19 +356,19 @@ export class RequestRuleBuilder extends BaseRuleBuilder {
356356
*/
357357
async thenForwardTo(
358358
forwardToLocation: string,
359-
options: Omit<PassThroughHandlerOptions, 'forwarding'> & {
360-
forwarding?: Omit<PassThroughHandlerOptions['forwarding'], 'targetHost'>
359+
options: Omit<PassThroughStepOptions, 'forwarding'> & {
360+
forwarding?: Omit<PassThroughStepOptions['forwarding'], 'targetHost'>
361361
} = {}
362362
): Promise<MockedEndpoint> {
363363
const rule: RequestRuleData = {
364364
...this.buildBaseRuleData(),
365-
handler: new PassThroughHandlerDefinition({
365+
steps: [new PassThroughStepDefinition({
366366
...options,
367367
forwarding: {
368368
...options.forwarding,
369369
targetHost: forwardToLocation
370370
}
371-
})
371+
})]
372372
};
373373

374374
return this.addRule(rule);
@@ -391,7 +391,7 @@ export class RequestRuleBuilder extends BaseRuleBuilder {
391391
thenCloseConnection(): Promise<MockedEndpoint> {
392392
const rule: RequestRuleData = {
393393
...this.buildBaseRuleData(),
394-
handler: new CloseConnectionHandlerDefinition()
394+
steps: [new CloseConnectionStepDefinition()]
395395
};
396396

397397
return this.addRule(rule);
@@ -418,7 +418,7 @@ export class RequestRuleBuilder extends BaseRuleBuilder {
418418
thenResetConnection(): Promise<MockedEndpoint> {
419419
const rule: RequestRuleData = {
420420
...this.buildBaseRuleData(),
421-
handler: new ResetConnectionHandlerDefinition()
421+
steps: [new ResetConnectionStepDefinition()]
422422
};
423423

424424
return this.addRule(rule);
@@ -441,7 +441,7 @@ export class RequestRuleBuilder extends BaseRuleBuilder {
441441
thenTimeout(): Promise<MockedEndpoint> {
442442
const rule: RequestRuleData = {
443443
...this.buildBaseRuleData(),
444-
handler: new TimeoutHandlerDefinition()
444+
steps: [new TimeoutStepDefinition()]
445445
};
446446

447447
return this.addRule(rule);
@@ -457,7 +457,7 @@ export class RequestRuleBuilder extends BaseRuleBuilder {
457457
thenSendJsonRpcResult(result: any) {
458458
const rule = {
459459
...this.buildBaseRuleData(),
460-
handler: new JsonRpcResponseHandlerDefinition({ result })
460+
steps: [new JsonRpcResponseStepDefinition({ result })]
461461
};
462462

463463
return this.addRule(rule);
@@ -473,7 +473,7 @@ export class RequestRuleBuilder extends BaseRuleBuilder {
473473
thenSendJsonRpcError(error: any) {
474474
const rule = {
475475
...this.buildBaseRuleData(),
476-
handler: new JsonRpcResponseHandlerDefinition({ error })
476+
steps: [new JsonRpcResponseStepDefinition({ error })]
477477
};
478478

479479
return this.addRule(rule);

0 commit comments

Comments
 (0)