No canonical snake_case/camelCase serialization policy

This commit is contained in:
2026-04-28 21:27:26 +02:00
parent b27765928a
commit ad21590f60
14 changed files with 186 additions and 475 deletions

View File

@@ -465,18 +465,14 @@ class BansByCountryResponse(BaseModel):
## 5. Pydantic Models
- Every model inherits from `pydantic.BaseModel`.
- Use `model_config = ConfigDict(strict=True)` where appropriate.
- Field names use **snake_case** in Python, export as **camelCase** to the frontend via alias generators if needed.
- Validate at the boundary — once data enters a Pydantic model it is trusted.
- Use `Field(...)` with descriptions for every field to keep auto-generated docs useful.
- Separate **request models**, **response models**, and **domain (internal) models** — do not reuse one model for all three.
### Base Class
Every model in `app/models/` **must** inherit from `BanGuiBaseModel` (defined in `app/models/response.py`), not from `pydantic.BaseModel` directly.
```python
from pydantic import BaseModel, Field
from datetime import datetime
from app.models.response import BanGuiBaseModel
class BanResponse(BaseModel):
class BanResponse(BanGuiBaseModel):
ip: str = Field(..., description="Banned IP address")
jail: str = Field(..., description="Jail that issued the ban")
banned_at: datetime = Field(..., description="UTC timestamp of the ban")
@@ -484,6 +480,24 @@ class BanResponse(BaseModel):
ban_count: int = Field(..., ge=1, description="Number of times this IP was banned")
```
`BanGuiBaseModel` sets `strict=True` and documents the naming policy. Do **not** override `model_config` on individual models unless you have a specific, documented reason.
### API Field Naming Policy — snake_case everywhere
All API field names use **`snake_case`** in Python, in the JSON wire format, and in the corresponding TypeScript interfaces. There is no `alias_generator` that converts to camelCase.
- ✅ Python field: `active_jails` → JSON key: `"active_jails"` → TypeScript property: `active_jails`
- ❌ Do **not** add a camelCase `alias_generator` to individual models.
- ❌ Do **not** mix field name conventions within a single API response.
This policy eliminates a whole class of frontendbackend contract bugs. If the naming policy ever needs to change (e.g. to emit camelCase), change `BanGuiBaseModel` once — all models update automatically.
### Other Model Rules
- Validate at the boundary — once data enters a Pydantic model it is trusted.
- Use `Field(...)` with descriptions for every field to keep auto-generated docs useful.
- Separate **request models**, **response models**, and **domain (internal) models** — do not reuse one model for all three.
### Using `Literal` Types for Constrained Strings
When a field should only accept a small set of predefined values, use `Literal` to enforce this at the type level:

View File

@@ -1,24 +1,3 @@
## 24) API response wrapper shape is inconsistent
- Where found:
- [backend/app/routers/dashboard.py](backend/app/routers/dashboard.py)
- [backend/app/routers/jails.py](backend/app/routers/jails.py)
- [frontend/src/types](frontend/src/types)
- Why this is needed:
- Inconsistent payload envelopes increase frontend branching and integration bugs.
- Goal:
- Define and enforce a consistent response envelope policy.
- What to do:
- Standardize endpoint response forms.
- Align frontend typing and parsing strategy.
- Possible traps and issues:
- Breaking contract for existing clients.
- Docs changes needed:
- Add API response style guide.
- Doc references:
- [Docs/Backend-Development.md](Docs/Backend-Development.md)
---
## 25) No canonical snake_case/camelCase serialization policy
- Where found:
- [backend/app/models/server.py](backend/app/models/server.py)

View File

@@ -966,6 +966,15 @@ This pattern prevents **stale session flicker** — the brief moment when a user
| Directories | lowercase kebabcase or camelCase | `components/`, `hooks/` |
| Boolean props/variables | `is`/`has`/`should` prefix | `isLoading`, `hasError` |
### API Field Names — snake_case
All TypeScript interface properties that mirror backend API responses use **`snake_case`**, not `camelCase`. This matches the JSON wire format emitted by the backend without any transformation layer.
- ✅ `active_jails`, `total_bans`, `log_level`, `db_purge_age`
- ❌ `activeJails`, `totalBans`, `logLevel`, `dbPurgeAge` (do **not** use camelCase for API shapes)
This applies to all interfaces in `src/types/`. Internal component state, props, and hook return values use `camelCase` as normal TypeScript convention.
---
## 9. Linting & Formatting