refactor: enforce strict architecture rules and add Pydantic response models
- Update architecture rules to be stricter (API-003 now blocks ALL exception raising in endpoints, not just HTTPException) - Update get_current_vendor_api dependency to guarantee token_vendor_id presence - Remove redundant _get_vendor_from_token helpers from all vendor API files - Move vendor access validation to service layer methods - Add Pydantic response models for media, notification, and payment endpoints - Add get_active_vendor_by_code service method for public vendor lookup - Add get_import_job_for_vendor service method with vendor validation - Update validation script to detect exception raising patterns in endpoints 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -75,17 +75,42 @@ api_endpoint_rules:
|
||||
# NOTE: db.commit() is intentionally NOT listed - it's allowed for transaction control
|
||||
|
||||
- id: "API-003"
|
||||
name: "Endpoint must NOT raise HTTPException directly"
|
||||
name: "Endpoint must NOT raise ANY exceptions directly"
|
||||
severity: "error"
|
||||
description: |
|
||||
API endpoints should NOT raise HTTPException directly. Instead, let domain
|
||||
exceptions bubble up to the global exception handler which converts them
|
||||
to appropriate HTTP responses. This ensures consistent error formatting
|
||||
and centralized error handling.
|
||||
API endpoints should NOT raise exceptions directly. Endpoints are a thin
|
||||
orchestration layer that:
|
||||
1. Accepts request parameters (validated by Pydantic)
|
||||
2. Calls dependencies for authentication/authorization (deps.py raises exceptions)
|
||||
3. Calls services for business logic (services raise domain exceptions)
|
||||
4. Returns response (formatted by Pydantic)
|
||||
|
||||
Exception raising belongs in:
|
||||
- Dependencies (app/api/deps.py) - authentication/authorization validation
|
||||
- Services (app/services/) - business logic validation
|
||||
|
||||
The global exception handler catches all WizamartException subclasses and
|
||||
converts them to appropriate HTTP responses.
|
||||
|
||||
WRONG (endpoint raises exception):
|
||||
@router.get("/orders")
|
||||
def get_orders(current_user: User = Depends(get_current_vendor_api)):
|
||||
if not hasattr(current_user, "token_vendor_id"): # ❌ Redundant check
|
||||
raise InvalidTokenException("...") # ❌ Don't raise here
|
||||
return order_service.get_orders(db, current_user.token_vendor_id)
|
||||
|
||||
RIGHT (dependency guarantees, endpoint trusts):
|
||||
@router.get("/orders")
|
||||
def get_orders(current_user: User = Depends(get_current_vendor_api)):
|
||||
# Dependency guarantees token_vendor_id is present
|
||||
return order_service.get_orders(db, current_user.token_vendor_id)
|
||||
pattern:
|
||||
file_pattern: "app/api/v1/**/*.py"
|
||||
anti_patterns:
|
||||
- "raise HTTPException"
|
||||
- "raise InvalidTokenException"
|
||||
- "raise InsufficientPermissionsException"
|
||||
- "if not hasattr\\(current_user.*token_vendor"
|
||||
exceptions:
|
||||
- "app/exceptions/handler.py" # Handler is allowed to use HTTPException
|
||||
|
||||
|
||||
Reference in New Issue
Block a user