from collections.abc import Mapping from typing import Any from ..api_config import APIEndpointRegistry from ..request import APIClient, APIClientError, APIResponseFormatError class APIAdapterError(APIClientError): """Adapter 层的结构化异常。""" class APIBusinessError(APIAdapterError): """HTTP 请求成功,但上游接口返回业务错误。""" def __init__( self, message: str, *, endpoint: str, code: Any = None, ): super().__init__(message) self.endpoint = endpoint self.code = code class EndpointAdapter: """把端点配置转换为 HTTP 请求;子类只处理各上游的协议差异。""" def __init__(self, client: APIClient, registry: APIEndpointRegistry): self._client = client self._registry = registry async def request( self, endpoint_name: str, *, params: Mapping[str, Any] | None = None, out_key: str | None = "data", path_params: Mapping[str, Any] | None = None, ) -> Any: endpoint = self._registry.get(endpoint_name) request_params = self.prepare_params(endpoint, params) url = self._registry.resolve(endpoint_name, path_params) configured_method = str(endpoint.get("method", "GET")).upper() if configured_method == "POST": payload = await self._client.post( url, data=request_params, retry=bool(endpoint.get("retry", False)), ) else: payload = await self._client.get( url, params=request_params, retry=bool(endpoint.get("retry", True)), ) payload = self.validate_payload(endpoint_name, payload) return self.extract_payload(payload, out_key) def prepare_params( self, endpoint: Mapping[str, Any], params: Mapping[str, Any] | None, ) -> dict[str, Any]: request_params = dict(endpoint.get("params", {})) if params: request_params.update(params) return request_params def validate_payload(self, endpoint_name: str, payload: Any) -> Any: return payload def extract_payload(self, payload: Any, out_key: str | None) -> Any: if out_key in (None, ""): return payload if isinstance(payload, bytes): raise APIResponseFormatError("二进制响应无法提取 JSON 字段") if not isinstance(payload, Mapping): raise APIResponseFormatError(f"响应不是对象,无法提取字段: {out_key}") if out_key not in payload: raise APIResponseFormatError(f"响应缺少字段: {out_key}") return payload[out_key]