Passed
Push — master ( 51bd10...72ce91 )
by Daniel
12:53
created

Endpoint::serve()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 45
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 4

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 4
eloc 27
c 2
b 0
f 0
nc 4
nop 2
dl 0
loc 45
ccs 26
cts 26
cp 1
crap 4
rs 9.488
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Usox\JsonSchemaApi;
6
7
use Http\Discovery\Psr17FactoryDiscovery;
8
use JsonSchema\Validator;
9
use Psr\Http\Message\ResponseInterface;
10
use Psr\Http\Message\ServerRequestInterface;
11
use Psr\Http\Message\StreamFactoryInterface;
12
use Psr\Http\Server\MiddlewareInterface;
13
use Psr\Http\Server\RequestHandlerInterface;
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\Exception\ApiException;
22
use Usox\JsonSchemaApi\Exception\InternalException;
23
use Usox\JsonSchemaApi\Dispatch\MethodDispatcher;
24
use Usox\JsonSchemaApi\Dispatch\MethodDispatcherInterface;
25
use Usox\JsonSchemaApi\Dispatch\MethodValidator;
26
use Usox\JsonSchemaApi\Dispatch\RequestValidator;
27
use Usox\JsonSchemaApi\Dispatch\RequestValidatorInterface;
28
use Usox\JsonSchemaApi\Dispatch\SchemaLoader;
29
use Usox\JsonSchemaApi\Response\ResponseBuilder;
30
use Usox\JsonSchemaApi\Response\ResponseBuilderInterface;
31
32
final class Endpoint implements 
33
    MiddlewareInterface
34
{
35
    private RequestValidatorInterface $inputValidator;
36
37
    private MethodDispatcherInterface $methodRetriever;
38
39
    private ResponseBuilderInterface $responseBuilder;
40
41
    private UuidFactoryInterface $uuidFactory;
42
43
    private StreamFactoryInterface $streamFactory;
44
45
    private ?LoggerInterface $logger;
46
47 7
    public function __construct(
48
        RequestValidatorInterface $inputValidator,
49
        MethodDispatcherInterface $methodRetriever,
50
        ResponseBuilderInterface $responseBuilder,
51
        UuidFactoryInterface $uuidFactory,
52
        StreamFactoryInterface $streamFactory,
53
        ?LoggerInterface $logger = null
54
    ) {
55 7
        $this->inputValidator = $inputValidator;
56 7
        $this->methodRetriever = $methodRetriever;
57 7
        $this->responseBuilder = $responseBuilder;
58 7
        $this->uuidFactory = $uuidFactory;
59 7
        $this->streamFactory = $streamFactory;
60 7
        $this->logger = $logger;
61 7
    }
62
63 6
    public function serve(
64
        ServerRequestInterface $request,
65
        ResponseInterface $response
66
    ): ResponseInterface {
67 6
        $statusCode = StatusCode::OK;
68
        
69
        try {
70
            // Process and build the response
71 6
            $responseData = $this->responseBuilder->buildResponse(
72 6
                $this->methodRetriever->dispatch(
73 6
                    $request,
74 6
                    $this->inputValidator->validate($request)
75
                )
76
            );
77 4
        } catch (ApiException $e) {
78 2
            $uuid = $this->uuidFactory->uuid4();
79
80 2
            $this->log($e, $uuid);
81
82
            // Build an error response
83 2
            $responseData = $this->responseBuilder->buildErrorResponse($e, $uuid);
84
85 2
            $statusCode = StatusCode::BAD_REQUEST;
86 2
        } catch (InternalException $e) {
87 1
            $uuid = $this->uuidFactory->uuid4();
88
89 1
            $this->log($e, $uuid, $e->getContext());
90
91 1
            $responseData = '';
92 1
            $statusCode = StatusCode::INTERNAL_SERVER_ERROR;
93 1
        } catch (Throwable $e) {
94 1
            $uuid = $this->uuidFactory->uuid4();
95
            
96 1
            $this->log($e, $uuid);
97
            
98 1
            $responseData = '';
99 1
            $statusCode = StatusCode::INTERNAL_SERVER_ERROR;
100
        }
101
102
        return $response
103 6
            ->withHeader('Content-Type', 'application/json')
104 6
            ->withStatus($statusCode)
105 6
            ->withBody(
106 6
                $this->streamFactory->createStream(
107 6
                    (string) json_encode($responseData)
108
                )
109
            );
110
    }
111
112
    /**
113
     * @param Throwable $e
114
     * @param UuidInterface $uuid
115
     * @param array<mixed, mixed> $context
116
     */
117 4
    private function log(
118
        Throwable $e,
119
        UuidInterface $uuid,
120
        array $context = []
121
    ): void {
122 4
        if ($this->logger !== null) {
123 4
            $this->logger->error(
124 4
                sprintf('%s (%d)', $e->getMessage(), $e->getCode()),
125
                [
126 4
                    'id' => $uuid->toString(),
127 4
                    'file' => $e->getFile(),
128 4
                    'line' => $e->getLine(),
129 4
                    'context' => $context
130
                ]
131
            );
132
        }
133 4
    }
134
    
135 1
    public static function factory(
136
        MethodProviderInterface $methodProvider,
137
        ?StreamFactoryInterface $streamFactory = null,
138
        ?LoggerInterface $logger = null
139
    ): Endpoint {
140 1
        $schemaValidator = new Validator();
141 1
        $schemaLoader = new SchemaLoader();
142
143 1
        if ($streamFactory === null) {
144
            $streamFactory = Psr17FactoryDiscovery::findStreamFactory();
145
        }
146
        
147 1
        return new self(
148 1
            new RequestValidator(
149 1
                $schemaLoader,
150
                $schemaValidator
151
            ),
152 1
            new MethodDispatcher(
153 1
                $schemaLoader,
154 1
                new MethodValidator($schemaValidator),
155
                $methodProvider
156
            ),
157 1
            new ResponseBuilder(),
158 1
            new UuidFactory(),
159
            $streamFactory,
160
            $logger
161
        );
162
    }
163
164 1
    public function process(
165
        ServerRequestInterface $request,
166
        RequestHandlerInterface $handler
167
    ): ResponseInterface {
168 1
        return $this->serve(
169 1
            $request,
170 1
            $handler->handle($request)
171
        );
172
    }
173
}
174