Skip to content

Commit 6ce89d6

Browse files
Alex Wangwangyb-A
authored andcommitted
fix: fix double encoding for execution input
- Check the input str before we serialize it to avoid double encoding for JSON input
1 parent e33a001 commit 6ce89d6

File tree

4 files changed

+66
-14
lines changed

4 files changed

+66
-14
lines changed

src/aws_durable_execution_sdk_python_testing/execution.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ def start(self) -> None:
161161
operation_type=OperationType.EXECUTION,
162162
status=OperationStatus.STARTED,
163163
execution_details=ExecutionDetails(
164-
input_payload=json.dumps(self.start_input.input)
164+
input_payload=self.start_input.get_normalized_input()
165165
),
166166
)
167167
)

src/aws_durable_execution_sdk_python_testing/model.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from dataclasses import dataclass, replace
77
from enum import Enum
88
from typing import Any
9+
import json
910

1011
from dateutil.tz import UTC
1112

@@ -166,6 +167,19 @@ def to_dict(self) -> dict[str, Any]:
166167
result["Input"] = self.input
167168
return result
168169

170+
def get_normalized_input(self):
171+
"""
172+
Normalize input string to be JSON deserializable.
173+
Avoid double coding json input.
174+
"""
175+
# Try to parse once
176+
try:
177+
_ = json.loads(self.input)
178+
return self.input
179+
except (json.JSONDecodeError, TypeError):
180+
# Not valid JSON, treat as plain string and encode it
181+
return json.dumps(self.input)
182+
169183

170184
@dataclass(frozen=True)
171185
class StartDurableExecutionOutput:

tests/execution_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def test_execution_start(mock_datetime):
9999
assert operation.start_timestamp == mock_now
100100
assert operation.operation_type == OperationType.EXECUTION
101101
assert operation.status == OperationStatus.STARTED
102-
assert operation.execution_details.input_payload == '"{\\"key\\": \\"value\\"}"'
102+
assert operation.execution_details.input_payload == '{"key": "value"}'
103103

104104

105105
def test_get_operation_execution_started():

tests/model_test.py

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -77,21 +77,23 @@
7777
TIMESTAMP_2023_01_01_00_02 = datetime.datetime(2023, 1, 1, 0, 2, 0, tzinfo=datetime.UTC)
7878
TIMESTAMP_2023_01_02_00_00 = datetime.datetime(2023, 1, 2, 0, 0, 0, tzinfo=datetime.UTC)
7979

80+
DEFAULT_START_DURABLE_EXECUTION_INPUT_DATA = {
81+
"AccountId": "123456789012",
82+
"FunctionName": "my-function",
83+
"FunctionQualifier": "$LATEST",
84+
"ExecutionName": "test-execution",
85+
"ExecutionTimeoutSeconds": 300,
86+
"ExecutionRetentionPeriodDays": 7,
87+
"InvocationId": "invocation-123",
88+
"TraceFields": {"key": "value"},
89+
"TenantId": "tenant-123",
90+
"Input": "test-input",
91+
}
92+
8093

8194
def test_start_durable_execution_input_serialization():
8295
"""Test StartDurableExecutionInput from_dict/to_dict round-trip."""
83-
data = {
84-
"AccountId": "123456789012",
85-
"FunctionName": "my-function",
86-
"FunctionQualifier": "$LATEST",
87-
"ExecutionName": "test-execution",
88-
"ExecutionTimeoutSeconds": 300,
89-
"ExecutionRetentionPeriodDays": 7,
90-
"InvocationId": "invocation-123",
91-
"TraceFields": {"key": "value"},
92-
"TenantId": "tenant-123",
93-
"Input": "test-input",
94-
}
96+
data = DEFAULT_START_DURABLE_EXECUTION_INPUT_DATA
9597

9698
# Test from_dict
9799
input_obj = StartDurableExecutionInput.from_dict(data)
@@ -115,6 +117,42 @@ def test_start_durable_execution_input_serialization():
115117
assert round_trip == input_obj
116118

117119

120+
def test_start_durable_execution_input_get_input_json_input():
121+
"""Test StartDurableExecutionInput from_dict/to_dict round-trip."""
122+
data = DEFAULT_START_DURABLE_EXECUTION_INPUT_DATA
123+
data["Input"] = '{"message": "hello"}'
124+
125+
input_obj = StartDurableExecutionInput.from_dict(data)
126+
assert '{"message": "hello"}' == input_obj.get_normalized_input()
127+
128+
129+
def test_start_durable_execution_input_get_input_str_non_json_input():
130+
"""Test StartDurableExecutionInput from_dict/to_dict round-trip."""
131+
data = DEFAULT_START_DURABLE_EXECUTION_INPUT_DATA
132+
data["Input"] = "hello"
133+
134+
input_obj = StartDurableExecutionInput.from_dict(data)
135+
assert '"hello"' == input_obj.get_normalized_input()
136+
137+
138+
def test_start_durable_execution_input_get_input_str_json_input():
139+
"""Test StartDurableExecutionInput from_dict/to_dict round-trip."""
140+
data = DEFAULT_START_DURABLE_EXECUTION_INPUT_DATA
141+
data["Input"] = '"hello"'
142+
143+
input_obj = StartDurableExecutionInput.from_dict(data)
144+
assert '"hello"' == input_obj.get_normalized_input()
145+
146+
147+
def test_start_durable_execution_input_get_input_list_json_input():
148+
"""Test StartDurableExecutionInput from_dict/to_dict round-trip."""
149+
data = DEFAULT_START_DURABLE_EXECUTION_INPUT_DATA
150+
data["Input"] = "[1,2,3]"
151+
152+
input_obj = StartDurableExecutionInput.from_dict(data)
153+
assert "[1,2,3]" == input_obj.get_normalized_input()
154+
155+
118156
def test_start_durable_execution_input_minimal():
119157
"""Test StartDurableExecutionInput with only required fields."""
120158
data = {

0 commit comments

Comments
 (0)