@@ -1117,8 +1117,9 @@ class BodyTransformConfig<T extends RequestTransform | ResponseTransform> extend
1117
1117
'replaceBody' ,
1118
1118
'replaceBodyFromFile' ,
1119
1119
'updateJsonBody' ,
1120
- 'patchJsonBody'
1121
- ] as const ;
1120
+ 'patchJsonBody' ,
1121
+ 'matchReplaceBody'
1122
+ ] as const satisfies ReadonlyArray < keyof RequestTransform & ResponseTransform > ;
1122
1123
1123
1124
@computed
1124
1125
get bodyReplacementBuffer ( ) {
@@ -1152,11 +1153,12 @@ class BodyTransformConfig<T extends RequestTransform | ResponseTransform> extend
1152
1153
< option value = 'updateJsonBody' > Update a JSON { type } body by merging data</ option >
1153
1154
{ advancedPatchesSupported && < >
1154
1155
< option value = 'patchJsonBody' > Update a JSON { type } body using JSON patch</ option >
1156
+ < option value = 'matchReplaceBody' > Match & replace text in the { type } body</ option >
1155
1157
</ > }
1156
1158
</ SelectTransform >
1157
1159
{
1158
1160
selected === 'replaceBody'
1159
- ? < RawBodyTransfomConfig
1161
+ ? < RawBodyTransformConfig
1160
1162
type = { type }
1161
1163
body = { bodyReplacementBuffer }
1162
1164
updateBody = { setBodyReplacement }
@@ -1189,6 +1191,12 @@ class BodyTransformConfig<T extends RequestTransform | ResponseTransform> extend
1189
1191
operations = { transform . patchJsonBody ! }
1190
1192
updateOperations = { setJsonBodyPatch }
1191
1193
/>
1194
+ : selected === 'matchReplaceBody'
1195
+ ? < MatchReplaceBodyTransformConfig
1196
+ type = { type }
1197
+ replacements = { transform . matchReplaceBody ! }
1198
+ updateReplacements = { this . props . onChange ( 'matchReplaceBody' ) }
1199
+ />
1192
1200
: selected === 'none'
1193
1201
? null
1194
1202
: unreachableCheck ( selected )
@@ -1208,6 +1216,8 @@ class BodyTransformConfig<T extends RequestTransform | ResponseTransform> extend
1208
1216
this . props . onChange ( 'replaceBody' ) ( '' ) ;
1209
1217
} else if ( value === 'replaceBodyFromFile' ) {
1210
1218
this . props . onChange ( 'replaceBodyFromFile' ) ( '' ) ;
1219
+ } else if ( value === 'matchReplaceBody' ) {
1220
+ this . props . onChange ( 'matchReplaceBody' ) ( [ ] ) ;
1211
1221
} else if ( value === 'none' ) {
1212
1222
return ;
1213
1223
} else unreachableCheck ( value ) ;
@@ -1249,7 +1259,7 @@ class BodyTransformConfig<T extends RequestTransform | ResponseTransform> extend
1249
1259
} ;
1250
1260
} ;
1251
1261
1252
- const RawBodyTransfomConfig = ( props : {
1262
+ const RawBodyTransformConfig = ( props : {
1253
1263
type : 'request' | 'response' ,
1254
1264
body : Buffer ,
1255
1265
updateBody : ( body : string ) => void
@@ -1381,6 +1391,74 @@ const JsonPatchTransformConfig = (props: {
1381
1391
</ TransformDetails > ;
1382
1392
} ;
1383
1393
1394
+ const MatchReplaceBodyTransformConfig = ( props : {
1395
+ type : 'request' | 'response' ,
1396
+ replacements : Array < [ RegExp | string , string ] > ,
1397
+ updateReplacements : ( replacements : Array < [ RegExp | string , string ] > ) => void
1398
+ } ) => {
1399
+ const [ error , setError ] = React . useState < Error > ( ) ;
1400
+
1401
+ const [ replacementPairs , updatePairs ] = React . useState < PairsArray > (
1402
+ props . replacements . map ( ( [ match , replace ] ) => ( {
1403
+ key : match instanceof RegExp
1404
+ ? match . source
1405
+ // It's type-possible to get a string here (since Mockttp supports it)
1406
+ // but it shouldn't be runtime-possible as we always use regex
1407
+ : _ . escapeRegExp ( match ) ,
1408
+ value : replace
1409
+ } ) )
1410
+ ) ;
1411
+
1412
+ React . useEffect ( ( ) => {
1413
+ const validPairs = replacementPairs . filter ( ( pair ) =>
1414
+ validateRegexMatcher ( pair . key ) === true
1415
+ ) ;
1416
+ const invalidCount = replacementPairs . length - validPairs . length ;
1417
+
1418
+ if ( invalidCount > 0 ) {
1419
+ setError ( new Error (
1420
+ `${ invalidCount } regular expression${ invalidCount === 1 ? ' is' : 's are' } invalid`
1421
+ ) ) ;
1422
+ } else {
1423
+ setError ( undefined ) ;
1424
+ }
1425
+
1426
+ props . updateReplacements ( validPairs . map ( ( { key, value } ) =>
1427
+ [ new RegExp ( key , 'g' ) , value ]
1428
+ ) ) ;
1429
+ } , [ replacementPairs ] ) ;
1430
+
1431
+ return < TransformDetails >
1432
+ < BodyHeader >
1433
+ < SectionLabel > Regex matchers & replacements </ SectionLabel >
1434
+ { error && < WarningIcon title = { error . message } /> }
1435
+ </ BodyHeader >
1436
+ < MonoKeyEditablePairs
1437
+ pairs = { replacementPairs }
1438
+ onChange = { updatePairs }
1439
+ keyPlaceholder = 'Regular expression to match'
1440
+ keyValidation = { validateRegexMatcher }
1441
+ valuePlaceholder = 'Replacement value'
1442
+ allowEmptyValues = { true }
1443
+ />
1444
+ </ TransformDetails > ;
1445
+ } ;
1446
+
1447
+ const MonoKeyEditablePairs = styled ( EditablePairs < PairsArray > ) `
1448
+ input:nth-of-type(odd) {
1449
+ font-family: ${ p => p . theme . monoFontFamily } ;
1450
+ }
1451
+ ` ;
1452
+
1453
+ const validateRegexMatcher = ( value : string ) => {
1454
+ try {
1455
+ new RegExp ( value , 'g' ) ;
1456
+ return true ;
1457
+ } catch ( e : any ) {
1458
+ return e . message ?? e . toString ( ) ;
1459
+ }
1460
+ }
1461
+
1384
1462
@observer
1385
1463
class PassThroughHandlerConfig extends HandlerConfig <
1386
1464
| PassThroughHandler
@@ -1583,7 +1661,7 @@ class EthCallResultHandlerConfig extends HandlerConfig<EthereumCallResultHandler
1583
1661
pairs = { typeValuePairs }
1584
1662
onChange = { this . onChange }
1585
1663
keyPlaceholder = 'Return value type (e.g. string, int256, etc)'
1586
- keyPattern = { NATIVE_ETH_TYPES_PATTERN }
1664
+ keyValidation = { NATIVE_ETH_TYPES_PATTERN }
1587
1665
valuePlaceholder = 'Return value'
1588
1666
allowEmptyValues = { true }
1589
1667
/>
0 commit comments