From 2a0ddf1b2b1583466e93113f0c467035663da893 Mon Sep 17 00:00:00 2001 From: Lee Hannigan <29015211+LeeroyHannigan@users.noreply.github.com> Date: Fri, 30 Oct 2026 09:20:17 +0000 Subject: [PATCH 1/3] fix(scan): report every Limit, Segment and TotalSegments range fault in the service's two forms The Scan range check reported the first fault in the order Limit, Segment, TotalSegments, with the previous release's texts for the two segment fields (Value '-1' at 'segment', and "The parameter TotalSegments should be greater than or equal to 1" for a value below 1). A float or an integer outside i64 in any of the three answered SerializationException. The service reports every range fault in one message. When each field present is an integer inside the int32 range it uses "Value at '' failed to satisfy constraint: ..." with the field capitalised and no value echoed, in the order Limit, Segment, TotalSegments. When any of the three is a float or outside int32, or an ExpressionAttributeValues entry is not one AttributeValue (empty, an unknown type descriptor, two descriptors), it truncates or saturates the value and uses "Value '' at '' ..." with the field in lower camel case and limit last. A float or an out-of-range integer that satisfies the constraint is accepted: Limit 1.5 returns one item and Segment 1.5 returns part of the table. The request-time check now builds that message from the raw body, and the three fields deserialize from any JSON number, truncated toward zero and saturated to int32, so the handler uses the value the check validated. Query answers the older form for a Limit of 0 beside an entry that is not one AttributeValue, as measured on Query. The two assertions in tests/test_query_scan.py that pinned the previous segment texts now assert the measured text. Measured against the service on 2026-10-25 (09:23, 09:25, 09:28, 10:38, 10:55 and 12:56 UTC), each request twice. --- crates/core/src/types/mod.rs | 4 +- crates/core/src/types/query.rs | 119 ++++++++- crates/engine/src/query_key_values.rs | 35 +++ crates/engine/src/query_key_values/tests.rs | 34 ++- crates/engine/src/scan_request.rs | 178 ++++++++----- crates/engine/src/scan_request/range_tests.rs | 195 +++++++++++++++ crates/engine/src/scan_request/tests.rs | 146 ++++------- tests/python/test_scan_range_constraints.py | 234 ++++++++++++++++++ tests/test_query_scan.py | 20 +- 9 files changed, 785 insertions(+), 180 deletions(-) create mode 100644 crates/engine/src/scan_request/range_tests.rs create mode 100644 tests/python/test_scan_range_constraints.py diff --git a/crates/core/src/types/mod.rs b/crates/core/src/types/mod.rs index caf90fd19..12fbe3d74 100755 --- a/crates/core/src/types/mod.rs +++ b/crates/core/src/types/mod.rs @@ -42,7 +42,9 @@ pub use key_schema::{ ScalarAttributeType, TableKeyInfo, VectorIndexKeyInfo, hash_key_elements, is_multipart_key_schema, partition_indexes, range_key_elements, }; -pub use query::{Condition, QueryInput, QueryOutput, ScanInput, ScanOutput, Select}; +pub use query::{ + Condition, QueryInput, QueryOutput, ScanInput, ScanOutput, Select, saturated_int32, +}; pub use stream::{ DescribeStreamInput, DescribeStreamOutput, GetRecordsInput, GetRecordsOutput, GetShardIteratorInput, GetShardIteratorOutput, ListStreamsInput, ListStreamsOutput, diff --git a/crates/core/src/types/query.rs b/crates/core/src/types/query.rs index d9aeaf345..695c63928 100755 --- a/crates/core/src/types/query.rs +++ b/crates/core/src/types/query.rs @@ -142,15 +142,30 @@ pub struct ScanInput { deserialize_with = "crate::serde_helpers::deserialize_expression_values" )] pub expression_attribute_values: Option>, - #[serde(rename = "Limit")] + /// Any JSON number; see [`saturated_int32`]. + #[serde( + rename = "Limit", + default, + deserialize_with = "deserialize_int32_field" + )] pub limit: Option, #[serde(rename = "ExclusiveStartKey")] pub exclusive_start_key: Option, #[serde(rename = "Select")] pub select: Option