Description
I was curious about the idempotent implementation and the general property of REST API bodies being un-ordered lists of JSON attributes. A REST API should be resilient to consumers and pragmatic in its approach to reading data supplied by clients calling them.
I created a simple JSON REST API using serverless and Powertools and implemented the Idempotency utility as follows:
import json
import os
from datetime import datetime
from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.utilities.idempotency import DynamoDBPersistenceLayer, idempotent, IdempotencyConfig
logger = Logger()
tracer = Tracer()
table_name = os.environ['TABLE']
persistence_layer = DynamoDBPersistenceLayer(table_name=table_name)
config = IdempotencyConfig(event_key_jmespath="body")
@logger.inject_lambda_context
@tracer.capture_lambda_handler
@idempotent(persistence_store=persistence_layer, config=config)
def handler(event, context):
body = {
"message": f"Data Created: {datetime.now()}",
"input": event['body']
}
logger.info('Processing API call - creating the data now!')
logger.info(event['body'])
response = {
"statusCode": 200,
"body": json.dumps(body)
}
return response
I deployed this to AWS and called the API with input as follows:
Test 1
{
"data": "test message 1",
"more_data": "more data 1"
}
Test 2
Note JSON attributes are submitted in different order which is OK for a REST API.
{
"more_data": "more data 1",
"data": "test message 1"
}
My expectation is that both these payloads will result in the exact same idempotent response since the payloads are essentially the same so far as REST APi conventions go.
The result I actually get is as follows:
Test 1
Result from first call to the API
{
"message": "Data Created: 2021-08-22 05:14:01.524104",
"input": "{\n \"data\": \"test message 1\",\n \"more_data\": \"more data 1\"\n}"
}
Test 2
Note: This is a different response than the first call to the API - I was expecting the same idempotent response.
{
"message": "Data Created: 2021-08-22 05:14:42.554149",
"input": "{\n \"more_data\": \"more data 1\",\n \"data\": \"test message 1\"\n}"
}
What were you trying to accomplish?
Implement Idempotency over a simple API using the entire API Body as the key for recognising Idempotency.
Expected Behavior
When calling my API with Test 1 payload and test 2 payload I am expecting the exact same idempotent response.
Current Behavior
Instead I am getting 2 different responses since the order of attributes plays a role in the idempotency key Hashing algorithm used by this utility.
Possible Solution
Suggest looking at using the sort_keys=True option for json.dumps when generating the Hash
Steps to Reproduce (for bugs)
- Create lambda API using code similar to code in this report.
- send payload: {"data":"test message 1","more_data":"more data 1"}
- call API again with same inputs but attribute order changed: {"more_data":"more data 1","data":"test message 1"}
- Expect same idempotent response from step 4 as in step 3
Environment
- Powertools version used: 1.20.0
- Packaging format (Layers, PyPi):
- AWS Lambda function runtime: Python 3.8
- Debugging logs