Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.62% covered (warning)
84.62%
11 / 13
84.62% covered (warning)
84.62%
11 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
HttpException
84.62% covered (warning)
84.62%
11 / 13
84.62% covered (warning)
84.62%
11 / 13
14.71
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 badRequest
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 invalidQuery
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 unauthenticated
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 forbidden
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 notFound
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 methodNotAllowed
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 notAcceptable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 conflict
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 payloadTooLarge
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 unsupportedMediaType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 unprocessable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 internal
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace LambdaTwelve\OneRecord\Server\Http;
6
7use LambdaTwelve\OneRecord\Api\Error;
8use RuntimeException;
9
10/**
11 * An endpoint's way of answering with an error: the status and the api:Error
12 * body the spec requires on every 4xx/5xx, with the 2.3 standard titles.
13 */
14final class HttpException extends RuntimeException
15{
16    /**
17     * @param list<Error> $errors
18     * @param array<string, string> $headers extra response headers (Allow, WWW-Authenticate, ...)
19     */
20    public function __construct(
21        public readonly int $status,
22        public readonly array $errors,
23        public readonly array $headers = [],
24    ) {
25        parent::__construct($errors[0]->title ?? 'HTTP ' . $status);
26    }
27
28    public static function badRequest(string $message, ?string $property = null, string $title = 'Invalid body request'): self
29    {
30        return new self(400, [Error::of($title, '400', $message, $property)]);
31    }
32
33    public static function invalidQuery(string $message, string $parameter): self
34    {
35        return new self(400, [Error::of('Invalid query parameter request', '400', $message, $parameter)]);
36    }
37
38    public static function unauthenticated(): self
39    {
40        return new self(401, [Error::of('Not authenticated or expired token', '401', 'The request does not contain valid authentication credentials, or the provided token is expired.')], ['WWW-Authenticate' => 'Bearer']);
41    }
42
43    public static function forbidden(?string $resource = null): self
44    {
45        return new self(403, [Error::of('Not authorized to perform action', '403', 'The authenticated party is not authorized to perform the requested action.', null, $resource)]);
46    }
47
48    public static function notFound(string $what, ?string $resource = null): self
49    {
50        return new self(404, [Error::of('Resource not found', '404', $what . ' could not be found.', null, $resource)]);
51    }
52
53    /**
54     * @param list<string> $allowed
55     */
56    public static function methodNotAllowed(array $allowed): self
57    {
58        return new self(405, [Error::of('Method not allowed', '405', 'The HTTP method is not supported for the requested resource.')], ['Allow' => implode(', ', $allowed)]);
59    }
60
61    public static function notAcceptable(string $message): self
62    {
63        return new self(406, [Error::of('Not acceptable', '406', $message)]);
64    }
65
66    public static function conflict(string $message, ?string $resource = null): self
67    {
68        return new self(409, [Error::of('Identifier conflict', '409', $message, null, $resource)]);
69    }
70
71    public static function payloadTooLarge(int $limit): self
72    {
73        return new self(413, [Error::of('Payload too large', '413', \sprintf('Request bodies are limited to %d bytes.', $limit))]);
74    }
75
76    public static function unsupportedMediaType(string $message): self
77    {
78        return new self(415, [Error::of('Unsupported content type', '415', $message)]);
79    }
80
81    /**
82     * @param list<Error> $errors
83     */
84    public static function unprocessable(string $message, ?string $property = null, array $errors = []): self
85    {
86        return new self(422, $errors === [] ? [Error::of('Unprocessable content', '422', $message, $property)] : $errors);
87    }
88
89    public static function internal(string $message = 'The server encountered an unexpected condition that prevented it from fulfilling the request.'): self
90    {
91        return new self(500, [Error::of('Internal server error', '500', $message)]);
92    }
93}