Passed
Push — master ( c3ebe1...248bb1 )
by Daniel
02:37
created

Endpoint::handle()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 5
ccs 2
cts 2
cp 1
crap 1
rs 10
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;
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 class Endpoint implements
36
    EndpointInterface
37
{
38
    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
    }
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 = StatusCode::OK;
56 6
        $responseData = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $responseData is dead and can be removed.
Loading history...
57
58
        try {
59
            // Process and build the response
60 6
            $responseData = $this->responseBuilder->buildResponse(
61 6
                $this->methodRetriever->dispatch(
62
                    $request,
63 6
                    $this->inputValidator->validate($request)
64
                )
65
            );
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 = StatusCode::BAD_REQUEST;
75 2
        } catch (InternalException $e) {
76 1
            $this->logError(
77
                $e,
78 1
                $this->uuidFactory->uuid4(),
79 1
                $e->getContext()
80
            );
81
82 1
            $statusCode = StatusCode::INTERNAL_SERVER_ERROR;
83 1
        } catch (Throwable $e) {
84 1
            $this->logError(
85
                $e,
86 1
                $this->uuidFactory->uuid4()
87
            );
88
89 1
            $statusCode = StatusCode::INTERNAL_SERVER_ERROR;
90
        }
91
92 6
        $response = $this->responseFactory->createResponse($statusCode);
93
94 6
        if ($responseData !== null) {
0 ignored issues
show
introduced by
The condition $responseData !== null is always true.
Loading history...
95 4
            $response = $response->withBody(
96 4
                $this->streamFactory->createStream(
97 4
                    (string) json_encode($responseData)
98
                )
99
            );
100
        }
101
102
        return $response
103
            ->withHeader('Content-Type', 'application/json')
104
            ;
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
            [
118 4
                'id' => $uuid->toString(),
119 4
                'file' => $e->getFile(),
120 4
                'line' => $e->getLine(),
121
                'context' => $context
122
            ]
123
        );
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
        if ($responseFactory === null) {
144 1
            $responseFactory = Psr17FactoryDiscovery::findResponseFactory();
145
        }
146
147 2
        return new self(
148 2
            new RequestValidator(
149
                $schemaLoader,
150
                $schemaValidator
151
            ),
152 2
            new MethodDispatcher(
153
                $schemaLoader,
154 2
                new MethodValidator(
155
                    $schemaValidator,
156 2
                    new ErrorFormatter()
157
                ),
158
                $methodProvider,
159
                $logger,
160
            ),
161 2
            new ResponseBuilder(),
162 2
            new UuidFactory(),
163
            $streamFactory,
164
            $responseFactory,
165
            $logger
166
        );
167
    }
168
169 1
    public function handle(
170
        ServerRequestInterface $request,
171
    ): ResponseInterface {
172 1
        return $this->serve(
173
            $request,
174
        );
175
    }
176
}
177