@@ -3,21 +3,29 @@ import { Duplex } from "stream";
3
3
4
4
import { PubSub } from "graphql-subscriptions" ;
5
5
import type { IResolvers } from "@graphql-tools/utils" ;
6
+ import { ErrorLike , UnreachableCheck } from "@httptoolkit/util" ;
6
7
8
+ import type { Headers } from '../types' ;
7
9
import type { MockttpServer } from "../server/mockttp-server" ;
8
10
import type { ServerMockedEndpoint } from "../server/mocked-endpoint" ;
9
11
import type {
10
12
MockedEndpoint ,
11
13
MockedEndpointData ,
12
14
CompletedRequest ,
13
15
CompletedResponse ,
14
- ClientError
16
+ ClientError ,
17
+ CompletedBody
15
18
} from "../types" ;
16
19
import type { Serialized } from "../serialization/serialization" ;
17
20
import type { RequestRuleData } from "../rules/requests/request-rule" ;
18
21
import type { WebSocketRuleData } from "../rules/websockets/websocket-rule" ;
19
22
20
- import { deserializeRuleData , deserializeWebSocketRuleData } from "../rules/rule-deserialization" ;
23
+ import {
24
+ deserializeRuleData ,
25
+ deserializeWebSocketRuleData ,
26
+ MockttpDeserializationOptions
27
+ } from "../rules/rule-deserialization" ;
28
+ import { decodeBodyBuffer } from "../util/request-utils" ;
21
29
import { SubscribableEvent } from "../main" ;
22
30
23
31
const graphqlSubscriptionPairs = Object . entries ( {
@@ -49,12 +57,51 @@ async function buildMockedEndpointData(endpoint: ServerMockedEndpoint): Promise<
49
57
} ;
50
58
}
51
59
60
+ const decodeAndSerializeBody = async ( body : CompletedBody , headers : Headers ) : Promise <
61
+ | false // Not required
62
+ | { decoded : Buffer , decodingError ?: undefined } // Success
63
+ | { decodingError : string , decoded ?: undefined } // Failure
64
+ > => {
65
+ try {
66
+ const decoded = await decodeBodyBuffer ( body . buffer , headers ) ;
67
+ if ( decoded === body . buffer ) return false ; // No decoding required - no-op.
68
+ else return { decoded } ; // Successful decoding result
69
+ } catch ( e ) {
70
+ return { // Failed decoding - we just return the error message.
71
+ decodingError : ( e as ErrorLike ) ?. message ?? 'Failed to decode message body'
72
+ } ;
73
+ }
74
+ } ;
75
+
52
76
export function buildAdminServerModel (
53
77
mockServer : MockttpServer ,
54
78
stream : Duplex ,
55
- ruleParameters : { [ key : string ] : any }
79
+ ruleParams : { [ key : string ] : any } ,
80
+ options : {
81
+ messageBodyDecoding ?: 'server-side' | 'none' ;
82
+ } = { }
56
83
) : IResolvers {
57
84
const pubsub = new PubSub ( ) ;
85
+ const messageBodyDecoding = options . messageBodyDecoding || 'server-side' ;
86
+
87
+ const ruleDeserializationOptions : MockttpDeserializationOptions = {
88
+ bodySerializer : messageBodyDecoding === 'server-side'
89
+ ? async ( body , headers ) => {
90
+ const encoded = body . buffer . toString ( 'base64' ) ;
91
+ const result = await decodeAndSerializeBody ( body , headers ) ;
92
+ if ( result === false ) { // No decoding required - no-op.
93
+ return { encoded } ;
94
+ } else if ( result . decodingError !== undefined ) { // Failed decoding - we just return the error message.
95
+ return { encoded, decodingError : result . decodingError } ;
96
+ } else if ( result . decoded ) { // Success - we return both formats to the client
97
+ return { encoded, decoded : result . decoded . toString ( 'base64' ) } ;
98
+ } else {
99
+ throw new UnreachableCheck ( result ) ;
100
+ }
101
+ }
102
+ : ( body ) => body . buffer . toString ( 'base64' ) , // 'None' = just send encoded body (as base64).
103
+ ruleParams
104
+ } ;
58
105
59
106
for ( let [ gqlName , eventName ] of graphqlSubscriptionPairs ) {
60
107
mockServer . on ( eventName as any , ( evt ) => {
@@ -91,30 +138,30 @@ export function buildAdminServerModel(
91
138
92
139
Mutation : {
93
140
addRule : async ( __ : any , { input } : { input : Serialized < RequestRuleData > } ) => {
94
- return mockServer . addRequestRule ( deserializeRuleData ( input , stream , ruleParameters ) ) ;
141
+ return mockServer . addRequestRule ( deserializeRuleData ( input , stream , ruleDeserializationOptions ) ) ;
95
142
} ,
96
143
addRules : async ( __ : any , { input } : { input : Array < Serialized < RequestRuleData > > } ) => {
97
144
return mockServer . addRequestRules ( ...input . map ( ( rule ) =>
98
- deserializeRuleData ( rule , stream , ruleParameters )
145
+ deserializeRuleData ( rule , stream , ruleDeserializationOptions )
99
146
) ) ;
100
147
} ,
101
148
setRules : async ( __ : any , { input } : { input : Array < Serialized < RequestRuleData > > } ) => {
102
149
return mockServer . setRequestRules ( ...input . map ( ( rule ) =>
103
- deserializeRuleData ( rule , stream , ruleParameters )
150
+ deserializeRuleData ( rule , stream , ruleDeserializationOptions )
104
151
) ) ;
105
152
} ,
106
153
107
154
addWebSocketRule : async ( __ : any , { input } : { input : Serialized < WebSocketRuleData > } ) => {
108
- return mockServer . addWebSocketRule ( deserializeWebSocketRuleData ( input , stream , ruleParameters ) ) ;
155
+ return mockServer . addWebSocketRule ( deserializeWebSocketRuleData ( input , stream , ruleDeserializationOptions ) ) ;
109
156
} ,
110
157
addWebSocketRules : async ( __ : any , { input } : { input : Array < Serialized < WebSocketRuleData > > } ) => {
111
158
return mockServer . addWebSocketRules ( ...input . map ( ( rule ) =>
112
- deserializeWebSocketRuleData ( rule , stream , ruleParameters )
159
+ deserializeWebSocketRuleData ( rule , stream , ruleDeserializationOptions )
113
160
) ) ;
114
161
} ,
115
162
setWebSocketRules : async ( __ : any , { input } : { input : Array < Serialized < WebSocketRuleData > > } ) => {
116
163
return mockServer . setWebSocketRules ( ...input . map ( ( rule ) =>
117
- deserializeWebSocketRuleData ( rule , stream , ruleParameters )
164
+ deserializeWebSocketRuleData ( rule , stream , ruleDeserializationOptions )
118
165
) ) ;
119
166
}
120
167
} ,
@@ -124,12 +171,20 @@ export function buildAdminServerModel(
124
171
Request : {
125
172
body : ( request : CompletedRequest ) => {
126
173
return request . body . buffer ;
174
+ } ,
175
+ decodedBody : async ( request : CompletedRequest ) => {
176
+ return ( await decodeAndSerializeBody ( request . body , request . headers ) )
177
+ || { } ; // No decoding required
127
178
}
128
179
} ,
129
180
130
181
Response : {
131
182
body : ( response : CompletedResponse ) => {
132
183
return response . body . buffer ;
184
+ } ,
185
+ decodedBody : async ( response : CompletedResponse ) => {
186
+ return ( await decodeAndSerializeBody ( response . body , response . headers ) )
187
+ || { } ; // No decoding required
133
188
}
134
189
} ,
135
190
0 commit comments