Passed
Push — master ( 37cafd...a8b392 )
by
unknown
07:18
created

ConnectionException::tryParsedResponse()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 2
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 8
rs 10
1
<?php declare(strict_types=1);
2
3
namespace Datamate\SeafileApi\Exception;
4
5
use Datamate\SeafileApi\Exception;
6
use Throwable;
7
8
/**
9
 * Class ConnectionException
10
 *
11
 * @internal
12
 */
13
final class ConnectionException extends Exception
14
{
15
    /**
16
     * HTTP status code of the response
17
     */
18
    private ?int $responseCode = null;
19
20
    /**
21
     * HTTP raw response (if any)
22
     */
23
    private ?string $responseBodyRaw = null;
24
25
    private const HTTP_STATUS = [
26
        200 => 'OK',
27
        201 => 'Created',
28
        202 => 'Accepted',
29
        301 => 'Moved Permanently',
30
        400 => 'Bad Request',
31
        401 => 'Unauthorized',
32
        403 => 'Forbidden',
33
        404 => 'Not Found',
34
        409 => 'Conflict',
35
        429 => 'Too Many Requests',
36
        440 => 'REPO_PASSWD_REQUIRED',
37
        441 => 'REPO_PASSWD_MAGIC_REQUIRED',
38
        500 => 'Internal Server Error',
39
        520 => 'OPERATION_FAILED',
40
    ];
41
42
    /**
43
     * @param int $code HTTP status code, e.g. curl_getinfo($curl)['http_code']
44
     * @param bool|string $curlResult return value from curl_exec();
45
     * @throws ConnectionException
46
     * @return no-return
0 ignored issues
show
Documentation Bug introduced by
The doc comment no-return at position 0 could not be parsed: Unknown type name 'no-return' at position 0 in no-return.
Loading history...
47
     */
48
    public static function throwCurlResult(int $code, string|bool $curlResult): never
49
    {
50
        $exception = new self(self::reasonPhrase($code), $code);
51
        $exception->responseCode = $code;
52
        $exception->responseBodyRaw = is_string($curlResult) ? $curlResult : null;
53
54
        throw $exception;
55
    }
56
57
    private static function reasonPhrase(int $code): string
58
    {
59
        return sprintf('%s %s', $code, self::HTTP_STATUS[$code] ?? "UNKNOWN_PHRASE");
60
    }
61
62
    public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)
63
    {
64
        // trigger E_USER_NOTICE if code is not known
65
        $isHttpCode = 100 <= $code && $code < 600;
66
        $isKnownHttpCode = $isHttpCode && isset(self::HTTP_STATUS[$code]);
67
        $isKnownCode = $code === -1 || $isKnownHttpCode;
68
        $this->responseCode = $isHttpCode ? $code : null;
69
        $isKnownCode || trigger_error(sprintf("%s: Unknown code: %s (%s)", __CLASS__, $code, gettype($code)), E_USER_NOTICE);
70
71
        parent::__construct($message, $code, $previous);
72
    }
73
74
    public function getStatusCode(): ?int
75
    {
76
        return $this->responseCode;
77
    }
78
79
    /**
80
     * @param int $code
81
     * @throws ConnectionException
82
     * @return void
83
     */
84
    public function assertStatusCode(int $code): void
85
    {
86
        if ($this->responseCode !== $code) {
87
            throw $this;
88
        }
89
    }
90
91
    /**
92
     * The raw response body
93
     *
94
     * @return string|null
95
     */
96
    public function getRawResponse(): ?string
97
    {
98
        return $this->responseBodyRaw;
99
    }
100
101
    /**
102
     * Response body parsed as JSON Object
103
     *
104
     * @return null|object - null either if the parsed response is NULL or if it can't be parsed as object
105
     */
106
    public function tryParsedResponse(): ?object
107
    {
108
        /**
109
         * @noinspection JsonEncodingApiUsageInspection
110
         * @noinspection RedundantSuppression
111
         */
112
        $result = json_decode((string)$this->responseBodyRaw, false);
113
        return is_object($result) ? $result : null;
114
    }
115
116
    public function getReasonPhrase(): ?string
117
    {
118
        $code = $this->responseCode;
119
120
        if (!is_int($code)) {
121
            return null;
122
        }
123
124
        return self::HTTP_STATUS[$code] ?? null;
125
126
    }
127
128
    /**
129
     * A seafile JSON response may contain error information, try to get them.
130
     *
131
     * It is not fool-proof or overly complete but often more informative than just looking at JSON response dumps.
132
     *
133
     * @return null|array|string[] messages, null if n/a otherwise array of messages (which can not be empty)
134
     */
135
    public function tryApiErrorMessages(): ?array
136
    {
137
        $response = $this->tryParsedResponse();
138
        if (null === $response) {
139
            return null;
140
        }
141
142
        $buffer = [];
143
144
        // {"error_msg": ... }
145
        if (isset($response->error_msg) && is_string($response->error_msg)) {
146
             $buffer[] = $response->error_msg;
147
        }
148
149
        // {"detail": "Invalid token header. No credentials provided."}
150
        if (isset($response->detail) && is_string($response->detail)) {
151
            $buffer[] = $response->detail;
152
        }
153
154
        // {"non_field_errors": [ "string...", ...]}
155
        if (isset($response->non_field_errors) && is_array($response->non_field_errors)) {
156
            foreach ($response->non_field_errors as $message) {
157
                if (is_string($message)) {
158
                    $buffer[] = $message;
159
                }
160
            }
161
        }
162
163
        return empty($buffer) ? null : $buffer;
164
    }
165
}
166