@@ -94,6 +94,47 @@ def search_handler(params: Annotated[QueryParams, Query()]):
94
94
assert any ("limit" in str (error ) for error in body ["detail" ])
95
95
96
96
97
+ def test_validate_pydantic_query_params_detailed_errors (gw_event ):
98
+ """Test that Pydantic validation errors include detailed field-level information"""
99
+ app = APIGatewayRestResolver (enable_validation = True )
100
+
101
+ class QueryParams (BaseModel ):
102
+ full_name : str = Field (..., min_length = 5 , description = "Full name with minimum 5 characters" )
103
+ age : int = Field (..., ge = 18 , le = 100 , description = "Age between 18 and 100" )
104
+
105
+ @app .get ("/query-model" )
106
+ def query_model (params : Annotated [QueryParams , Query ()]):
107
+ return {"full_name" : params .full_name , "age" : params .age }
108
+
109
+ # Test validation error with detailed field information
110
+ gw_event ["path" ] = "/query-model"
111
+ gw_event ["queryStringParameters" ] = {"full_name" : "Jo" , "age" : "15" } # Both invalid
112
+
113
+ result = app (gw_event , {})
114
+ assert result ["statusCode" ] == 422
115
+
116
+ body = json .loads (result ["body" ])
117
+ assert "detail" in body
118
+
119
+ # Check that we get detailed field-level errors
120
+ errors = body ["detail" ]
121
+
122
+ # Should have errors for both fields
123
+ full_name_error = next ((e for e in errors if "full_name" in e ["loc" ]), None )
124
+ age_error = next ((e for e in errors if "age" in e ["loc" ]), None )
125
+
126
+ assert full_name_error is not None , "Should have error for full_name field"
127
+ assert age_error is not None , "Should have error for age field"
128
+
129
+ # Check error details for full_name
130
+ assert full_name_error ["loc" ] == ["query" , "full_name" ]
131
+ assert full_name_error ["type" ] == "string_too_short"
132
+
133
+ # Check error details for age
134
+ assert age_error ["loc" ] == ["query" , "age" ]
135
+ assert age_error ["type" ] == "greater_than_equal"
136
+
137
+
97
138
def test_validate_pydantic_header_params (gw_event ):
98
139
"""Test that Pydantic models in Header parameters are validated correctly"""
99
140
app = APIGatewayRestResolver (enable_validation = True )
@@ -141,6 +182,49 @@ def protected_handler(headers: Annotated[HeaderParams, Header()]):
141
182
assert any ("authorization" in str (error ) for error in body ["detail" ])
142
183
143
184
185
+ def test_validate_pydantic_header_snake_case_to_kebab_case_schema (gw_event ):
186
+ """Test that snake_case header fields are converted to kebab-case in OpenAPI schema and validation"""
187
+ app = APIGatewayRestResolver (enable_validation = True )
188
+ app .enable_swagger ()
189
+
190
+ class HeaderParams (BaseModel ):
191
+ correlation_id : str = Field (description = "Correlation ID header" )
192
+ user_agent : str = Field (default = "PowerTools/1.0" , description = "User agent header" )
193
+
194
+ @app .get ("/kebab-headers" )
195
+ def kebab_handler (headers : Annotated [HeaderParams , Header ()]):
196
+ return {
197
+ "correlation_id" : headers .correlation_id ,
198
+ "user_agent" : headers .user_agent ,
199
+ }
200
+
201
+ # Test that OpenAPI schema uses kebab-case for headers
202
+ openapi_schema = app .get_openapi_schema ()
203
+ operation = openapi_schema ["paths" ]["/kebab-headers" ]["get" ]
204
+ parameters = operation ["parameters" ]
205
+
206
+ # Find the correlation_id parameter
207
+ correlation_param = next ((p for p in parameters if p ["name" ] == "correlation-id" ), None )
208
+ assert correlation_param is not None , "Should have correlation-id parameter in kebab-case"
209
+ assert correlation_param ["in" ] == "header"
210
+
211
+ # Find the user_agent parameter
212
+ user_agent_param = next ((p for p in parameters if p ["name" ] == "user-agent" ), None )
213
+ assert user_agent_param is not None , "Should have user-agent parameter in kebab-case"
214
+ assert user_agent_param ["in" ] == "header"
215
+
216
+ # Test validation with kebab-case headers
217
+ gw_event ["path" ] = "/kebab-headers"
218
+ gw_event ["headers" ] = {"correlation-id" : "test-123" , "user-agent" : "TestClient/1.0" }
219
+
220
+ result = app (gw_event , {})
221
+ assert result ["statusCode" ] == 200
222
+
223
+ body = json .loads (result ["body" ])
224
+ assert body ["correlation_id" ] == "test-123"
225
+ assert body ["user_agent" ] == "TestClient/1.0"
226
+
227
+
144
228
def test_validate_pydantic_mixed_params (gw_event ):
145
229
"""Test that mixed Pydantic models (Query + Header) are validated correctly"""
146
230
app = APIGatewayRestResolver (enable_validation = True )
0 commit comments