@@ -1117,8 +1117,9 @@ class BodyTransformConfig<T extends RequestTransform | ResponseTransform> extend
11171117 'replaceBody' ,
11181118 'replaceBodyFromFile' ,
11191119 'updateJsonBody' ,
1120- 'patchJsonBody'
1121- ] as const ;
1120+ 'patchJsonBody' ,
1121+ 'matchReplaceBody'
1122+ ] as const satisfies ReadonlyArray < keyof RequestTransform & ResponseTransform > ;
11221123
11231124 @computed
11241125 get bodyReplacementBuffer ( ) {
@@ -1152,11 +1153,12 @@ class BodyTransformConfig<T extends RequestTransform | ResponseTransform> extend
11521153 < option value = 'updateJsonBody' > Update a JSON { type } body by merging data</ option >
11531154 { advancedPatchesSupported && < >
11541155 < option value = 'patchJsonBody' > Update a JSON { type } body using JSON patch</ option >
1156+ < option value = 'matchReplaceBody' > Match & replace text in the { type } body</ option >
11551157 </ > }
11561158 </ SelectTransform >
11571159 {
11581160 selected === 'replaceBody'
1159- ? < RawBodyTransfomConfig
1161+ ? < RawBodyTransformConfig
11601162 type = { type }
11611163 body = { bodyReplacementBuffer }
11621164 updateBody = { setBodyReplacement }
@@ -1189,6 +1191,12 @@ class BodyTransformConfig<T extends RequestTransform | ResponseTransform> extend
11891191 operations = { transform . patchJsonBody ! }
11901192 updateOperations = { setJsonBodyPatch }
11911193 />
1194+ : selected === 'matchReplaceBody'
1195+ ? < MatchReplaceBodyTransformConfig
1196+ type = { type }
1197+ replacements = { transform . matchReplaceBody ! }
1198+ updateReplacements = { this . props . onChange ( 'matchReplaceBody' ) }
1199+ />
11921200 : selected === 'none'
11931201 ? null
11941202 : unreachableCheck ( selected )
@@ -1208,6 +1216,8 @@ class BodyTransformConfig<T extends RequestTransform | ResponseTransform> extend
12081216 this . props . onChange ( 'replaceBody' ) ( '' ) ;
12091217 } else if ( value === 'replaceBodyFromFile' ) {
12101218 this . props . onChange ( 'replaceBodyFromFile' ) ( '' ) ;
1219+ } else if ( value === 'matchReplaceBody' ) {
1220+ this . props . onChange ( 'matchReplaceBody' ) ( [ ] ) ;
12111221 } else if ( value === 'none' ) {
12121222 return ;
12131223 } else unreachableCheck ( value ) ;
@@ -1249,7 +1259,7 @@ class BodyTransformConfig<T extends RequestTransform | ResponseTransform> extend
12491259 } ;
12501260} ;
12511261
1252- const RawBodyTransfomConfig = ( props : {
1262+ const RawBodyTransformConfig = ( props : {
12531263 type : 'request' | 'response' ,
12541264 body : Buffer ,
12551265 updateBody : ( body : string ) => void
@@ -1381,6 +1391,74 @@ const JsonPatchTransformConfig = (props: {
13811391 </ TransformDetails > ;
13821392} ;
13831393
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+
13841462@observer
13851463class PassThroughHandlerConfig extends HandlerConfig <
13861464 | PassThroughHandler
@@ -1583,7 +1661,7 @@ class EthCallResultHandlerConfig extends HandlerConfig<EthereumCallResultHandler
15831661 pairs = { typeValuePairs }
15841662 onChange = { this . onChange }
15851663 keyPlaceholder = 'Return value type (e.g. string, int256, etc)'
1586- keyPattern = { NATIVE_ETH_TYPES_PATTERN }
1664+ keyValidation = { NATIVE_ETH_TYPES_PATTERN }
15871665 valuePlaceholder = 'Return value'
15881666 allowEmptyValues = { true }
15891667 />
0 commit comments