Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
ApiRouteHandler
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
3 / 3
15
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 process
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
7
 processApi
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
1 / 1
7
1<?php
2
3declare(strict_types=1);
4
5namespace Engelsystem\Middleware;
6
7use Engelsystem\Exceptions\Handler;
8use Engelsystem\Http\Exceptions\HttpException;
9use Engelsystem\Http\Request;
10use Engelsystem\Http\Response;
11use Illuminate\Database\Eloquent\ModelNotFoundException;
12use Illuminate\Support\Str;
13use Nyholm\Psr7\Stream;
14use Nyholm\Psr7\Uri;
15use Psr\Http\Message\ResponseInterface;
16use Psr\Http\Message\ServerRequestInterface;
17use Psr\Http\Server\MiddlewareInterface;
18use Psr\Http\Server\RequestHandlerInterface;
19use Throwable;
20
21class ApiRouteHandler implements MiddlewareInterface
22{
23    public function __construct(
24        protected ?string $apiPrefix = '/api',
25        protected ?array $apiAccessiblePaths = [
26            '/atom',
27            '/rss',
28            '/health',
29            '/ical',
30            '/metrics',
31            '/shifts-json-export',
32        ]
33    ) {
34    }
35
36    /**
37     * Process the incoming request and handling API responses
38     */
39    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
40    {
41        $path = (new Uri((string) $request->getUri()))->getPath();
42        if ($request instanceof Request) {
43            $path = $request->getPathInfo();
44        }
45
46        $path = urldecode($path);
47        $isApi = $this->apiPrefix && (Str::startsWith($path, $this->apiPrefix . '/') || $path == $this->apiPrefix);
48        $isApiAccessible = $isApi || $this->apiAccessiblePaths && in_array($path, $this->apiAccessiblePaths);
49        $request = $request
50            ->withAttribute('route-api', $isApi)
51            ->withAttribute('route-api-accessible', $isApiAccessible);
52
53        return $isApi ? $this->processApi($request, $handler) : $handler->handle($request);
54    }
55
56    /**
57     * Process the API request by ensuring that JSON is returned
58     */
59    protected function processApi(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
60    {
61        try {
62            $response = $handler->handle($request);
63        } catch (ModelNotFoundException) {
64            $response = new Response('', 404);
65            $response->setContent($response->getReasonPhrase());
66        } catch (HttpException $e) {
67            $response = new Response($e->getMessage(), $e->getStatusCode(), $e->getHeaders());
68            $response->setContent($response->getContent() ?: $response->getReasonPhrase());
69        } catch (Throwable $e) {
70            /** @var Handler $handler */
71            $handler = app('error.handler');
72            $handler->exceptionHandler($e, false);
73            $response = new Response('', 500);
74            $response->setContent($response->getReasonPhrase());
75        }
76
77        if (!Str::isJson((string) $response->getBody())) {
78            $content = (string) $response->getBody();
79            $content = Stream::create(json_encode([
80                'message' => $content,
81            ]));
82            $response = $response
83                ->withHeader('content-type', 'application/json')
84                ->withBody($content);
85        }
86
87        if (!$response->hasHeader('access-control-allow-origin')) {
88            $response = $response->withHeader('access-control-allow-origin', '*');
89        }
90
91        $eTag = md5((string) $response->getBody());
92        $response->setEtag($eTag);
93
94        return $response;
95    }
96}