TASK-015: Add validation for GlobalConfigUpdate.log_target and log_level

- Add LogLevel Literal type: CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG
- Add log_target validation to accept special values (STDOUT, STDERR, SYSLOG)
  or validated file paths within allowed directories
- Update GlobalConfigResponse to use LogLevel type
- Add field_validator for log_target in both GlobalConfigUpdate and
  GlobalConfigResponse following the same pattern as AddLogPathRequest
- Add @autouse fixture to test_config_service.py to mock get_settings
- Update existing tests to use uppercase log level values
- Add 12 comprehensive tests for new validation in test_models.py
- Update Features.md to document valid log_target and log_level values
- Add section to Backend-Development.md documenting Literal types and
  field_validator patterns with examples

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-04-26 13:57:22 +02:00
parent b9e046bd66
commit d66493f135
6 changed files with 306 additions and 45 deletions

View File

@@ -191,6 +191,56 @@ class BanResponse(BaseModel):
ban_count: int = Field(..., ge=1, description="Number of times this IP was banned")
```
### 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:
```python
from typing import Literal
from pydantic import BaseModel, Field
LogLevel = Literal["CRITICAL", "ERROR", "WARNING", "NOTICE", "INFO", "DEBUG"]
class GlobalConfigUpdate(BaseModel):
log_level: LogLevel | None = Field(
default=None,
description="Log level: CRITICAL, ERROR, WARNING, NOTICE, INFO, or DEBUG.",
)
```
This provides:
- **Type safety** — IDEs and type checkers enforce valid values.
- **API documentation** — OpenAPI docs automatically list all allowed values.
- **Validation** — Pydantic rejects invalid values and provides a clear error message.
### Custom Field Validators
For fields that require complex validation (e.g., file paths that must be within allowed directories), use `@field_validator`:
```python
from pydantic import field_validator
class AddLogPathRequest(BaseModel):
log_path: str = Field(..., description="Absolute path to the log file to monitor.")
@field_validator("log_path", mode="after")
@classmethod
def validate_log_path(cls, value: str) -> str:
"""Validate that the log path is within allowed directories."""
settings = get_settings()
resolved_path = Path(value).resolve()
for allowed_dir in settings.allowed_log_dirs:
if resolved_path.is_relative_to(Path(allowed_dir).resolve()):
return value
raise ValueError(f"Path {value!r} is outside allowed directories")
```
**Key points:**
- Use `mode="after"` to validate after Pydantic's basic type coercion.
- Raise `ValueError` if validation fails; Pydantic converts it to an HTTP 400 response.
- **Never use string prefix matching** for path validation (e.g., `path.startswith("/var/log")`). Use `Path.is_relative_to()` to avoid bypasses like `/var/log_evil/file.log`.
- Resolve symlinks before validating to prevent symlink-based escapes.
---
## 6. Async Rules