Skip to content

Commit 7bf9288

Browse files
committed
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.
1 parent 8096018 commit 7bf9288

File tree

1 file changed

+132
-4
lines changed

1 file changed

+132
-4
lines changed

docs/core/event_handler/api_gateway.md

Lines changed: 132 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,17 +214,145 @@ Each dynamic route you set must be part of your function signature. This allows
214214
???+ tip
215215
You can also nest dynamic paths, for example `/todos/<todo_id>/<todo_status>`.
216216

217+
#### Routing Rules Syntax
218+
219+
The routing system uses a specific syntax to define dynamic URL patterns. Understanding this syntax is crucial for creating flexible and robust API routes.
220+
221+
##### Dynamic Path Parameters
222+
223+
Dynamic path parameters are defined using angle brackets `<parameter_name>` syntax. These parameters are automatically converted to regex patterns for efficient route matching.
224+
225+
**Syntax**: `/path/<parameter_name>`
226+
227+
* **Parameter names** must contain only word characters (letters, numbers, underscore)
228+
* **Captured values** can contain letters, numbers, underscores, and these special characters: `-._~()'!*:@,;=+&$%<> \[]{}|^`
229+
230+
=== "routing_syntax_basic.py"
231+
232+
```python
233+
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
234+
235+
app = APIGatewayRestResolver()
236+
237+
@app.get("/users/<user_id>")
238+
def get_user(user_id: str):
239+
# user_id can be: "123", "user-456", "john.doe", "user_with_underscores"
240+
return {"user_id": user_id}
241+
242+
@app.get("/orders/<order_id>/items/<item_id>")
243+
def get_order_item(order_id: str, item_id: str):
244+
# Multiple parameters: /orders/ORD-123/items/ITEM_456
245+
return {"order_id": order_id, "item_id": item_id}
246+
```
247+
248+
##### Regex Pattern Conversion
249+
250+
Behind the scenes, dynamic routes are converted to regex patterns for efficient matching:
251+
252+
| Route Pattern | Generated Regex | Matches | Doesn't Match |
253+
|---------------|-----------------|---------|---------------|
254+
| `/users/<user_id>` | `^/users/(?P<user_id>[-._~()'!*:@,;=+&$%<> \[\]{}|^\w]+)$` | `/users/123`, `/users/user-456` | `/users/123/profile` |
255+
| `/api/<version>/users` | `^/api/(?P<version>[-._~()'!*:@,;=+&$%<> \[\]{}|^\w]+)/users$` | `/api/v1/users`, `/api/2.0/users` | `/api/users` |
256+
| `/files/<path>` | `^/files/(?P<path>[-._~()'!*:@,;=+&$%<> \[\]{}|^\w]+)$` | `/files/document.pdf`, `/files/folder%20name` | `/files/sub/folder/file.txt` |
257+
258+
???+ warning "Route Matching Behavior"
259+
* Routes are matched **exactly** - no partial matches
260+
* Dynamic parameters match **non-slash characters only** by default
261+
* For paths with slashes, use [catch-all routes](#catch-all-routes) instead
262+
263+
##### Advanced Examples
264+
265+
**Complex Parameter Names**
266+
267+
```python
268+
@app.get("/api/<api_version>/resources/<resource_type>/<resource_id>")
269+
def get_resource(api_version: str, resource_type: str, resource_id: str):
270+
# Matches: /api/v1/resources/users/123
271+
# api_version = "v1", resource_type = "users", resource_id = "123"
272+
return {
273+
"version": api_version,
274+
"type": resource_type,
275+
"id": resource_id
276+
}
277+
```
278+
279+
**Mixed Static and Dynamic Paths**
280+
281+
```python
282+
@app.get("/organizations/<org_id>/teams/<team_id>/members")
283+
def list_team_members(org_id: str, team_id: str):
284+
# Matches: /organizations/acme-corp/teams/engineering/members
285+
return {"org": org_id, "team": team_id, "action": "list_members"}
286+
```
287+
288+
**Handling Special Characters**
289+
290+
```python
291+
@app.get("/files/<filename>")
292+
def get_file(filename: str):
293+
# These all work:
294+
# /files/document.pdf → filename = "document.pdf"
295+
# /files/my-file_v2.txt → filename = "my-file_v2.txt"
296+
# /files/file%20with%20spaces → filename = "file%20with%20spaces"
297+
return {"filename": filename}
298+
```
299+
300+
???+ tip "Function Parameter Names Must Match"
301+
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.
302+
217303
#### Catch-all routes
218304

219305
???+ note
220306
We recommend having explicit routes whenever possible; use catch-all routes sparingly.
221307

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 `.+`.
308+
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.
223309

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

226-
???+ warning
227-
We choose the most explicit registered route that matches an incoming event.
312+
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:
313+
314+
| Pattern | Description | Examples |
315+
|---------|-------------|----------|
316+
| `.+` | Matches one or more characters (greedy) | `/proxy/.+` matches `/proxy/any/deep/path` |
317+
| `.*` | Matches zero or more characters (greedy) | `/files/.*` matches `/files/` and `/files/deep/path` |
318+
| `[^/]+` | Matches one or more non-slash characters | `/api/[^/]+` matches `/api/v1` but not `/api/v1/users` |
319+
| `\w+` | Matches one or more word characters | `/users/\w+` matches `/users/john123` |
320+
321+
**Common Regex Route Examples:**
322+
323+
```python
324+
# File path proxy - captures everything after /files/
325+
@app.get("/files/.+")
326+
def serve_file():
327+
file_path = app.current_event.path.replace("/files/", "")
328+
return {"file_path": file_path}
329+
330+
# API versioning with any format
331+
@app.get("/api/v\d+/.*") # Matches /api/v1/users, /api/v2/posts/123
332+
def handle_versioned_api():
333+
return {"api_version": "handled"}
334+
335+
# Catch-all for unmatched routes
336+
@app.route(".*", method=["GET", "POST"]) # Must be last route
337+
def catch_all():
338+
return {"message": "Route not found", "path": app.current_event.path}
339+
```
340+
341+
##### Combining Dynamic Parameters with Regex
342+
343+
```python
344+
# Mixed: dynamic parameter + regex catch-all
345+
@app.get("/users/<user_id>/files/.+")
346+
def get_user_files(user_id: str):
347+
file_path = app.current_event.path.split(f"/users/{user_id}/files/")[1]
348+
return {"user_id": user_id, "file_path": file_path}
349+
```
350+
351+
???+ warning "Route Matching Priority"
352+
* Routes are matched in **order of specificity**, not registration order
353+
* More specific routes (exact matches) take precedence over regex patterns
354+
* Among regex routes, the first registered matching route wins
355+
* Always place catch-all routes (`.*`) last
228356

229357
=== "dynamic_routes_catch_all.py"
230358

0 commit comments

Comments
 (0)