refactor(core): enhance SDK structure and documentation
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
61
src/Http/Middleware/AlfaResponseMiddleware.php
Normal file
61
src/Http/Middleware/AlfaResponseMiddleware.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/*
|
||||
// MODULE_CONTRACT: Provides a Saloon middleware to standardize error handling for Alfa Bank API responses.
|
||||
// RESPONSIBILITY: To intercept API responses, identify various error formats (BaaS, SBP, or generic HTTP), and throw a consistent `AlfaBankException`, thereby centralizing error reporting and simplifying error management in the SDK.
|
||||
// INPUTS: Saloon `Response` object.
|
||||
// OUTPUTS: Throws an `AlfaBankException` if an error is detected; otherwise, allows the response to pass through unmodified.
|
||||
// LINKS: Exceptions/AlfaBankException.php, Core/AlfaBankConnector.php, Sbp/SbpConnector.php
|
||||
*/
|
||||
|
||||
namespace Apoloz\AlfaBank\Http\Middleware;
|
||||
|
||||
// <SECTION name="module_imports" intent="Import necessary classes for custom exception and Saloon Response.">
|
||||
// <BELIEF_STATE "These imports are crucial for the middleware's functionality. `AlfaBankException` provides a standardized way to signal API errors within the SDK, while `Response` from Saloon is the object that the middleware processes.">
|
||||
use Apoloz\AlfaBank\Exceptions\AlfaBankException;
|
||||
use Saloon\Http\Response;
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define the Alfa Bank API response error handling middleware.">
|
||||
// <BELIEF_STATE "The `AlfaResponseMiddleware` is a critical component for robust error handling. It acts as an intermediary, inspecting every API response to detect specific error patterns from both BaaS and SBP endpoints, as well as generic HTTP failures. By consolidating diverse error indicators into a single, predictable `AlfaBankException`, it significantly streamlines error management for consumers of the SDK.">
|
||||
class AlfaResponseMiddleware
|
||||
{
|
||||
// <SECTION name="invoke_method" intent="Process the API response for errors.">
|
||||
// <BELIEF_STATE "The `__invoke` method is the entry point for this middleware, allowing it to be used as a callable. It systematically checks the response JSON for known error structures: first for standard BaaS errors (`error`), then for SBP-specific errors (`ErrorCode`), and finally for any generic HTTP failures. If an error is found, it constructs and throws an `AlfaBankException`, providing comprehensive context about the failure.">
|
||||
public function __invoke(Response $response): void
|
||||
{
|
||||
$contentType = $response->header('Content-Type') ?? '';
|
||||
|
||||
if (str_contains($contentType, 'application/json')) {
|
||||
$data = $response->json();
|
||||
|
||||
// 1. Handle standard BaaS errors
|
||||
if (isset($data['error'])) {
|
||||
throw new AlfaBankException(
|
||||
$response,
|
||||
$data['error'],
|
||||
$data['error_description'] ?? 'No description'
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Handle SBP specific errors
|
||||
if (isset($data['ErrorCode']) && (string)$data['ErrorCode'] !== '0') {
|
||||
throw new AlfaBankException(
|
||||
$response,
|
||||
(string)$data['ErrorCode'],
|
||||
$data['message'] ?? 'No message'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Handle generic HTTP errors without body or non-json body
|
||||
if ($response->failed()) {
|
||||
throw new AlfaBankException(
|
||||
$response,
|
||||
'HTTP_' . $response->status(),
|
||||
'Request failed with status ' . $response->status()
|
||||
);
|
||||
}
|
||||
}
|
||||
// </SECTION>
|
||||
}
|
||||
// </SECTION>
|
||||
79
src/Http/Middleware/LoggingMiddleware.php
Normal file
79
src/Http/Middleware/LoggingMiddleware.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/*
|
||||
// MODULE_CONTRACT: Provides a Saloon middleware for logging API requests and responses, with sensitive data masking.
|
||||
// RESPONSIBILITY: To intercept and log outgoing API requests and incoming API responses, providing visibility into the communication flow for debugging and monitoring purposes, while also sanitizing sensitive information from logs to maintain security and compliance.
|
||||
// INPUTS: Psr\Log\LoggerInterface for logging; Saloon `Request` and `Response` objects.
|
||||
// OUTPUTS: Logs detailed request and response information (with masked sensitive data) to the configured logger.
|
||||
// LINKS: Core/AlfaBankConnector.php, Sbp/SbpConnector.php, README.md#logging
|
||||
*/
|
||||
|
||||
namespace Apoloz\AlfaBank\Http\Middleware;
|
||||
|
||||
// <SECTION name="module_imports" intent="Import necessary classes for logging and Saloon HTTP components.">
|
||||
// <BELIEF_STATE "These imports are fundamental for the logging middleware. `LoggerInterface` from PSR-3 defines the contract for logging, allowing flexibility in the choice of logging implementation. `Request` and `Response` from Saloon are the HTTP objects that this middleware will observe and log, providing insights into the API communication lifecycle.">
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Saloon\Http\PendingRequest;
|
||||
use Saloon\Http\Response;
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define the API request and response logging middleware.">
|
||||
// <BELIEF_STATE "The `LoggingMiddleware` plays a crucial role in the observability of the SDK. By implementing `onRequest` and `onResponse` methods, it captures detailed information about HTTP interactions, including headers and body. Crucially, it incorporates sensitive data masking to prevent credentials and personal information from being exposed in logs, balancing transparency with security and compliance requirements.">
|
||||
class LoggingMiddleware
|
||||
{
|
||||
// <SECTION name="constructor_definition" intent="Initialize the middleware with a logger instance.">
|
||||
// <BELIEF_STATE "The constructor injects a `LoggerInterface` instance, making the logging functionality easily testable and configurable. This adheres to dependency inversion principles, allowing the middleware to log messages without being tightly coupled to a specific logging implementation.">
|
||||
public function __construct(protected LoggerInterface $logger) {}
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="on_request_method" intent="Log outgoing API requests.">
|
||||
// <BELIEF_STATE "The `onRequest` method intercepts each outgoing HTTP request. It logs the request method and endpoint, along with masked headers and body content. Masking of sensitive information like authorization headers and payment data is performed by `maskHeaders` and `maskBody` methods, ensuring that debugging logs do not compromise security.">
|
||||
public function onRequest(PendingRequest $request): void
|
||||
{
|
||||
$this->logger->info("AlfaBank Request: {$request->getMethod()->value} {$request->getRequest()->resolveEndpoint()}", [
|
||||
'headers' => $this->maskHeaders($request->headers()->all()),
|
||||
'body' => $this->maskBody($request->body()?->all()),
|
||||
]);
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="on_response_method" intent="Log incoming API responses.">
|
||||
// <BELIEF_STATE "The `onResponse` method intercepts each incoming HTTP response. It logs the response status and the masked response body. This provides valuable information for debugging API integrations and understanding the server's responses, all while maintaining the privacy of potentially sensitive data within the response payload.">
|
||||
public function onResponse(Response $response): void
|
||||
{
|
||||
$this->logger->info("AlfaBank Response: {$response->status()}", [
|
||||
'body' => $this->maskBody($response->json()),
|
||||
]);
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="mask_headers_method" intent="Mask sensitive information in HTTP headers.">
|
||||
// <BELIEF_STATE "This protected helper method iterates through the request headers and replaces the `Authorization` header's value with a generic placeholder. This prevents sensitive tokens from being inadvertently exposed in logs, which is a crucial security measure.">
|
||||
protected function maskHeaders(array $headers): array
|
||||
{
|
||||
if (isset($headers['Authorization'])) {
|
||||
$headers['Authorization'] = 'Bearer *****';
|
||||
}
|
||||
return $headers;
|
||||
}
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="mask_body_method" intent="Mask sensitive information in request/response bodies.">
|
||||
// <BELIEF_STATE "This protected recursive helper method inspects the request or response body for predefined sensitive keys (e.g., `pan`, `client_secret`). If a sensitive key is found, its value is replaced with a mask. The method recursively processes nested arrays, ensuring that sensitive data is masked even within complex JSON structures, thereby enhancing log security.">
|
||||
protected function maskBody(mixed $body): mixed
|
||||
{
|
||||
if (!is_array($body)) return $body;
|
||||
|
||||
$sensitiveKeys = ['pan', 'cvc', 'digestSignatures', 'client_secret', 'refresh_token', 'access_token'];
|
||||
|
||||
foreach ($body as $key => $value) {
|
||||
if (in_array($key, $sensitiveKeys, true)) {
|
||||
$body[$key] = '*****';
|
||||
} elseif (is_array($value)) {
|
||||
$body[$key] = $this->maskBody($value);
|
||||
}
|
||||
}
|
||||
return $body;
|
||||
}
|
||||
// </SECTION>
|
||||
}
|
||||
// </SECTION>
|
||||
32
src/Http/Middleware/RequestIdMiddleware.php
Normal file
32
src/Http/Middleware/RequestIdMiddleware.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/*
|
||||
// MODULE_CONTRACT: Provides a Saloon middleware to ensure each API request includes a unique `X-Request-ID` header.
|
||||
// RESPONSIBILITY: To automatically generate and attach a UUID-based `X-Request-ID` header to outgoing API requests if one is not already present, thereby facilitating request tracking, idempotency, and debugging across distributed systems.
|
||||
// INPUTS: Saloon `PendingRequest` object.
|
||||
// OUTPUTS: Modified `PendingRequest` with an `X-Request-ID` header.
|
||||
// LINKS: Core/AlfaBankConnector.php, Sbp/SbpConnector.php, README.md#request-tracking
|
||||
*/
|
||||
|
||||
namespace Apoloz\AlfaBank\Http\Middleware;
|
||||
|
||||
// <SECTION name="module_imports" intent="Import necessary classes for Saloon PendingRequest.">
|
||||
// <BELIEF_STATE "These imports are critical for the middleware's operation. `PendingRequest` from Saloon represents the request being prepared for dispatch, allowing modification before sending.">
|
||||
use Apoloz\AlfaBank\Utils\Uuid;
|
||||
use Saloon\Http\PendingRequest;
|
||||
// </SECTION>
|
||||
|
||||
// <SECTION name="class_definition" intent="Define the Request ID generation middleware.">
|
||||
// <BELIEF_STATE "The `RequestIdMiddleware` is a lightweight yet crucial component for API observability and idempotency. It ensures that every outgoing request is uniquely identifiable through an `X-Request-ID` header. This is invaluable for tracing requests through complex microservice architectures, debugging issues, and preventing duplicate processing on the server-side, adhering to best practices for API communication.">
|
||||
class RequestIdMiddleware
|
||||
{
|
||||
// <SECTION name="invoke_method" intent="Add a unique X-Request-ID header to the pending request.">
|
||||
// <BELIEF_STATE "The `__invoke` method is executed for each outgoing API request. It first checks if an `X-Request-ID` header already exists. If not, it generates a new unique ID and adds it to the request headers. This conditional logic ensures that existing IDs are respected while guaranteeing that every request has a unique identifier, making request tracking reliable and automatic.">
|
||||
public function __invoke(PendingRequest $pendingRequest): void
|
||||
{
|
||||
if (!$pendingRequest->headers()->get('X-Request-ID')) {
|
||||
$pendingRequest->headers()->add('X-Request-ID', Uuid::v4());
|
||||
}
|
||||
}
|
||||
// </SECTION>
|
||||
}
|
||||
// </SECTION>
|
||||
Reference in New Issue
Block a user