Passed
Push — next ( 5fb4ae...5405f8 )
by Bas
04:08 queued 02:27
created

ArangoClient::cleanupResponse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 7
ccs 5
cts 5
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ArangoClient;
6
7
use ArangoClient\Exceptions\ArangoException;
8
use ArangoClient\Http\HttpClientConfig;
9
use ArangoClient\Http\HttpRequestOptions;
10
use ArangoClient\Statement\Statement;
11
use ArangoClient\Transactions\SupportsTransactions;
12
use GuzzleHttp\Client as GuzzleClient;
13
use GuzzleHttp\Exception\GuzzleException;
14
use GuzzleHttp\Exception\RequestException;
15
use GuzzleHttp\Psr7\StreamWrapper;
16
use JsonMachine\JsonMachine;
17
use Psr\Http\Message\ResponseInterface;
18
use Throwable;
19
use Traversable;
20
21
/**
22
 * The arangoClient handles connections to ArangoDB's HTTP REST API.
23
 *
24
 * @see https://www.arangodb.com/docs/stable/http/
25
 */
26
class ArangoClient
27
{
28
    use HasManagers;
29
    use SupportsTransactions;
30
31
    protected GuzzleClient $httpClient;
32
33
    protected HttpClientConfig $config;
34
35
    /**
36
     * ArangoClient constructor.
37
     *
38
     * @param  array<string|numeric|null>  $config
39
     * @param  GuzzleClient|null  $httpClient
40
     */
41 104
    public function __construct(array $config = [], GuzzleClient $httpClient = null)
42
    {
43 104
        $config['endpoint'] = $this->generateEndpoint($config);
44 104
        $this->config = new HttpClientConfig($config);
45
46 104
        $this->httpClient = $httpClient ?? new GuzzleClient($this->config->mapGuzzleHttpClientConfig());
47 104
    }
48
49
    /**
50
     * @param  array<mixed>  $config
51
     * @return string
52
     */
53 104
    public function generateEndpoint(array $config): string
54
    {
55 104
        if (isset($config['endpoint'])) {
56
            return (string) $config['endpoint'];
57
        }
58 104
        $endpoint = 'http://localhost:8529';
59 104
        if (isset($config['host'])) {
60 2
            $endpoint = (string) $config['host'];
61
        }
62 104
        if (isset($config['port'])) {
63 2
            $endpoint .= ':' . (string) $config['port'];
64
        }
65
66 104
        return $endpoint;
67
    }
68
69
    /**
70
     * @psalm-suppress MixedReturnStatement
71
     *
72
     * @param  string  $method
73
     * @param  string  $uri
74
     * @param  array<mixed>|HttpRequestOptions  $options
75
     * @param  string|null  $database
76
     * @return array<mixed>
77
     * @throws ArangoException
78
     */
79 104
    public function request(string $method, string $uri, $options = [], ?string $database = null): array
80
    {
81 104
        $uri = $this->prependDatabaseToUri($uri, $database);
82
83 104
        if (is_array($options)) {
84 104
            $options = $this->prepareRequestOptions($options);
85
        }
86
87 104
        $response = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $response is dead and can be removed.
Loading history...
88
        try {
89 104
            $response = $this->httpClient->request($method, $uri, $options->all());
90 1
        } catch (Throwable $e) {
91 1
            $this->handleGuzzleException($e);
92
        }
93
94 104
        return $this->cleanupResponse($response);
95
    }
96
97
    /**
98
     * @param array<mixed> $options
99
     * @return HttpRequestOptions
100
     * @throws ArangoException
101
     */
102 104
    protected function prepareRequestOptions(array $options): HttpRequestOptions
103
    {
104 104
        if (isset($options['body'])) {
105 67
            $options['body'] = $this->jsonEncode($options['body']);
106
        }
107
108 104
        return new HttpRequestOptions($options);
109
    }
110
111
    /**
112
     * Return the response with debug information (for internal testing purposes).
113
     *
114
     * @param  string  $method
115
     * @param  string  $uri
116
     * @param  array<mixed>  $options
117
     * @param  string|null  $database
118
     * @return ResponseInterface
119
     * @throws GuzzleException
120
     */
121 2
    public function debugRequest(
122
        string $method,
123
        string $uri,
124
        array $options = [],
125
        ?string $database = null
126
    ): ResponseInterface {
127 2
        $uri = $this->prependDatabaseToUri($uri, $database);
128 2
        $options['debug'] = true;
129
130 2
        return $this->httpClient->request($method, $uri, $options);
131
    }
132
133 104
    protected function prependDatabaseToUri(string $uri, ?string $database = null): string
134
    {
135 104
        if (! isset($database)) {
136 104
            $database = $this->config->database;
137
        }
138 104
        return '/_db/' . urlencode($database) . $uri;
139
    }
140
141
    /**
142
     * @param  Throwable  $e
143
     * @throws ArangoException
144
     */
145 1
    protected function handleGuzzleException(Throwable $e): void
146
    {
147 1
        $message = $e->getMessage();
148 1
        $code = $e->getCode();
149
150 1
        if ($e instanceof RequestException && $e->hasResponse()) {
151 1
            $decodedResponse = $this->decodeResponse($e->getResponse());
152 1
            $message = (string) $decodedResponse['errorMessage'];
153 1
            $code = (int) $decodedResponse['code'];
154
        }
155
156
        throw(
157 1
            new ArangoException(
158 1
                $code . ' - ' .  $message,
159 1
                (int) $code
160
            )
161
        );
162
    }
163
164
    /**
165
     * @psalm-suppress MixedAssignment, MixedArrayOffset
166
     * @SuppressWarnings(PHPMD.StaticAccess)
167
     *
168
     * @param  ResponseInterface|null  $response
169
     * @return array<mixed>
170
     */
171 104
    protected function cleanupResponse(?ResponseInterface $response): array
172
    {
173 104
        $response =  $this->decodeResponse($response);
174 104
        unset($response['error']);
175 104
        unset($response['code']);
176
177 104
        return $response;
178
    }
179
180
    /**
181
     * @psalm-suppress MixedAssignment, MixedArrayOffset
182
     * @SuppressWarnings(PHPMD.StaticAccess)
183
     *
184
     * @param  ResponseInterface|null  $response
185
     * @return array<mixed>
186
     */
187 104
    protected function decodeResponse(?ResponseInterface $response): array
188
    {
189 104
        if (! isset($response)) {
190
            return [];
191
        }
192
193 104
        $decodedResponse = [];
194
195 104
        $phpStream = StreamWrapper::getResource($response->getBody());
196 104
        $decodedStream = JsonMachine::fromStream($phpStream);
197
198 104
        foreach ($decodedStream as $key => $value) {
199 104
            $decodedResponse[$key] = $value;
200
        }
201
202 104
        return $decodedResponse;
203
    }
204
205
    /**
206
     * @param  mixed  $data
207
     * @return string
208
     * @throws ArangoException
209
     */
210 71
    public function jsonEncode($data): string
211
    {
212 71
        $options = 0;
213 71
        if (empty($data)) {
214 3
            $options = JSON_FORCE_OBJECT;
215
        }
216
217 71
        $response = json_encode($data, $options);
218
219 71
        if ($response === false) {
220 1
            throw new ArangoException('JSON encoding failed with error: ' . json_last_error_msg(), json_last_error());
221
        }
222
223 70
        return $response;
224
    }
225
226
    /**
227
     * @param  string  $query
228
     * @param  array<scalar>  $bindVars
229
     * @param  array<mixed>  $options
230
     * @return Traversable<mixed>
231
     */
232 16
    public function prepare(
233
        string $query,
234
        array $bindVars = [],
235
        array $options = []
236
    ): Traversable {
237 16
        return new Statement($this, $query, $bindVars, $options);
238
    }
239
240
    /**
241
     * @return array<array-key, mixed>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, mixed> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, mixed>.
Loading history...
242
     */
243 4
    public function getConfig(): array
244
    {
245 4
        return $this->config->toArray();
246
    }
247
248
    /**
249
     * @param string $name
250
     * @return void
251
     */
252 104
    public function setDatabase(string $name): void
253
    {
254 104
        $this->config->database = $name;
255 104
    }
256
257
    /**
258
     * @return string
259
     */
260 1
    public function getDatabase(): string
261
    {
262 1
        return $this->config->database;
263
    }
264
265 1
    public function setHttpClient(GuzzleClient $httpClient): void
266
    {
267 1
        $this->httpClient = $httpClient;
268 1
    }
269
270 1
    public function getHttpClient(): GuzzleClient
271
    {
272 1
        return $this->httpClient;
273
    }
274
275 104
    public function getUser(): string
276
    {
277 104
        return (string) $this->config->username;
278
    }
279
}
280