Repository navigation
Expand file tree
/
Copy pathErrorMiddleware.php
More file actions
99 lines (90 loc) · 4.05 KB
/
Copy pathErrorMiddleware.php
File metadata and controls
99 lines (90 loc) · 4.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
<?php
declare(strict_types=1);
namespace TinyBlocks\Http\ErrorHandler;
use Closure;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerInterface;
use Throwable;
use TinyBlocks\Http\Code;
use TinyBlocks\Http\ErrorHandler\Internal\DefaultErrorMiddlewareBuilder;
use TinyBlocks\Http\ErrorHandler\Internal\ErrorLogger;
use TinyBlocks\Http\ErrorHandler\Internal\ErrorOutcome;
use TinyBlocks\Http\ErrorHandler\Internal\Reporting\CompositeErrorReporter;
use TinyBlocks\Http\ErrorHandler\Internal\ResolvedRoute;
use TinyBlocks\Http\ErrorHandler\Reporters\SilentErrorReporter;
/**
* PSR-15 middleware that captures exceptions thrown downstream, delegates to a consumer-provided
* {@see ExceptionMapping}, and produces a structured JSON error response. Unmapped exceptions either
* fall back to a 500 response or rethrow, depending on the builder configuration.
*/
final readonly class ErrorMiddleware implements MiddlewareInterface
{
private function __construct(private ErrorOutcome $errorOutcome)
{
}
/**
* Builds an ErrorMiddleware from its configuration components.
*
* @param LoggerInterface|null $logger The logger to use for error logging, or <code>null</code> to disable logging.
* @param Closure(ErrorPayload, ServerRequestInterface): string $message The rule settling the message the
* response answers with.
* @param ExceptionMappingTable $mappings The composed table that maps exceptions to error responses.
* @param Closure(?MappedError, Code): ReportingPriority $priority The rule deriving a priority from the rule
* that matched and the resolved status.
* @param ErrorHandlingSettings $settings The settings controlling error display and logging behavior.
* @param bool $fallbackOnUnmapped Whether to return a fallback response when no mapping matches.
* @param ErrorReporter $reporter The reporter notified after the response is produced and logged. Defaults to
* {@see SilentErrorReporter}, which forwards every report to no provider.
* @return ErrorMiddleware The configured middleware instance.
*/
public static function build(
?LoggerInterface $logger,
Closure $message,
ExceptionMappingTable $mappings,
Closure $priority,
ErrorHandlingSettings $settings,
bool $fallbackOnUnmapped,
ErrorReporter $reporter = new SilentErrorReporter()
): ErrorMiddleware {
$errorOutcome = new ErrorOutcome(
message: $message,
mappings: $mappings,
priority: $priority,
settings: $settings,
errorLogger: ErrorLogger::from(logger: $logger, settings: $settings),
errorReporter: CompositeErrorReporter::create()->with(reporter: $reporter),
fallbackOnUnmapped: $fallbackOnUnmapped
);
return new ErrorMiddleware(errorOutcome: $errorOutcome);
}
/**
* Creates an ErrorMiddlewareBuilder for fluent configuration.
*
* @return ErrorMiddlewareBuilder The builder instance.
*/
public static function create(): ErrorMiddlewareBuilder
{
return new DefaultErrorMiddlewareBuilder();
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$tags = ReportingTags::pending();
$route = ResolvedRoute::pending();
$routed = $request
->withAttribute(ResolvedRoute::ATTRIBUTE_NAME, $route)
->withAttribute(ReportingTags::ATTRIBUTE_NAME, $tags);
try {
return $handler->handle($routed);
} catch (Throwable $exception) {
return $this->errorOutcome->resolve(
tags: $tags,
route: $route,
request: $routed,
exception: $exception
);
}
}
}