@@ -1228,3 +1228,171 @@ def list_items(limit: Annotated[constrained_int, Query()] = 10):
12281228 assert limit_param .schema_ .type == "integer"
12291229 assert limit_param .schema_ .default == 10
12301230 assert limit_param .required is False
1231+
1232+
1233+ def test_query_alias_sets_validation_alias_automatically ():
1234+ """
1235+ Test for issue #7552: When alias is set but validation_alias is not,
1236+ validation_alias should be automatically set to alias value.
1237+ This ensures compatibility with Pydantic 2.12+.
1238+ """
1239+ from annotated_types import Ge , Le
1240+ from pydantic import StringConstraints
1241+
1242+ # GIVEN an APIGatewayRestResolver with validation enabled
1243+ app = APIGatewayRestResolver (enable_validation = True )
1244+
1245+ # AND constrained types using annotated_types
1246+ IntQuery = Annotated [int , Ge (1 ), Le (100 )]
1247+ StrQuery = Annotated [str , StringConstraints (min_length = 4 , max_length = 128 )]
1248+
1249+ @app .get ("/foo" )
1250+ def get_foo (
1251+ str_query : Annotated [StrQuery , Query (alias = "strQuery" )],
1252+ int_query : Annotated [IntQuery , Query (alias = "intQuery" )],
1253+ ):
1254+ return {"int_query" : int_query , "str_query" : str_query }
1255+
1256+ # WHEN sending a request with aliased query parameters
1257+ event = {
1258+ "httpMethod" : "GET" ,
1259+ "path" : "/foo" ,
1260+ "queryStringParameters" : {
1261+ "intQuery" : "20" ,
1262+ "strQuery" : "fooBarFizzBuzz" ,
1263+ },
1264+ }
1265+
1266+ # THEN the request should succeed with correct values
1267+ result = app (event , {})
1268+ assert result ["statusCode" ] == 200
1269+ body = json .loads (result ["body" ])
1270+ assert body ["int_query" ] == 20
1271+ assert body ["str_query" ] == "fooBarFizzBuzz"
1272+
1273+
1274+ def test_query_alias_with_multivalue_query_string_parameters ():
1275+ """
1276+ Test for issue #7552: Ensure alias works with multiValueQueryStringParameters.
1277+ """
1278+ from annotated_types import Ge , Le
1279+ from pydantic import StringConstraints
1280+
1281+ # GIVEN an APIGatewayRestResolver with validation enabled
1282+ app = APIGatewayRestResolver (enable_validation = True )
1283+
1284+ IntQuery = Annotated [int , Ge (1 ), Le (100 )]
1285+ StrQuery = Annotated [str , StringConstraints (min_length = 4 , max_length = 128 )]
1286+
1287+ @app .get ("/foo" )
1288+ def get_foo (
1289+ str_query : Annotated [StrQuery , Query (alias = "strQuery" )],
1290+ int_query : Annotated [IntQuery , Query (alias = "intQuery" )],
1291+ ):
1292+ return {"int_query" : int_query , "str_query" : str_query }
1293+
1294+ # WHEN sending a request with multiValueQueryStringParameters
1295+ event = {
1296+ "httpMethod" : "GET" ,
1297+ "path" : "/foo" ,
1298+ "multiValueQueryStringParameters" : {
1299+ "intQuery" : ["20" ],
1300+ "strQuery" : ["fooBarFizzBuzz" ],
1301+ },
1302+ }
1303+
1304+ # THEN the request should succeed
1305+ result = app (event , {})
1306+ assert result ["statusCode" ] == 200
1307+ body = json .loads (result ["body" ])
1308+ assert body ["int_query" ] == 20
1309+ assert body ["str_query" ] == "fooBarFizzBuzz"
1310+
1311+
1312+ def test_query_explicit_validation_alias_takes_precedence ():
1313+ """
1314+ Test that explicitly set validation_alias is preserved and not overwritten by alias.
1315+ The alias is used by Powertools to extract the value from the request,
1316+ while validation_alias is used by Pydantic for internal validation.
1317+ """
1318+ # GIVEN an APIGatewayRestResolver with validation enabled
1319+ app = APIGatewayRestResolver (enable_validation = True )
1320+
1321+ @app .get ("/foo" )
1322+ def get_foo (
1323+ my_param : Annotated [str , Query (alias = "aliasName" , validation_alias = "validationAliasName" )],
1324+ ):
1325+ return {"my_param" : my_param }
1326+
1327+ # WHEN sending a request with the alias name (used by Powertools to extract value)
1328+ event = {
1329+ "httpMethod" : "GET" ,
1330+ "path" : "/foo" ,
1331+ "queryStringParameters" : {
1332+ "aliasName" : "test_value" ,
1333+ },
1334+ }
1335+
1336+ # THEN the request should succeed using alias for extraction
1337+ result = app (event , {})
1338+ assert result ["statusCode" ] == 200
1339+ body = json .loads (result ["body" ])
1340+ assert body ["my_param" ] == "test_value"
1341+
1342+
1343+ def test_header_alias_sets_validation_alias_automatically ():
1344+ """
1345+ Test for issue #7552: Header alias should also set validation_alias automatically.
1346+ """
1347+ # GIVEN an APIGatewayRestResolver with validation enabled
1348+ app = APIGatewayRestResolver (enable_validation = True )
1349+
1350+ @app .get ("/foo" )
1351+ def get_foo (
1352+ custom_header : Annotated [str , Header (alias = "X-Custom-Header" )],
1353+ ):
1354+ return {"custom_header" : custom_header }
1355+
1356+ # WHEN sending a request with the aliased header
1357+ event = {
1358+ "httpMethod" : "GET" ,
1359+ "path" : "/foo" ,
1360+ "headers" : {
1361+ "X-Custom-Header" : "header_value" ,
1362+ },
1363+ }
1364+
1365+ # THEN the request should succeed
1366+ result = app (event , {})
1367+ assert result ["statusCode" ] == 200
1368+ body = json .loads (result ["body" ])
1369+ assert body ["custom_header" ] == "header_value"
1370+
1371+
1372+ def test_query_without_alias_works_normally ():
1373+ """
1374+ Test that Query without alias continues to work normally.
1375+ """
1376+ # GIVEN an APIGatewayRestResolver with validation enabled
1377+ app = APIGatewayRestResolver (enable_validation = True )
1378+
1379+ @app .get ("/foo" )
1380+ def get_foo (
1381+ my_param : Annotated [str , Query ()],
1382+ ):
1383+ return {"my_param" : my_param }
1384+
1385+ # WHEN sending a request with the parameter name
1386+ event = {
1387+ "httpMethod" : "GET" ,
1388+ "path" : "/foo" ,
1389+ "queryStringParameters" : {
1390+ "my_param" : "test_value" ,
1391+ },
1392+ }
1393+
1394+ # THEN the request should succeed
1395+ result = app (event , {})
1396+ assert result ["statusCode" ] == 200
1397+ body = json .loads (result ["body" ])
1398+ assert body ["my_param" ] == "test_value"
0 commit comments