p2c: add Request Body & Validation tutorial — Pydantic, Schema, body_schema, native path, constraints table

swadhinbiswas committed 3 months ago
949b1c7
1 parent 128ea1f
commit 949b1c7
1 file changed
+152 -0
▼docs_site/src/content/docs/tutorials/request-body.md
+152-0
1
+---
2
+title: Request Body & Validation
3
+description: Parse and validate JSON request bodies with Pydantic schemas and JustAPI's native fast path.
4
+---
5
+
6
+JustAPI validates request bodies against schemas defined with Pydantic models or the built-in `Schema` class. Validation runs in Rust by default, keeping the GIL released.
7
+
8
+## Using Pydantic Models
9
+
10
+```python
11
+from justapi import JustAPIApp
12
+from pydantic import BaseModel
13
+
14
+app = JustAPIApp()
15
+
16
+
17
+class Item(BaseModel):
18
+ name: str
19
+ description: str | None = None
20
+ price: float
21
+ tax: float | None = None
22
+
23
+
24
+@app.post("/items/")
25
+def create_item(request, item: Item):
26
+ item_dict = item.model_dump()
27
+ if item.tax:
28
+ item_dict["price_with_tax"] = item.price + item.tax
29
+ return item_dict
30
+```
31
+
32
+```bash
33
+curl -X POST http://127.0.0.1:8000/items/ \
34
+ -H "Content-Type: application/json" \
35
+ -d '{"name": "Laptop", "price": 999.99, "tax": 50.0}'
36
+# Output: {"name":"Laptop","description":null,"price":999.99,"tax":50.0,"price_with_tax":1049.99}
37
+```
38
+
39
+### Validation Errors
40
+
41
+Invalid data returns 422 with details:
42
+
43
+```bash
44
+curl -X POST http://127.0.0.1:8000/items/ \
45
+ -H "Content-Type: application/json" \
46
+ -d '{"name": "Laptop", "price": "free"}'
47
+# Output: {"detail":"validation error for price: invalid type"}
48
+```
49
+
50
+## Using `body_schema` for Rust-Side Validation
51
+
52
+For maximum performance, pass a schema explicitly via `body_schema`. Validation runs entirely in Rust before Python is called:
53
+
54
+```python
55
+from justapi import JustAPIApp, Schema
56
+from pydantic import Field
57
+
58
+
59
+class ProductCreate(Schema):
60
+ name: str = Field(..., min_length=1, max_length=100)
61
+ price: float = Field(..., gt=0, le=100000)
62
+ quantity: int = Field(0, ge=0)
63
+
64
+
65
+@app.post("/products/", body_schema=ProductCreate)
66
+def create_product(request):
67
+ data = request.json()
68
+ return {"status": "created", "product": data}
69
+```
70
+
71
+## The Native Fast Path (`native=True`)
72
+
73
+For the absolute fastest path (**724,000+ RPS**), use `native=True`. The entire handler runs in Rust — no Python bytecode execution:
74
+
75
+```python
76
+@app.post("/fast-items/", body_schema=ProductCreate, native=True)
77
+def fast_create(request):
78
+ return {"status": "ok"}
79
+```
80
+
81
+### Performance Comparison
82
+
83
+| Approach | RPS | p50 Latency |
84
+|---|---|---|
85
+| Python handler + Pydantic | ~60,000 | 0.8 ms |
86
+| `body_schema` (Rust validates) | ~280,000 | 0.2 ms |
87
+| `native=True` (full Rust) | ~724,000 | 0.07 ms |
88
+
89
+## Using JustAPI's Native `Schema` Class
90
+
91
+The built-in `Schema` class supports field constraints with `Field()`:
92
+
93
+```python
94
+from justapi import JustAPIApp, Schema
95
+from pydantic import Field
96
+
97
+
98
+class UserCreate(Schema):
99
+ username: str = Field(..., min_length=3, max_length=50, regex=r"^\w+$")
100
+ email: str = Field(..., format="email")
101
+ age: int = Field(..., ge=18, le=120)
102
+ role: str = Field("user", enum=["user", "admin", "moderator"])
103
+```
104
+
105
+### Supported Field Constraints
106
+
107
+| Constraint | Type | Description |
108
+|---|---|---|
109
+| `gt` | number | Greater than |
110
+| `ge` | number | Greater than or equal |
111
+| `lt` | number | Less than |
112
+| `le` | number | Less than or equal |
113
+| `min_length` | int | Minimum string length |
114
+| `max_length` | int | Maximum string length |
115
+| `regex` | str | Regex pattern match |
116
+| `format` | str | Named format (`email`, `uri`, `uuid`, `date`, `date-time`, `hostname`, `ipv4`) |
117
+| `enum` | list | Allowed values |
118
+| `default` | any | Default value |
119
+| `description` | str | Field description for OpenAPI |
120
+
121
+## Nested Models
122
+
123
+```python
124
+class Address(Schema):
125
+ street: str
126
+ city: str
127
+ zip_code: str = Field(..., regex=r"^\d{5}(-\d{4})?$")
128
+
129
+
130
+class Customer(Schema):
131
+ name: str
132
+ address: Address
133
+ tags: list[str]
134
+```
135
+
136
+The nested `Address` schema is lifted into `$defs` and referenced via `$ref` in the generated JSON Schema. Validation runs entirely in Rust.
137
+
138
+## Request Body Size Limits
139
+
140
+By default, request bodies are limited to 50 MiB. You can configure this:
141
+
142
+```python
143
+app.run("0.0.0.0:8000", max_body_size=1024 * 1024) # 1 MiB cap
144
+```
145
+
146
+Bodies exceeding the limit receive **413 Payload Too Large** immediately.
147
+
148
+## Next Steps
149
+
150
+- [Dependency Injection](/tutorials/dependency-injection/) — Access request components with Depends
151
+- [Error Handling](/tutorials/error-handling/) — Custom validation error responses
152
+- [Native Fast Path](/advanced/native-fast-path/) — Deep dive into Rust-native execution
0
153