@@ -3,21 +3,29 @@ import { Duplex } from "stream";
33
44import { PubSub } from "graphql-subscriptions" ;
55import type { IResolvers } from "@graphql-tools/utils" ;
6+ import { ErrorLike , UnreachableCheck } from "@httptoolkit/util" ;
67
8+ import type { Headers } from '../types' ;
79import type { MockttpServer } from "../server/mockttp-server" ;
810import type { ServerMockedEndpoint } from "../server/mocked-endpoint" ;
911import type {
1012 MockedEndpoint ,
1113 MockedEndpointData ,
1214 CompletedRequest ,
1315 CompletedResponse ,
14- ClientError
16+ ClientError ,
17+ CompletedBody
1518} from "../types" ;
1619import type { Serialized } from "../serialization/serialization" ;
1720import type { RequestRuleData } from "../rules/requests/request-rule" ;
1821import type { WebSocketRuleData } from "../rules/websockets/websocket-rule" ;
1922
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" ;
2129import { SubscribableEvent } from "../main" ;
2230
2331const graphqlSubscriptionPairs = Object . entries ( {
@@ -49,12 +57,51 @@ async function buildMockedEndpointData(endpoint: ServerMockedEndpoint): Promise<
4957 } ;
5058}
5159
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+
5276export function buildAdminServerModel (
5377 mockServer : MockttpServer ,
5478 stream : Duplex ,
55- ruleParameters : { [ key : string ] : any }
79+ ruleParams : { [ key : string ] : any } ,
80+ options : {
81+ messageBodyDecoding ?: 'server-side' | 'none' ;
82+ } = { }
5683) : IResolvers {
5784 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+ } ;
58105
59106 for ( let [ gqlName , eventName ] of graphqlSubscriptionPairs ) {
60107 mockServer . on ( eventName as any , ( evt ) => {
@@ -91,30 +138,30 @@ export function buildAdminServerModel(
91138
92139 Mutation : {
93140 addRule : async ( __ : any , { input } : { input : Serialized < RequestRuleData > } ) => {
94- return mockServer . addRequestRule ( deserializeRuleData ( input , stream , ruleParameters ) ) ;
141+ return mockServer . addRequestRule ( deserializeRuleData ( input , stream , ruleDeserializationOptions ) ) ;
95142 } ,
96143 addRules : async ( __ : any , { input } : { input : Array < Serialized < RequestRuleData > > } ) => {
97144 return mockServer . addRequestRules ( ...input . map ( ( rule ) =>
98- deserializeRuleData ( rule , stream , ruleParameters )
145+ deserializeRuleData ( rule , stream , ruleDeserializationOptions )
99146 ) ) ;
100147 } ,
101148 setRules : async ( __ : any , { input } : { input : Array < Serialized < RequestRuleData > > } ) => {
102149 return mockServer . setRequestRules ( ...input . map ( ( rule ) =>
103- deserializeRuleData ( rule , stream , ruleParameters )
150+ deserializeRuleData ( rule , stream , ruleDeserializationOptions )
104151 ) ) ;
105152 } ,
106153
107154 addWebSocketRule : async ( __ : any , { input } : { input : Serialized < WebSocketRuleData > } ) => {
108- return mockServer . addWebSocketRule ( deserializeWebSocketRuleData ( input , stream , ruleParameters ) ) ;
155+ return mockServer . addWebSocketRule ( deserializeWebSocketRuleData ( input , stream , ruleDeserializationOptions ) ) ;
109156 } ,
110157 addWebSocketRules : async ( __ : any , { input } : { input : Array < Serialized < WebSocketRuleData > > } ) => {
111158 return mockServer . addWebSocketRules ( ...input . map ( ( rule ) =>
112- deserializeWebSocketRuleData ( rule , stream , ruleParameters )
159+ deserializeWebSocketRuleData ( rule , stream , ruleDeserializationOptions )
113160 ) ) ;
114161 } ,
115162 setWebSocketRules : async ( __ : any , { input } : { input : Array < Serialized < WebSocketRuleData > > } ) => {
116163 return mockServer . setWebSocketRules ( ...input . map ( ( rule ) =>
117- deserializeWebSocketRuleData ( rule , stream , ruleParameters )
164+ deserializeWebSocketRuleData ( rule , stream , ruleDeserializationOptions )
118165 ) ) ;
119166 }
120167 } ,
@@ -124,12 +171,20 @@ export function buildAdminServerModel(
124171 Request : {
125172 body : ( request : CompletedRequest ) => {
126173 return request . body . buffer ;
174+ } ,
175+ decodedBody : async ( request : CompletedRequest ) => {
176+ return ( await decodeAndSerializeBody ( request . body , request . headers ) )
177+ || { } ; // No decoding required
127178 }
128179 } ,
129180
130181 Response : {
131182 body : ( response : CompletedResponse ) => {
132183 return response . body . buffer ;
184+ } ,
185+ decodedBody : async ( response : CompletedResponse ) => {
186+ return ( await decodeAndSerializeBody ( response . body , response . headers ) )
187+ || { } ; // No decoding required
133188 }
134189 } ,
135190
0 commit comments