Implement Phase 9: client libraries (Go, Rust, Lisp, Python)
- clients/README.md: canonical API surface and error type reference - clients/testdata/: shared JSON response fixtures - clients/go/: mciasgoclient package; net/http + TLS 1.2+; sync.RWMutex token state; DisallowUnknownFields on all decoders; 25 tests pass - clients/rust/: async mcias-client crate; reqwest+rustls (no OpenSSL); thiserror MciasError enum; Arc<RwLock> token state; 22+1 tests pass; cargo clippy -D warnings clean - clients/lisp/: ASDF mcias-client; dexador HTTP, yason JSON; mcias-error condition hierarchy; Hunchentoot mock-dispatcher; 37 fiveam checks pass on SBCL 2.6.1; yason boolean normalisation in validate-token - clients/python/: mcias_client package (Python 3.11+); httpx sync; py.typed; dataclasses; 32 pytest tests; mypy --strict + ruff clean - test/mock/mockserver.go: in-memory mock server for Go client tests - ARCHITECTURE.md §19: updated per-language notes to match implementation - PROGRESS.md: Phase 9 marked complete - .gitignore: exclude clients/rust/target/, python .venv, .pytest_cache, .fasl files Security: token never logged or exposed in error messages in any library; TLS enforced in all four languages; token stored under lock/mutex/RwLock
This commit is contained in:
30
clients/python/mcias_client/_errors.py
Normal file
30
clients/python/mcias_client/_errors.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""Typed exception hierarchy for MCIAS client errors."""
|
||||
class MciasError(Exception):
|
||||
"""Base exception for all MCIAS API errors."""
|
||||
def __init__(self, status_code: int, message: str) -> None:
|
||||
super().__init__(f"HTTP {status_code}: {message}")
|
||||
self.status_code = status_code
|
||||
self.message = message
|
||||
class MciasAuthError(MciasError):
|
||||
"""401 Unauthorized — token missing, invalid, or expired."""
|
||||
class MciasForbiddenError(MciasError):
|
||||
"""403 Forbidden — insufficient role."""
|
||||
class MciasNotFoundError(MciasError):
|
||||
"""404 Not Found — resource does not exist."""
|
||||
class MciasInputError(MciasError):
|
||||
"""400 Bad Request — malformed request."""
|
||||
class MciasConflictError(MciasError):
|
||||
"""409 Conflict — e.g. duplicate username."""
|
||||
class MciasServerError(MciasError):
|
||||
"""5xx — unexpected server error."""
|
||||
def raise_for_status(status_code: int, message: str) -> None:
|
||||
"""Raise the appropriate MciasError subclass for the given status code."""
|
||||
exc_map = {
|
||||
400: MciasInputError,
|
||||
401: MciasAuthError,
|
||||
403: MciasForbiddenError,
|
||||
404: MciasNotFoundError,
|
||||
409: MciasConflictError,
|
||||
}
|
||||
cls = exc_map.get(status_code, MciasServerError)
|
||||
raise cls(status_code, message)
|
||||
Reference in New Issue
Block a user