1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace Usox\JsonSchemaApi; |
||
6 | |||
7 | use Http\Discovery\Psr17FactoryDiscovery; |
||
8 | use Opis\JsonSchema\Errors\ErrorFormatter; |
||
9 | use Opis\JsonSchema\Validator; |
||
10 | use Psr\Http\Message\ResponseFactoryInterface; |
||
11 | use Psr\Http\Message\ResponseInterface; |
||
12 | use Psr\Http\Message\ServerRequestInterface; |
||
13 | use Psr\Http\Message\StreamFactoryInterface; |
||
14 | use Psr\Log\LoggerInterface; |
||
15 | use Ramsey\Uuid\UuidFactory; |
||
16 | use Ramsey\Uuid\UuidFactoryInterface; |
||
17 | use Ramsey\Uuid\UuidInterface; |
||
18 | use Teapot\StatusCode\Http; |
||
19 | use Throwable; |
||
20 | use Usox\JsonSchemaApi\Contract\MethodProviderInterface; |
||
21 | use Usox\JsonSchemaApi\Dispatch\MethodDispatcher; |
||
22 | use Usox\JsonSchemaApi\Dispatch\MethodDispatcherInterface; |
||
23 | use Usox\JsonSchemaApi\Dispatch\MethodValidator; |
||
24 | use Usox\JsonSchemaApi\Dispatch\RequestValidator; |
||
25 | use Usox\JsonSchemaApi\Dispatch\RequestValidatorInterface; |
||
26 | use Usox\JsonSchemaApi\Dispatch\SchemaLoader; |
||
27 | use Usox\JsonSchemaApi\Exception\ApiException; |
||
28 | use Usox\JsonSchemaApi\Exception\InternalException; |
||
29 | use Usox\JsonSchemaApi\Response\ResponseBuilder; |
||
30 | use Usox\JsonSchemaApi\Response\ResponseBuilderInterface; |
||
31 | |||
32 | /** |
||
33 | * @see Endpoint::factory() |
||
34 | */ |
||
35 | final readonly class Endpoint implements |
||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
36 | EndpointInterface |
||
37 | { |
||
38 | 8 | public function __construct( |
|
39 | private RequestValidatorInterface $inputValidator, |
||
40 | private MethodDispatcherInterface $methodRetriever, |
||
41 | private ResponseBuilderInterface $responseBuilder, |
||
42 | private UuidFactoryInterface $uuidFactory, |
||
43 | private StreamFactoryInterface $streamFactory, |
||
44 | private ResponseFactoryInterface $responseFactory, |
||
45 | private ?LoggerInterface $logger = null, |
||
46 | ) { |
||
47 | 8 | } |
|
48 | |||
49 | /** |
||
50 | * Execute the api handler and build the response |
||
51 | */ |
||
52 | 6 | public function serve( |
|
53 | ServerRequestInterface $request, |
||
54 | ): ResponseInterface { |
||
55 | 6 | $statusCode = Http::OK; |
|
56 | 6 | $responseData = null; |
|
57 | |||
58 | try { |
||
59 | // Process and build the response |
||
60 | 6 | $responseData = $this->responseBuilder->buildResponse( |
|
61 | 6 | $this->methodRetriever->dispatch( |
|
62 | 6 | $request, |
|
63 | 6 | $this->inputValidator->validate($request), |
|
64 | 6 | ), |
|
65 | 6 | ); |
|
66 | 4 | } catch (ApiException $e) { |
|
67 | 2 | $uuid = $this->uuidFactory->uuid4(); |
|
68 | |||
69 | 2 | $this->logError($e, $uuid); |
|
70 | |||
71 | // Build an error response |
||
72 | 2 | $responseData = $this->responseBuilder->buildErrorResponse($e, $uuid); |
|
73 | |||
74 | 2 | $statusCode = Http::BAD_REQUEST; |
|
75 | 2 | } catch (InternalException $e) { |
|
76 | 1 | $this->logError( |
|
77 | 1 | $e, |
|
78 | 1 | $this->uuidFactory->uuid4(), |
|
79 | 1 | $e->getContext(), |
|
80 | 1 | ); |
|
81 | |||
82 | 1 | $statusCode = Http::INTERNAL_SERVER_ERROR; |
|
83 | 1 | } catch (Throwable $e) { |
|
84 | 1 | $this->logError( |
|
85 | 1 | $e, |
|
86 | 1 | $this->uuidFactory->uuid4(), |
|
87 | 1 | ); |
|
88 | |||
89 | 1 | $statusCode = Http::INTERNAL_SERVER_ERROR; |
|
90 | } |
||
91 | |||
92 | 6 | $response = $this->responseFactory->createResponse($statusCode); |
|
93 | |||
94 | 6 | if ($responseData !== null) { |
|
95 | 4 | $response = $response->withBody( |
|
96 | 4 | $this->streamFactory->createStream( |
|
97 | 4 | (string) json_encode($responseData), |
|
98 | 4 | ), |
|
99 | 4 | ); |
|
100 | } |
||
101 | |||
102 | 6 | return $response |
|
103 | 6 | ->withHeader('Content-Type', 'application/json') |
|
104 | 6 | ; |
|
105 | } |
||
106 | |||
107 | /** |
||
108 | * @param array<mixed, mixed> $context |
||
109 | */ |
||
110 | 4 | private function logError( |
|
111 | Throwable $e, |
||
112 | UuidInterface $uuid, |
||
113 | array $context = [], |
||
114 | ): void { |
||
115 | 4 | $this->logger?->error( |
|
116 | 4 | sprintf('%s (%d)', $e->getMessage(), $e->getCode()), |
|
117 | 4 | [ |
|
118 | 4 | 'id' => $uuid->toString(), |
|
119 | 4 | 'file' => $e->getFile(), |
|
120 | 4 | 'line' => $e->getLine(), |
|
121 | 4 | 'context' => $context, |
|
122 | 4 | ], |
|
123 | 4 | ); |
|
124 | } |
||
125 | |||
126 | /** |
||
127 | * Builds the endpoint. |
||
128 | * |
||
129 | * The factories may be omitted, the endpoint will try to autodetect existing PSR17 implementations |
||
130 | */ |
||
131 | 2 | public static function factory( |
|
132 | MethodProviderInterface $methodProvider, |
||
133 | ?StreamFactoryInterface $streamFactory = null, |
||
134 | ?ResponseFactoryInterface $responseFactory = null, |
||
135 | ?LoggerInterface $logger = null, |
||
136 | ): EndpointInterface { |
||
137 | 2 | $schemaValidator = new Validator(); |
|
138 | 2 | $schemaLoader = new SchemaLoader(); |
|
139 | |||
140 | 2 | if ($streamFactory === null) { |
|
141 | 1 | $streamFactory = Psr17FactoryDiscovery::findStreamFactory(); |
|
142 | } |
||
143 | 2 | ||
144 | 1 | if ($responseFactory === null) { |
|
145 | $responseFactory = Psr17FactoryDiscovery::findResponseFactory(); |
||
146 | } |
||
147 | 2 | ||
148 | 2 | return new self( |
|
149 | 2 | new RequestValidator( |
|
150 | 2 | $schemaLoader, |
|
151 | 2 | $schemaValidator, |
|
152 | 2 | ), |
|
153 | 2 | new MethodDispatcher( |
|
154 | 2 | $schemaLoader, |
|
155 | 2 | new MethodValidator( |
|
156 | 2 | $schemaValidator, |
|
157 | 2 | new ErrorFormatter(), |
|
158 | 2 | ), |
|
159 | 2 | $methodProvider, |
|
160 | 2 | $logger, |
|
161 | 2 | ), |
|
162 | 2 | new ResponseBuilder(), |
|
163 | 2 | new UuidFactory(), |
|
164 | 2 | $streamFactory, |
|
165 | 2 | $responseFactory, |
|
166 | 2 | $logger, |
|
167 | ); |
||
168 | } |
||
169 | 1 | ||
170 | public function handle( |
||
171 | ServerRequestInterface $request, |
||
172 | 1 | ): ResponseInterface { |
|
173 | 1 | return $this->serve( |
|
174 | 1 | $request, |
|
175 | ); |
||
176 | } |
||
177 | } |
||
178 |