Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
88.75% |
71 / 80 |
|
80.00% |
12 / 15 |
CRAP | |
0.00% |
0 / 1 |
| VultrClientHandler | |
88.75% |
71 / 80 |
|
80.00% |
12 / 15 |
41.17 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
| setClient | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| setRequestFactory | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| setResponseFactory | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| setStreamFactory | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| delete | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
| post | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| put | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| patch | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| get | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
| generateRequest | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
| request | |
60.00% |
6 / 10 |
|
0.00% |
0 / 1 |
5.02 | |||
| applyOptions | |
86.67% |
13 / 15 |
|
0.00% |
0 / 1 |
7.12 | |||
| formalizeErrorMessage | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
7 | |||
| bodySummary | |
80.00% |
12 / 15 |
|
0.00% |
0 / 1 |
7.39 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Vultr\VultrPhp; |
| 6 | |
| 7 | use InvalidArgumentException; |
| 8 | use Psr\Http\Client\ClientExceptionInterface; |
| 9 | use Psr\Http\Client\ClientInterface; |
| 10 | use Psr\Http\Message\MessageInterface; |
| 11 | use Psr\Http\Message\RequestFactoryInterface; |
| 12 | use Psr\Http\Message\RequestInterface; |
| 13 | use Psr\Http\Message\ResponseFactoryInterface; |
| 14 | use Psr\Http\Message\ResponseInterface; |
| 15 | use Psr\Http\Message\StreamFactoryInterface; |
| 16 | use Throwable; |
| 17 | use Vultr\VultrPhp\Util\VultrUtil; |
| 18 | |
| 19 | class VultrClientHandler |
| 20 | { |
| 21 | private ClientInterface $client; |
| 22 | private RequestFactoryInterface $request_fact; |
| 23 | private ResponseFactoryInterface $response_fact; |
| 24 | private StreamFactoryInterface $stream_fact; |
| 25 | |
| 26 | private VultrAuth $auth; |
| 27 | |
| 28 | private const QUERY = 0; |
| 29 | private const JSON = 1; |
| 30 | |
| 31 | public function __construct( |
| 32 | VultrAuth $auth, |
| 33 | ClientInterface $http, |
| 34 | RequestFactoryInterface $request, |
| 35 | ResponseFactoryInterface $response, |
| 36 | StreamFactoryInterface $stream |
| 37 | ) |
| 38 | { |
| 39 | $this->auth = $auth; |
| 40 | |
| 41 | $this->setClient($http); |
| 42 | $this->setRequestFactory($request); |
| 43 | $this->setResponseFactory($response); |
| 44 | $this->setStreamFactory($stream); |
| 45 | } |
| 46 | |
| 47 | public function setClient(ClientInterface $http) : void |
| 48 | { |
| 49 | $this->client = $http; |
| 50 | } |
| 51 | |
| 52 | public function setRequestFactory(RequestFactoryInterface $request) : void |
| 53 | { |
| 54 | $this->request_fact = $request; |
| 55 | } |
| 56 | |
| 57 | public function setResponseFactory(ResponseFactoryInterface $response) : void |
| 58 | { |
| 59 | $this->response_fact = $response; |
| 60 | } |
| 61 | |
| 62 | public function setStreamFactory(StreamFactoryInterface $stream) : void |
| 63 | { |
| 64 | $this->stream_fact = $stream; |
| 65 | } |
| 66 | |
| 67 | /** |
| 68 | * @param $uri - string - anything after api.vultr.com/v2/ |
| 69 | * @param $params - array|null - query parameters that will be added to the uri query stirng. |
| 70 | * @throws VultrClientException |
| 71 | * @return ResponseInterface |
| 72 | */ |
| 73 | public function delete(string $uri, ?array $params = []) : ResponseInterface |
| 74 | { |
| 75 | $options = []; |
| 76 | if ($params !== null) |
| 77 | { |
| 78 | $options[self::QUERY] = $params; |
| 79 | } |
| 80 | |
| 81 | return $this->request($this->generateRequest('DELETE', $uri, $options)); |
| 82 | } |
| 83 | |
| 84 | /** |
| 85 | * @param $uri - string - anything after api.vultr.com/v2/ |
| 86 | * @param $params - array - form data that will be encoded to a json |
| 87 | * @throws VultrClientException |
| 88 | * @return ResponseInterface |
| 89 | */ |
| 90 | public function post(string $uri, array $params = []) : ResponseInterface |
| 91 | { |
| 92 | return $this->request($this->generateRequest('POST', $uri, [self::JSON => $params])); |
| 93 | } |
| 94 | |
| 95 | /** |
| 96 | * @param $uri - string - anything after api.vultr.com/v2/ |
| 97 | * @param $params - array - form data that will be encoded to a json |
| 98 | * @throws VultrClientException |
| 99 | * @return ResponseInterface |
| 100 | */ |
| 101 | public function put(string $uri, array $params = []) : ResponseInterface |
| 102 | { |
| 103 | return $this->request($this->generateRequest('PUT', $uri, [self::JSON => $params])); |
| 104 | } |
| 105 | |
| 106 | /** |
| 107 | * @param $uri - string - anything after api.vultr.com/v2/ |
| 108 | * @param $params - array - form data that will be encoded to a json |
| 109 | * @throws VultrClientException |
| 110 | * @return ResponseInterface |
| 111 | */ |
| 112 | public function patch(string $uri, array $params = []) : ResponseInterface |
| 113 | { |
| 114 | return $this->request($this->generateRequest('PATCH', $uri, [self::JSON => $params])); |
| 115 | } |
| 116 | |
| 117 | /** |
| 118 | * @param $uri - string - anything after api.vultr.com/v2/ |
| 119 | * @param $params - array|null - query parameters that will be added to the uri query stirng. |
| 120 | * @throws VultrClientException |
| 121 | * @return ResponseInterface |
| 122 | */ |
| 123 | public function get(string $uri, ?array $params = null) : ResponseInterface |
| 124 | { |
| 125 | $options = []; |
| 126 | if ($params !== null) |
| 127 | { |
| 128 | $options[self::QUERY] = $params; |
| 129 | } |
| 130 | |
| 131 | return $this->request($this->generateRequest('GET', $uri, $options)); |
| 132 | } |
| 133 | |
| 134 | private function generateRequest(string $method, string $uri, array $options = []) : RequestInterface |
| 135 | { |
| 136 | $request = $this->request_fact->createRequest($method, VultrConfig::getBaseURI().ltrim($uri, '/')); |
| 137 | foreach (VultrConfig::generateHeaders($this->auth) as $header => $value) |
| 138 | { |
| 139 | $request = $request->withHeader($header, $value); |
| 140 | } |
| 141 | |
| 142 | return $this->applyOptions($request, $options); |
| 143 | } |
| 144 | |
| 145 | private function request(RequestInterface $request) : ResponseInterface |
| 146 | { |
| 147 | try |
| 148 | { |
| 149 | $response = $this->client->sendRequest($request); |
| 150 | } |
| 151 | catch (ClientExceptionInterface $e) |
| 152 | { |
| 153 | throw new VultrClientException($this->formalizeErrorMessage($this->response_fact->createResponse(500), $e->getRequest()), null, $e); |
| 154 | } |
| 155 | catch (Throwable $e) |
| 156 | { |
| 157 | throw new VultrClientException($request->getMethod().' fatal failed: '.$e->getMessage(), null, $e); |
| 158 | } |
| 159 | |
| 160 | $level = VultrUtil::getLevel($response); |
| 161 | if ($level >= 4) |
| 162 | { |
| 163 | $message = $this->formalizeErrorMessage($response, $request); |
| 164 | throw new VultrClientException($request->getMethod().' failed: '.$message, $response->getStatusCode()); |
| 165 | } |
| 166 | |
| 167 | return $response; |
| 168 | } |
| 169 | |
| 170 | private function applyOptions(RequestInterface $request, array &$options) : RequestInterface |
| 171 | { |
| 172 | if (isset($options[self::JSON]) && !empty($options[self::JSON])) |
| 173 | { |
| 174 | $json = json_encode($options[self::JSON], 0, 512); |
| 175 | if (JSON_ERROR_NONE !== json_last_error()) |
| 176 | { |
| 177 | throw new InvalidArgumentException('json_encode error: ' . json_last_error_msg()); |
| 178 | } |
| 179 | $request = $request->withBody($this->stream_fact->createStream($json)); |
| 180 | unset($options[self::JSON]); |
| 181 | } |
| 182 | |
| 183 | if (isset($options[self::QUERY])) |
| 184 | { |
| 185 | $value = $options[self::QUERY]; |
| 186 | if (is_array($value)) |
| 187 | { |
| 188 | $value = http_build_query($value, '', '&', PHP_QUERY_RFC3986); |
| 189 | } |
| 190 | |
| 191 | if (!is_string($value)) |
| 192 | { |
| 193 | throw new InvalidArgumentException('query must be a string or array'); |
| 194 | } |
| 195 | |
| 196 | $request = $request->withUri($request->getUri()->withQuery($value), true); |
| 197 | unset($options[self::QUERY]); |
| 198 | } |
| 199 | |
| 200 | return $request; |
| 201 | } |
| 202 | |
| 203 | private function formalizeErrorMessage(ResponseInterface $response, RequestInterface $request) : string |
| 204 | { |
| 205 | if ($response->getHeaderLine('Content-Type') === 'application/json' && $response->getBody()->getSize() < 512) |
| 206 | { |
| 207 | $error = json_decode((string)$response->getBody(), true); |
| 208 | if (isset($error['error'])) |
| 209 | { |
| 210 | return $error['error']; |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | $level = VultrUtil::getLevel($response); |
| 215 | |
| 216 | $label = 'Unsuccessful request'; |
| 217 | if ($level === 4) $label = 'Client error'; |
| 218 | else if ($level === 5) $label = 'Server error'; |
| 219 | |
| 220 | $message = sprintf( |
| 221 | '%s: `%s %s` resulted in a `%s %s` response.', |
| 222 | $label, |
| 223 | $request->getMethod(), |
| 224 | $request->getUri(), |
| 225 | $response->getStatusCode(), |
| 226 | $response->getReasonPhrase() |
| 227 | ); |
| 228 | |
| 229 | if (($summary = $this->bodySummary($response)) !== null) |
| 230 | { |
| 231 | $message .= "\n{$summary}\n"; |
| 232 | } |
| 233 | |
| 234 | return $message; |
| 235 | } |
| 236 | |
| 237 | private function bodySummary(MessageInterface $message, int $truncateAt = 120): ?string |
| 238 | { |
| 239 | $body = $message->getBody(); |
| 240 | |
| 241 | if (!$body->isSeekable() || !$body->isReadable()) |
| 242 | { |
| 243 | return null; |
| 244 | } |
| 245 | |
| 246 | $size = $body->getSize(); |
| 247 | |
| 248 | if ($size === 0) |
| 249 | { |
| 250 | return null; |
| 251 | } |
| 252 | |
| 253 | if ($body->eof()) |
| 254 | { |
| 255 | $body->rewind(); |
| 256 | } |
| 257 | |
| 258 | $summary = $body->read($truncateAt); |
| 259 | $body->rewind(); |
| 260 | |
| 261 | if ($size > $truncateAt) |
| 262 | { |
| 263 | $summary .= ' (truncated...)'; |
| 264 | } |
| 265 | |
| 266 | // Matches any printable character, including unicode characters: |
| 267 | // letters, marks, numbers, punctuation, spacing, and separators. |
| 268 | if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/u', $summary)) |
| 269 | { |
| 270 | return null; |
| 271 | } |
| 272 | |
| 273 | return $summary; |
| 274 | } |
| 275 | } |