Skip to content

Commit bcb99ba

Browse files
authored
fix: missing idempotency key should not persist any data (#1201)
1 parent c96c0ad commit bcb99ba

File tree

4 files changed

+43
-16
lines changed

4 files changed

+43
-16
lines changed

docs/utilities/idempotency.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -708,10 +708,12 @@ By using **`withPayloadValidationJMESPath("amount")`**, we prevent this potentia
708708

709709
### Making idempotency key required
710710

711-
If you want to enforce that an idempotency key is required, you can set **`ThrowOnNoIdempotencyKey`** to `True`.
711+
If you want to enforce that an idempotency key is required, you can set **`ThrowOnNoIdempotencyKey`** to `true`.
712712

713713
This means that we will throw **`IdempotencyKeyException`** if the evaluation of **`EventKeyJMESPath`** is `null`.
714714

715+
When set to `false` (the default), if the idempotency key is null, then the data is not persisted in the store.
716+
715717
=== "App.java"
716718

717719
```java hl_lines="9-10 13"

powertools-idempotency/src/main/java/software/amazon/lambda/powertools/idempotency/internal/IdempotencyHandler.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ private Object processIdempotency() throws Throwable {
8484
persistenceStore.saveInProgress(data, Instant.now(), getRemainingTimeInMillis());
8585
} catch (IdempotencyItemAlreadyExistsException iaee) {
8686
DataRecord record = getIdempotencyRecord();
87-
return handleForStatus(record);
87+
if (record != null) {
88+
return handleForStatus(record);
89+
}
8890
} catch (IdempotencyKeyException ike) {
8991
throw ike;
9092
} catch (Exception e) {
@@ -111,7 +113,7 @@ private OptionalInt getRemainingTimeInMillis() {
111113
/**
112114
* Retrieve the idempotency record from the persistence layer.
113115
*
114-
* @return the record if available
116+
* @return the record if available, potentially null
115117
*/
116118
private DataRecord getIdempotencyRecord() {
117119
try {

powertools-idempotency/src/main/java/software/amazon/lambda/powertools/idempotency/persistence/BasePersistenceStore.java

+33-10
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,12 @@
3535
import java.time.Instant;
3636
import java.time.temporal.ChronoUnit;
3737
import java.util.Map;
38-
import java.util.OptionalLong;
38+
import java.util.Optional;
3939
import java.util.OptionalInt;
40-
import java.util.stream.Stream;
41-
import java.util.Spliterators;
40+
import java.util.OptionalLong;
4241
import java.util.Spliterator;
42+
import java.util.Spliterators;
43+
import java.util.stream.Stream;
4344
import java.util.stream.StreamSupport;
4445

4546
import static software.amazon.lambda.powertools.core.internal.LambdaConstants.LAMBDA_FUNCTION_NAME_ENV;
@@ -117,8 +118,13 @@ public void saveSuccess(JsonNode data, Object result, Instant now) {
117118
} else {
118119
responseJson = writer.writeValueAsString(result);
119120
}
121+
Optional<String> hashedIdempotencyKey = getHashedIdempotencyKey(data);
122+
if (!hashedIdempotencyKey.isPresent()) {
123+
// missing idempotency key => non-idempotent transaction, we do not store the data, simply return
124+
return;
125+
}
120126
DataRecord record = new DataRecord(
121-
getHashedIdempotencyKey(data),
127+
hashedIdempotencyKey.get(),
122128
DataRecord.Status.COMPLETED,
123129
getExpiryEpochSecond(now),
124130
responseJson,
@@ -140,8 +146,13 @@ public void saveSuccess(JsonNode data, Object result, Instant now) {
140146
* @param now
141147
*/
142148
public void saveInProgress(JsonNode data, Instant now, OptionalInt remainingTimeInMs) throws IdempotencyItemAlreadyExistsException {
143-
String idempotencyKey = getHashedIdempotencyKey(data);
149+
Optional<String> hashedIdempotencyKey = getHashedIdempotencyKey(data);
150+
if (!hashedIdempotencyKey.isPresent()) {
151+
// missing idempotency key => non-idempotent transaction, we do not store the data, simply return
152+
return;
153+
}
144154

155+
String idempotencyKey = hashedIdempotencyKey.get();
145156
if (retrieveFromCache(idempotencyKey, now) != null) {
146157
throw new IdempotencyItemAlreadyExistsException();
147158
}
@@ -170,8 +181,13 @@ public void saveInProgress(JsonNode data, Instant now, OptionalInt remainingTime
170181
* @param throwable The throwable thrown by the function
171182
*/
172183
public void deleteRecord(JsonNode data, Throwable throwable) {
173-
String idemPotencyKey = getHashedIdempotencyKey(data);
184+
Optional<String> hashedIdempotencyKey = getHashedIdempotencyKey(data);
185+
if (!hashedIdempotencyKey.isPresent()) {
186+
// missing idempotency key => non-idempotent transaction, we do not delete the data, simply return
187+
return;
188+
}
174189

190+
String idemPotencyKey = hashedIdempotencyKey.get();
175191
LOG.debug("Function raised an exception {}. " +
176192
"Clearing in progress record in persistence store for idempotency key: {}",
177193
throwable.getClass(),
@@ -190,8 +206,13 @@ public void deleteRecord(JsonNode data, Throwable throwable) {
190206
* @throws IdempotencyItemNotFoundException Exception thrown if no record exists in persistence store with the idempotency key
191207
*/
192208
public DataRecord getRecord(JsonNode data, Instant now) throws IdempotencyValidationException, IdempotencyItemNotFoundException {
193-
String idemPotencyKey = getHashedIdempotencyKey(data);
209+
Optional<String> hashedIdempotencyKey = getHashedIdempotencyKey(data);
210+
if (!hashedIdempotencyKey.isPresent()) {
211+
// missing idempotency key => non-idempotent transaction, we do not get the data, simply return nothing
212+
return null;
213+
}
194214

215+
String idemPotencyKey = hashedIdempotencyKey.get();
195216
DataRecord cachedRecord = retrieveFromCache(idemPotencyKey, now);
196217
if (cachedRecord != null) {
197218
LOG.debug("Idempotency record found in cache with idempotency key: {}", idemPotencyKey);
@@ -211,7 +232,7 @@ public DataRecord getRecord(JsonNode data, Instant now) throws IdempotencyValida
211232
* @param data incoming data
212233
* @return Hashed representation of the data extracted by the jmespath expression
213234
*/
214-
private String getHashedIdempotencyKey(JsonNode data) {
235+
private Optional<String> getHashedIdempotencyKey(JsonNode data) {
215236
JsonNode node = data;
216237

217238
if (eventKeyJMESPath != null) {
@@ -221,13 +242,15 @@ private String getHashedIdempotencyKey(JsonNode data) {
221242
if (isMissingIdemPotencyKey(node)) {
222243
if (throwOnNoIdempotencyKey) {
223244
throw new IdempotencyKeyException("No data found to create a hashed idempotency key");
245+
} else {
246+
LOG.warn("No data found to create a hashed idempotency key. JMESPath: {}", eventKeyJMESPath);
247+
return Optional.empty();
224248
}
225-
LOG.warn("No data found to create a hashed idempotency key. JMESPath: {}", eventKeyJMESPath);
226249
}
227250

228251
String hash = generateHash(node);
229252
hash = functionName + "#" + hash;
230-
return hash;
253+
return Optional.of(hash);
231254
}
232255

233256
private boolean isMissingIdemPotencyKey(JsonNode data) {

powertools-idempotency/src/test/java/software/amazon/lambda/powertools/idempotency/persistence/BasePersistenceStoreTest.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -143,15 +143,15 @@ public void saveInProgress_jmespath_NotFound_shouldThrowException() {
143143
}
144144

145145
@Test
146-
public void saveInProgress_jmespath_NotFound_shouldNotThrowException() {
146+
public void saveInProgress_jmespath_NotFound_shouldNotPersist() {
147147
APIGatewayProxyRequestEvent event = EventLoader.loadApiGatewayRestEvent("apigw_event.json");
148148
persistenceStore.configure(IdempotencyConfig.builder()
149149
.withEventKeyJMESPath("unavailable")
150150
.build(), "");
151151
Instant now = Instant.now();
152152
persistenceStore.saveInProgress(JsonConfig.get().getObjectMapper().valueToTree(event), now, OptionalInt.empty());
153-
assertThat(dr.getStatus()).isEqualTo(DataRecord.Status.INPROGRESS);
154-
assertThat(status).isEqualTo(1);
153+
assertThat(dr).isNull();
154+
assertThat(status).isEqualTo(-1);
155155
}
156156

157157
@Test

0 commit comments

Comments
 (0)