Skip to content

Commit bc3fcd5

Browse files
docs(event_handler): improve routing rules syntax documentation (#7094)
* docs: add comprehensive routing rules syntax documentation Addresses issue #2962 by adding detailed documentation for: - Dynamic path parameter syntax (<parameter_name>) - Supported characters in path parameters - Regex pattern conversion process - Advanced routing examples with complex parameters - Catch-all routes with regex patterns - Route matching priority and behavior The new documentation provides clear explanations of how routing rules work, the regex syntax supported, and examples of complex routing patterns that were previously undocumented. * docs: refactor routing rules syntax documentation per review feedback - Remove redundant routing rules syntax introduction section - Update header levels from h5 to h4 for better hierarchy - Move inline code examples to separate files in examples/event_handler_rest/src - Add proper Lambda handlers with Logger and Tracer utilities - Reorganize content by moving regex pattern table to Dynamic Path Parameters section - Update tip formatting to match project style - Simplify inline comments to follow project conventions * Small changes --------- Co-authored-by: Leandro Damascena <[email protected]>
1 parent 4e1c4e9 commit bc3fcd5

File tree

4 files changed

+135
-16
lines changed

4 files changed

+135
-16
lines changed

docs/core/event_handler/api_gateway.md

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,8 @@ You can use `/todos/<todo_id>` to configure dynamic URL paths, where `<todo_id>`
196196

197197
Each dynamic route you set must be part of your function signature. This allows us to call your function using keyword arguments when matching your dynamic route.
198198

199-
???+ note
200-
For brevity, we will only include the necessary keys for each sample request for the example to work.
199+
???+ tip
200+
You can also nest dynamic paths, for example `/todos/<todo_id>/<todo_status>`.
201201

202202
=== "dynamic_routes.py"
203203

@@ -211,32 +211,65 @@ Each dynamic route you set must be part of your function signature. This allows
211211
--8<-- "examples/event_handler_rest/src/dynamic_routes.json"
212212
```
213213

214-
???+ tip
215-
You can also nest dynamic paths, for example `/todos/<todo_id>/<todo_status>`.
214+
#### Dynamic path mechanism
215+
216+
Dynamic path parameters are defined using angle brackets `<parameter_name>` syntax. These parameters are automatically converted to regex patterns for efficient route matching and performance gains.
217+
218+
**Syntax**: `/path/<parameter_name>`
219+
220+
* **Parameter names** must contain only word characters (letters, numbers, underscore)
221+
* **Captured values** can contain letters, numbers, underscores, and these special characters: `-._~()'!*:@,;=+&$%<> \[]{}|^`. Reserved characters must be percent-encoded in URLs to prevent errors.
222+
223+
| Route Pattern | Matches | Doesn't Match |
224+
|---------------|---------|---------------|
225+
| `/users/<user_id>` | `/users/123`, `/users/user-456` | `/users/123/profile` |
226+
| `/api/<version>/users` | `/api/v1/users`, `/api/2.0/users` | `/api/users` |
227+
| `/files/<path>` | `/files/document.pdf`, `/files/folder%20name` | `/files/sub/folder/file.txt` |
228+
| `/files/<folder>/<name>` | `/files/src/document.pdf`, `/files/src/test.txt` | `/files/sub/folder/file.txt` |
229+
230+
=== "routing_syntax_basic.py"
231+
232+
```python hl_lines="11 18"
233+
--8<-- "examples/event_handler_rest/src/routing_syntax_basic.py"
234+
```
235+
236+
=== "routing_advanced_examples.py"
237+
238+
```python hl_lines="11 22"
239+
--8<-- "examples/event_handler_rest/src/routing_advanced_examples.py"
240+
```
241+
242+
???+ tip "Function parameter names must match"
243+
The parameter names in your route (`<user_id>`) must exactly match the parameter names in your function signature (`user_id: str`). This is how the framework knows which captured values to pass to which parameters.
216244

217245
#### Catch-all routes
218246

219-
???+ note
220-
We recommend having explicit routes whenever possible; use catch-all routes sparingly.
247+
For scenarios where you need to handle arbitrary or deeply nested paths, you can use regex patterns directly in your route definitions. These are particularly useful for proxy routes or when dealing with file paths.
221248

222-
You can use a [regex](https://docs.python.org/3/library/re.html#regular-expression-syntax){target="_blank" rel="nofollow"} string to handle an arbitrary number of paths within a request, for example `.+`.
249+
**We recommend** having explicit routes whenever possible; use catch-all routes sparingly.
223250

224-
You can also combine nested paths with greedy regex to catch in between routes.
251+
##### Using Regex Patterns
225252

226-
???+ warning
227-
We choose the most explicit registered route that matches an incoming event.
253+
You can use standard [Python regex patterns](https://docs.python.org/3/library/re.html#regular-expression-syntax){target="_blank" rel="nofollow"} in your route definitions, for example:
254+
255+
| Pattern | Description | Examples |
256+
|---------|-------------|----------|
257+
| `.+` | Matches one or more characters (greedy) | `/proxy/.+` matches `/proxy/any/deep/path` |
258+
| `.*` | Matches zero or more characters (greedy) | `/files/.*` matches `/files/` and `/files/deep/path` |
259+
| `[^/]+` | Matches one or more non-slash characters | `/api/[^/]+` matches `/api/v1` but not `/api/v1/users` |
260+
| `\w+` | Matches one or more word characters | `/users/\w+` matches `/users/john123` |
228261

229262
=== "dynamic_routes_catch_all.py"
230263

231-
```python hl_lines="11"
264+
```python hl_lines="11 17 18 24 25 30 31 36 37"
232265
--8<-- "examples/event_handler_rest/src/dynamic_routes_catch_all.py"
233266
```
234267

235-
=== "dynamic_routes_catch_all.json"
236-
237-
```json
238-
--8<-- "examples/event_handler_rest/src/dynamic_routes_catch_all.json"
239-
```
268+
???+ warning "Route Matching Priority"
269+
- Routes are matched in **order of specificity**, not registration order
270+
- More specific routes (exact matches) take precedence over regex patterns
271+
- Among regex routes, the first registered matching route wins
272+
- Always place catch-all routes (`.*`) last
240273

241274
### HTTP Methods
242275

examples/event_handler_rest/src/dynamic_routes_catch_all.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,32 @@ def catch_any_route_get_method():
1414
return {"path_received": app.current_event.path}
1515

1616

17+
# File path proxy - captures everything after /files/
18+
@app.get("/files/.+")
19+
def serve_file():
20+
file_path = app.current_event.path.replace("/files/", "")
21+
return {"file_path": file_path}
22+
23+
24+
# API versioning with any format
25+
@app.get(r"/api/v\d+/.*") # Matches /api/v1/users, /api/v2/posts/123
26+
def handle_versioned_api():
27+
return {"api_version": "handled"}
28+
29+
30+
# Catch-all for unmatched routes
31+
@app.route(".*", method=["GET", "POST"]) # Must be last route
32+
def catch_all():
33+
return {"message": "Route not found", "path": app.current_event.path}
34+
35+
36+
# Mixed: dynamic parameter + regex catch-all
37+
@app.get("/users/<user_id>/files/.+")
38+
def get_user_files(user_id: str):
39+
file_path = app.current_event.path.split(f"/users/{user_id}/files/")[1]
40+
return {"user_id": user_id, "file_path": file_path}
41+
42+
1743
# You can continue to use other utilities just as before
1844
@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST)
1945
@tracer.capture_lambda_handler
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from aws_lambda_powertools import Logger, Tracer
2+
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
3+
from aws_lambda_powertools.logging import correlation_paths
4+
from aws_lambda_powertools.utilities.typing import LambdaContext
5+
6+
tracer = Tracer()
7+
logger = Logger()
8+
app = APIGatewayRestResolver()
9+
10+
11+
@app.get("/api/<api_version>/resources/<resource_type>/<resource_id>")
12+
@tracer.capture_method
13+
def get_resource(api_version: str, resource_type: str, resource_id: str):
14+
# handles nested dynamic parameters in API versioned routes
15+
return {
16+
"version": api_version,
17+
"type": resource_type,
18+
"id": resource_id,
19+
}
20+
21+
22+
@app.get("/organizations/<org_id>/teams/<team_id>/members")
23+
@tracer.capture_method
24+
def list_team_members(org_id: str, team_id: str):
25+
# combines dynamic paths with static segments
26+
return {"org": org_id, "team": team_id, "action": "list_members"}
27+
28+
29+
@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST)
30+
@tracer.capture_lambda_handler
31+
def lambda_handler(event: dict, context: LambdaContext) -> dict:
32+
return app.resolve(event, context)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from aws_lambda_powertools import Logger, Tracer
2+
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
3+
from aws_lambda_powertools.logging import correlation_paths
4+
from aws_lambda_powertools.utilities.typing import LambdaContext
5+
6+
tracer = Tracer()
7+
logger = Logger()
8+
app = APIGatewayRestResolver()
9+
10+
11+
@app.get("/users/<user_id>")
12+
@tracer.capture_method
13+
def get_user(user_id: str):
14+
# user_id value comes as a string with special chars support
15+
return {"user_id": user_id}
16+
17+
18+
@app.get("/orders/<order_id>/items/<item_id>")
19+
@tracer.capture_method
20+
def get_order_item(order_id: str, item_id: str):
21+
# multiple dynamic parameters are supported
22+
return {"order_id": order_id, "item_id": item_id}
23+
24+
25+
@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST)
26+
@tracer.capture_lambda_handler
27+
def lambda_handler(event: dict, context: LambdaContext) -> dict:
28+
return app.resolve(event, context)

0 commit comments

Comments
 (0)