Skip to content

Commit 79d9ba3

Browse files
authored
feat: adding array and regex support to deleting a state (#98)
- add `contexts` parameter to `deleteState` - add `contextsMatching` parameter to `deleteState` - cleaned up `DeleteStateEventListenerTest` - fixed display issues in README <!-- Please describe your pull request here. --> ## References #68 <!-- References to relevant GitHub issues and pull requests, esp. upstream and downstream changes --> ## Submitter checklist - [ ] Recommended: Join [WireMock Slack](https://slack.wiremock.org/) to get any help in `#help-contributing` or a project-specific channel like `#wiremock-java` - [ ] Recommended: If you participate in Hacktoberfest 2023, make sure you're [signed up](https://wiremock.org/events/hacktoberfest/) there and in the WireMock form - [ ] The PR request is well described and justified, including the body and the references - [ ] The PR title represents the desired changelog entry - [ ] The repository's code style is followed (see the contributing guide) - [ ] Test coverage that demonstrates that the change works as expected - [ ] For new features, there's necessary documentation in this pull request or in a subsequent PR to [wiremock.org](https://github.com/wiremock/wiremock.org) <!-- Put an `x` into the [ ] to show you have filled the information. The template comes from https://github.com/wiremock/.github/blob/main/.github/pull_request_template.md You can override it by creating .github/pull_request_template.md in your own repository -->
1 parent 14384c0 commit 79d9ba3

File tree

6 files changed

+985
-328
lines changed

6 files changed

+985
-328
lines changed

README.md

Lines changed: 60 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# WireMock State extension
22

33
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/wiremock/wiremock-state-extension)](https://github.com/wiremock/wiremock-state-extension/releases)
4+
[![Maven Central](https://img.shields.io/maven-central/v/org.wiremock.extensions/wiremock-state-extension)](https://img.shields.io/maven-central/v/org.wiremock.extensions/wiremock-state-extension)
45
[![Slack](https://img.shields.io/badge/slack-slack.wiremock.org-brightgreen?style=flat&logo=slack)](https://slack.wiremock.org/)
56
[![GitHub contributors](https://img.shields.io/github/contributors/wiremock/wiremock-state-extension)](https://github.com/wiremock/wiremock-state-extension/graphs/contributors)
67

@@ -124,48 +125,41 @@ the `GET` won't have any knowledge of the previous post.
124125
{
125126
"firstName": "Jane",
126127
"lastName": "Doe"
127-
128-
}
129-
128+
}
130129
```
131130

132131
- Response:
133132

134133
```json
135-
{
136-
"id": "54owywgurlqepq1wc5xvyc2hipe8xp4u",
137-
#
138-
Random
139-
value
140-
"firstName": "Jane",
141-
"lastName": "Doe"
142-
}
134+
{
135+
"id": "54owywgurlqepq1wc5xvyc2hipe8xp4u", # Random value
136+
"firstName": "Jane",
137+
"lastName": "Doe"
138+
}
143139
```
144140

145141
3. `GET` to retrieve the first value (`GET /queue`)
146142

147143
- Response:
148144

149145
```json
150-
{
146+
{
151147
"id": "kn0ixsaswzrzcfzriytrdupnjnxor1is",
152148
"firstName": "John",
153149
"lastName": "Doe"
154-
155-
}
156-
150+
}
157151
```
158152

159153
4. `GET` to retrieve the second value (`GET /queue`)
160154

161155
- Response:
162156

163157
```json
164-
{
158+
{
165159
"id": "54owywgurlqepq1wc5xvyc2hipe8xp4u",
166160
"firstName": "Jane",
167161
"lastName": "Doe"
168-
}
162+
}
169163
```
170164

171165
# Usage
@@ -288,7 +282,7 @@ storing any data.
288282
The standalone jar can be downloaded from [GitHub](https://github.com/wiremock/wiremock-extension-state/packages/1902576) .
289283

290284
```bash
291-
java -cp "wiremock-state-extension-standalone-0.0.5.jar:wiremock-standalone-3.0.0-beta-11.jar" wiremock.Run
285+
java -cp "wiremock-state-extension-standalone-0.4.0.jar:wiremock-standalone-3.3.0.jar" wiremock.Run
292286
```
293287

294288
### Docker
@@ -303,7 +297,7 @@ docker run -it --rm \
303297
-p 8080:8080 \
304298
--name wiremock \
305299
-v $PWD/extensions:/var/wiremock/extensions \
306-
wiremock/wiremock:3x \
300+
wiremock/wiremock:3.3.0 \
307301
-- --global-response-templating
308302
```
309303

@@ -349,7 +343,7 @@ The following parameters have to be provided:
349343
<td>
350344

351345
```json
352-
{
346+
{
353347
"id": "{{jsonPath response.body '$.id'}}",
354348
"firstName": "{{jsonPath request.body '$.firstName'}}",
355349
"lastName": "{{jsonPath request.body '$.lastName'}}"
@@ -374,7 +368,7 @@ Dictionary
374368
<td>
375369

376370
```json
377-
{
371+
{
378372
"addLast": {
379373
"id": "{{jsonPath response.body '$.id'}}",
380374
"firstName": "{{jsonPath request.body '$.firstName'}}",
@@ -529,14 +523,19 @@ The following parameters have to be provided:
529523

530524
<table>
531525
<tr>
526+
<th>Task</th>
532527
<th>Parameter</th>
533528
<th>Type</th>
534529
<th>Example</th>
535530
</tr>
536531
<tr>
532+
<td rowspan="3">
533+
context deletion
534+
</td>
537535
<td>
538536

539-
`context`
537+
`context`<br>
538+
Deletes a single context.
540539

541540
</td>
542541
<td>String</td>
@@ -550,9 +549,45 @@ The following parameters have to be provided:
550549
<tr>
551550
<td>
552551

553-
`list` (Optional) <br>
554-
When list is provided, only the specified list element is deleted. If `list` is not provided, the whole context is deleted.
552+
`contexts`
553+
Deletes all contexts specified in the array.
554+
555+
</td>
556+
<td>Array<br>
557+
An empty array or unknown contexts are silently ignored.
558+
</td>
559+
<td>
560+
561+
- `"contexts": ["{{jsonPath response.body '$.firstContext'}}", "{{jsonPath response.body '$.secondContext'}}"]`
562+
- `"contexts": ["a", "b", "c"]`
563+
564+
</td>
565+
</tr>
566+
<tr>
567+
<td>
568+
569+
`contextsMatching`
570+
Deletes all contexts matching the regex.
571+
</td>
572+
<td>String (regex)<br>
573+
An invalid regex results in an exception. If there are no matches, this is silently ignored.
574+
</td>
575+
<td>
576+
577+
- `"contextsMatching": ".*userNa.*"`
578+
- `"contextsMatching": ".*(john|jane).*"`
579+
- `"contextsMatching": ".*"` (delete all contexts)
580+
581+
</td>
582+
</tr>
583+
<tr>
584+
<td>List entry deletion</td>
585+
<td>
586+
587+
- `context` (string): the context to delete the list entry from
588+
- `list` (dictionary, see next column)
555589

590+
If `list` is specified and `context` is missing, an error is thrown.
556591
</td>
557592
<td>
558593
Dictionary - only one option is interpreted (top to bottom as listed here)
@@ -666,7 +701,7 @@ The default expiration is 60 minutes. The default value can be overwritten (`0`
666701

667702
```java
668703
int expiration=1024;
669-
var store=new CaffeineStore(expiration);
704+
var store=new CaffeineStore(expiration);
670705
```
671706

672707
## Match a request against a context

demo/wiremock_state_extension_demo/src/stores/counter.ts

Lines changed: 0 additions & 12 deletions
This file was deleted.

src/main/java/org/wiremock/extensions/state/extensions/DeleteStateEventListener.java

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,13 @@
2828
import org.wiremock.extensions.state.internal.ResponseTemplateModel;
2929
import org.wiremock.extensions.state.internal.StateExtensionMixin;
3030

31+
import java.util.List;
3132
import java.util.Map;
3233
import java.util.Objects;
3334
import java.util.Optional;
35+
import java.util.regex.Pattern;
36+
import java.util.regex.PatternSyntaxException;
37+
import java.util.stream.Collectors;
3438

3539
import static org.wiremock.extensions.state.internal.ExtensionLogger.logger;
3640

@@ -68,13 +72,52 @@ public void beforeResponseSent(ServeEvent serveEvent, Parameters parameters) {
6872
"response", ResponseTemplateModel.from(serveEvent.getResponse())
6973
);
7074
var configuration = Json.mapToObject(parameters, DeleteStateParameters.class);
71-
var contextName = createContextName(model, configuration);
7275
Optional.ofNullable(configuration.getList()).ifPresentOrElse(
73-
listConfig -> handleListDeletion(listConfig, contextName, model),
74-
() -> contextManager.deleteContext(contextName)
76+
listConfig -> handleListDeletion(listConfig, createContextName(model, configuration.getContext()), model),
77+
() -> handleContextDeletion(configuration, model)
7578
);
7679
}
7780

81+
private void handleContextDeletion(DeleteStateParameters configuration, Map<String, Object> model) {
82+
if (configuration.getContext() != null) {
83+
deleteContext(configuration.getContext(), model);
84+
} else if (configuration.getContexts() != null) {
85+
deleteContexts(configuration.getContexts(), model);
86+
} else if (configuration.getContextsMatching() != null) {
87+
deleteContextsMatching(configuration.getContextsMatching(), model);
88+
} else {
89+
throw createConfigurationError("Missing/invalid configuration for context deletion");
90+
}
91+
}
92+
93+
private void deleteContexts(List<String> rawContexts, Map<String, Object> model) {
94+
95+
var contexts = rawContexts.stream().map(it -> renderTemplate(model, it)).collect(Collectors.toList());
96+
contextManager.onEach(context -> {
97+
if(contexts.contains(context.getContextName())) {
98+
contextManager.deleteContext(context.getContextName());
99+
}
100+
});
101+
}
102+
103+
private void deleteContextsMatching(String rawRegex, Map<String, Object> model) {
104+
try {
105+
var regex = renderTemplate(model, rawRegex);
106+
var pattern = Pattern.compile(regex);
107+
contextManager.onEach(context -> {
108+
if(pattern.matcher(context.getContextName()).matches()) {
109+
contextManager.deleteContext(context.getContextName());
110+
}
111+
});
112+
} catch (PatternSyntaxException ex) {
113+
throw createConfigurationError("Missing/invalid configuration for context deletion: %s", ex.getMessage());
114+
}
115+
}
116+
117+
private void deleteContext(String rawContext, Map<String, Object> model) {
118+
contextManager.deleteContext(createContextName(model, rawContext));
119+
}
120+
78121
private void handleListDeletion(DeleteStateParameters.ListParameters listConfig, String contextName, Map<String, Object> model) {
79122
if (Boolean.TRUE.equals(listConfig.getDeleteFirst())) {
80123
deleteFirst(contextName);
@@ -88,7 +131,7 @@ private void handleListDeletion(DeleteStateParameters.ListParameters listConfig,
88131
) {
89132
deleteWhere(listConfig, contextName, model);
90133
} else {
91-
throw createConfigurationError("Missing/invalid configuration for list: ");
134+
throw createConfigurationError("Missing/invalid configuration for list entry deletion");
92135
}
93136
}
94137

@@ -114,7 +157,7 @@ private void deleteIndex(DeleteStateParameters.ListParameters listConfig, String
114157
logger().info(contextName, String.format("list::deleteIndex(%d)", index));
115158
});
116159
} catch (IndexOutOfBoundsException | NumberFormatException e) {
117-
throw createConfigurationError("List index '%s' does not exist or cannot be parsed: %s", listConfig.getDeleteIndex(), e.getMessage());
160+
logger().info(contextName, String.format("Unknown or unparsable list index: '%s' - ignoring", listConfig.getDeleteIndex()));
118161
}
119162
}
120163

@@ -134,9 +177,10 @@ private void deleteWhere(DeleteStateParameters.ListParameters listConfig, String
134177
});
135178
}
136179

137-
private String createContextName(Map<String, Object> model, DeleteStateParameters parameters) {
138-
var rawContext = Optional.ofNullable(parameters.getContext()).filter(StringUtils::isNotBlank).orElseThrow(() -> new ConfigurationException("no context specified"));
139-
String context = renderTemplate(model, rawContext);
180+
private String createContextName(Map<String, Object> model, String rawContext) {
181+
var context = Optional.ofNullable(rawContext).filter(StringUtils::isNotBlank)
182+
.map(it -> renderTemplate(model, it))
183+
.orElseThrow(() -> new ConfigurationException("no context specified"));
140184
if (StringUtils.isBlank(context)) {
141185
throw createConfigurationError("Context cannot be blank");
142186
}

src/main/java/org/wiremock/extensions/state/internal/ContextManager.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public Object getState(String contextName, String property) {
5252
*/
5353
public Optional<Context> getContext(String contextName) {
5454
synchronized (store) {
55-
return store.get(contextName).map(it -> (Context) it).map(Context::new);
55+
return getSafeContextCopy(contextName);
5656
}
5757
}
5858

@@ -63,6 +63,14 @@ public void deleteContext(String contextName) {
6363
}
6464
}
6565

66+
public void onEach(Consumer<Context> consumer) {
67+
synchronized(store) {
68+
store.getAllKeys().forEach(contextName -> {
69+
getSafeContextCopy(contextName).ifPresent(consumer);
70+
});
71+
}
72+
}
73+
6674
public void deleteAllContexts() {
6775
synchronized (store) {
6876
logger().info("allContexts", "deleted");
@@ -117,4 +125,8 @@ public Long numReads(String contextName) {
117125
return store.get(contextName).map(it -> ((Context) it).getMatchCount()).orElse(0L);
118126
}
119127
}
128+
129+
private Optional<Context> getSafeContextCopy(String contextName) {
130+
return store.get(contextName).map(it -> (Context) it).map(Context::new);
131+
}
120132
}

src/main/java/org/wiremock/extensions/state/internal/DeleteStateParameters.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,14 @@
1717

1818
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
1919

20+
import java.util.List;
21+
2022
@JsonIgnoreProperties(ignoreUnknown = true)
2123
public class DeleteStateParameters {
2224
private String context;
25+
private String contextsMatching;
26+
27+
private List<String> contexts;
2328
private ListParameters list;
2429

2530
public ListParameters getList() {
@@ -38,6 +43,22 @@ public void setContext(String context) {
3843
this.context = context;
3944
}
4045

46+
public List<String> getContexts() {
47+
return contexts;
48+
}
49+
50+
public void setContexts(List<String> contexts) {
51+
this.contexts = contexts;
52+
}
53+
54+
public String getContextsMatching() {
55+
return contextsMatching;
56+
}
57+
58+
public void setContextsMatching(String contextsMatching) {
59+
this.contextsMatching = contextsMatching;
60+
}
61+
4162
@JsonIgnoreProperties(ignoreUnknown = true)
4263
public static class ListParameters {
4364
private Boolean deleteFirst;

0 commit comments

Comments
 (0)