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 | } |