Passed
Pull Request — master (#13)
by
unknown
26:12 queued 11:18
created

ApiService::getDefaultValues()   C

Complexity

Conditions 12
Paths 12

Size

Total Lines 42
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 12

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 12
eloc 30
nc 12
nop 1
dl 0
loc 42
ccs 8
cts 8
cp 1
crap 12
rs 6.9666
c 2
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace ElevenLabs\Api\Service;
6
7
use Assert\Assertion;
8
use ElevenLabs\Api\Decoder\DecoderUtils;
9
use ElevenLabs\Api\Definition\Parameter;
10
use ElevenLabs\Api\Definition\Parameters;
11
use ElevenLabs\Api\Definition\RequestDefinition;
12
use ElevenLabs\Api\Definition\ResponseDefinition;
13
use ElevenLabs\Api\Schema;
14
use ElevenLabs\Api\Service\Exception\ConstraintViolations;
15
use ElevenLabs\Api\Service\Exception\RequestViolations;
16
use ElevenLabs\Api\Service\Exception\ResponseViolations;
17
use ElevenLabs\Api\Service\Resource\ErrorInterface;
18
use ElevenLabs\Api\Service\Resource\Item;
19
use ElevenLabs\Api\Service\Resource\ResourceInterface;
20
use ElevenLabs\Api\Validator\MessageValidator;
21
use Http\Client\HttpAsyncClient;
22
use Http\Client\HttpClient;
23
use Http\Message\MessageFactory;
24
use Http\Message\UriFactory;
25
use Http\Promise\Promise;
26
use Psr\Http\Message\RequestInterface;
27
use Psr\Http\Message\ResponseInterface;
28
use Psr\Http\Message\UriInterface;
29
use Rize\UriTemplate;
30
use Symfony\Component\Serializer\SerializerInterface;
31
32
/**
33
 * Class ApiService.
34
 */
35
class ApiService
36
{
37
    const DEFAULT_CONFIG = [
38
        // override the scheme and host provided in the API schema by a baseUri (ex:https://domain.com)
39
        'baseUri' => null,
40
        // validate request
41
        'validateRequest' => true,
42
        // validate response
43
        'validateResponse' => false,
44
        // return response instead of a denormalized object
45
        'returnResponse' => false,
46
    ];
47
48
    /**
49
     * @var UriInterface
50
     */
51
    private $baseUri;
52
53
    /**
54
     * @var Schema
55
     */
56
    private $schema;
57
58
    /**
59
     * @var MessageValidator
60
     */
61
    private $messageValidator;
62
63
    /**
64
     * @var HttpAsyncClient|HttpClient
65
     */
66
    private $client;
67
68
    /**
69
     * @var MessageFactory
70
     */
71
    private $messageFactory;
72
73
    /**
74
     * @var UriTemplate
75
     */
76
    private $uriTemplate;
77
78
    /**
79
     * @var UriFactory
80
     */
81
    private $uriFactory;
82
83
    /**
84
     * @var SerializerInterface
85
     */
86
    private $serializer;
87
88
    /**
89
     * @var array
90
     */
91
    private $config;
92
93
    public function __construct(
94
        UriFactory $uriFactory,
95 13
        UriTemplate $uriTemplate,
96
        HttpClient $client,
97
        MessageFactory $messageFactory,
98
        Schema $schema,
99
        MessageValidator $messageValidator,
100
        SerializerInterface $serializer,
101
        array $config = []
102
    ) {
103
        $this->uriFactory = $uriFactory;
104
        $this->uriTemplate = $uriTemplate;
105 13
        $this->schema = $schema;
106 13
        $this->messageValidator = $messageValidator;
107 13
        $this->client = $client;
108 13
        $this->messageFactory = $messageFactory;
109 13
        $this->serializer = $serializer;
110 13
        $this->config = $this->getConfig($config);
111 13
        $this->baseUri = $this->getBaseUri();
112 13
    }
113 13
114 10
    public function call(string $operationId, array $params = [])
115
    {
116
        $requestDefinition = $this->schema->getRequestDefinition($operationId);
117
        $request = $this->createRequestFromDefinition($requestDefinition, $params);
118
        $this->validateRequest($request, $requestDefinition);
119
120
        $response = $this->client->sendRequest($request);
121
        $this->validateResponse($response, $requestDefinition);
122
123
        return $this->getDataFromResponse(
124 5
            $response,
125
            $requestDefinition->getResponseDefinition(
126 5
                $response->getStatusCode()
127 5
            ),
128 5
            $request
129
        );
130 4
    }
131 4
132
    public function callAsync(string $operationId, array $params = []): Promise
133 3
    {
134 3
        if (!$this->client instanceof HttpAsyncClient) {
135 3
            throw new \RuntimeException(
136 3
                sprintf(
137
                    '"%s" does not support async request',
138 3
                    get_class($this->client)
139
                )
140
            );
141 3
        }
142
143
        $requestDefinition = $this->schema->getRequestDefinition($operationId);
144
        $request = $this->createRequestFromDefinition($requestDefinition, $params);
145
        $promise = $this->client->sendAsyncRequest($request);
146
147
        return $promise->then(
148
            function (ResponseInterface $response) use ($request, $requestDefinition) {
149
150
                return $this->getDataFromResponse(
151
                    $response,
152 1
                    $requestDefinition->getResponseDefinition(
153
                        $response->getStatusCode()
154 1
                    ),
155
                    $request
156
                );
157
            }
158
        );
159
    }
160
161
    public function getSchema(): Schema
162
    {
163 1
        return $this->schema;
164 1
    }
165 1
166
    public static function buildQuery(array $params): array
167 1
    {
168
        foreach ($params as $key => $param) {
169
            if (is_array($param)) {
170 1
                foreach ($param as $k => $v) {
171 1
                    if (is_int($k)) {
172 1
                        $params[$key.'[]'][] = $v;
173 1
                    } else {
174
                        $params[$key.'['.$k.']'][] = $v;
175 1
                    }
176
                }
177 1
                unset($params[$key]);
178
            }
179
            if (false !== stripos($key, '_')) {
180
                $params[str_replace('_', '.', $key)] = $param;
181
                unset($params[$key]);
182
            }
183
        }
184
185
        return $params;
186
    }
187 13
188
    private function getBaseUri(): UriInterface
189
    {
190 13
        // Create a base uri from the API Schema
191 10
        if (null === $this->config['baseUri']) {
192 10
            $schemes = $this->schema->getSchemes();
193 10
            if (empty($schemes)) {
194 1
                throw new \LogicException('You need to provide at least on scheme in your API Schema');
195
            }
196
197 9
            $scheme = null;
198
            foreach ($this->schema->getSchemes() as $candidate) {
199 9
                // Always prefer https
200 8
                if ('https' === $candidate) {
201
                    $scheme = 'https';
202 9
                }
203 9
                if (null === $scheme && 'http' === $candidate) {
204
                    $scheme = 'http';
205
                }
206 9
            }
207 1
208
            if (null === $scheme) {
209
                throw new \RuntimeException('Cannot choose a proper scheme from the API Schema. Supported: https, http');
210 8
            }
211 8
212 1
            $host = $this->schema->getHost();
213
            if ('' === $host) {
214
                throw new \LogicException('The host in the API Schema should not be null');
215 7
            }
216
217 3
            return $this->uriFactory->createUri($scheme.'://'.$host);
218
        } else {
219
            return $this->uriFactory->createUri($this->config['baseUri']);
220
        }
221
    }
222
223
    private function getConfig(array $config): array
224 13
    {
225
        $config = array_merge(self::DEFAULT_CONFIG, $config);
226 13
        Assertion::boolean($config['returnResponse']);
227 13
        Assertion::boolean($config['validateRequest']);
228 13
        Assertion::boolean($config['validateResponse']);
229 13
        Assertion::nullOrString($config['baseUri']);
230 13
231
        return array_intersect_key($config, self::DEFAULT_CONFIG);
232 13
    }
233
234
    private function createRequestFromDefinition(RequestDefinition $definition, array $params): RequestInterface
235
    {
236
        $contentType = $definition->getContentTypes()[0] ?? 'application/json';
237
        $requestParameters = $definition->getRequestParameters();
238
        list($path, $query, $headers, $body, $formData) = $this->getDefaultValues($requestParameters);
239
        $headers = array_merge(
240
            $headers,
241
            ['Content-Type' => $contentType, 'Accept' => $definition->getAccepts()[0] ?? 'application/json']
0 ignored issues
show
Bug introduced by
The method getAccepts() does not exist on ElevenLabs\Api\Definition\RequestDefinition. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

241
            ['Content-Type' => $contentType, 'Accept' => $definition->/** @scrutinizer ignore-call */ getAccepts()[0] ?? 'application/json']

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
242
        );
243
244
        foreach ($params as $name => $value) {
245 6
            $requestParameter = $requestParameters->getByName($name);
246
            if (null === $requestParameter) {
247 6
                throw new \InvalidArgumentException(sprintf('%s is not a defined request parameter for operationId %s', $name, $definition->getOperationId()));
248 6
            }
249 6
250 6
            switch ($requestParameter->getLocation()) {
251 6
                case 'path':
252 6
                    $path[$name] = $value;
253
                    break;
254 6
                case 'query':
255 2
                    $query[$name] = $value;
256 2
                    break;
257
                case 'header':
258
                    $headers[$name] = $value;
259
                    break;
260 2
                case 'body':
261 2
                    $body = $this->serializeRequestBody(array_merge($body ?? [], $value), $contentType);
262 1
                    break;
263 1
                case 'formData':
264 2
                    $formData[$name] = sprintf('%s=%s', $name, $value);
265 2
                    break;
266 2
            }
267 1
        }
268
269
        if (!empty($formData)) {
270 1
            $body = implode('&', $formData);
271 2
        }
272
273
        $request = $this->messageFactory->createRequest(
274
            $definition->getMethod(),
275 6
            $this->buildRequestUri($definition->getPathTemplate(), $path, $query),
276 6
            $headers,
277 6
            $body
278 6
        );
279 6
280
        return $request;
281
    }
282 6
283
    private function getDefaultValues(Parameters $requestParameters): array
284
    {
285
        $path = [];
286
        $query = [];
287
        $headers = [];
288
        $body = null;
289
        $formData = [];
290
291
        /** @var Parameter $parameter */
292
        foreach ($requestParameters->getIterator() as $name => $parameter) {
293
            switch ($parameter->getLocation()) {
294
                case 'path':
295
                    if (!empty($parameter->getSchema()->default)) {
296
                        $path[$name] = $parameter->getSchema()->default;
297
                    }
298
                    break;
299
                case 'query':
300 6
                    if (!empty($parameter->getSchema()->default)) {
301
                        $query[$name] = $parameter->getSchema()->default;
302 6
                    }
303 6
                    break;
304
                case 'header':
305 6
                    if (!empty($parameter->getSchema()->default)) {
306
                        $headers[$name] = $parameter->getSchema()->default;
307
                    }
308
                    break;
309
                case 'formData':
310
                    if (!empty($parameter->getSchema()->default)) {
311
                        $formData[$name] = sprintf('%s=%s', $name, $parameter->getSchema()->default);
312
                    }
313
                    break;
314 1
                case 'body':
315
                    if (!empty($parameter->getSchema()->properties)) {
316 1
                        $body = array_filter(array_map(function (array $params) {
317 1
                            return $params['default'] ?? null;
318 1
                        }, json_decode(json_encode($parameter->getSchema()->properties), true)));
319
                    }
320
                    break;
321
            }
322
        }
323
324
        return [$path, $query, $headers, $body, $formData];
325
    }
326
327
    private function buildRequestUri(string $pathTemplate, array $pathParameters, array $queryParameters): UriInterface
328
    {
329
        $path = $this->uriTemplate->expand($pathTemplate, $pathParameters);
330
        $query = http_build_query($queryParameters);
331
332 4
        return $this->baseUri->withPath($path)->withQuery($query);
333
    }
334
335
    private function serializeRequestBody(array $decodedBody, string $contentType): string
336
    {
337 4
        return $this->serializer->serialize(
338
            $decodedBody,
339
            DecoderUtils::extractFormatFromContentType($contentType)
340
        );
341
    }
342 4
343 1
    private function getDataFromResponse(ResponseInterface $response, ResponseDefinition $definition, RequestInterface $request)
344
    {
345
        if (true === $this->config['returnResponse']) {
346 3
            return $response;
347 3
        }
348 3
349 3
        // @todo Find a better way to handle responses with a body definition
350
        if (!$definition->hasBodySchema()) {
351 3
            return new Item([], $request->getHeaders(), []);
352 3
        }
353 3
        $statusCode = $response->getStatusCode();
354
355
        return $this->serializer->deserialize(
356
            (string) $response->getBody(),
357
            $statusCode >= 400 && $statusCode <= 599 ? ErrorInterface::class : ResourceInterface::class,
358
            DecoderUtils::extractFormatFromContentType($response->getHeaderLine('Content-Type')),
359
            [
360
                'response' => $response,
361
                'responseDefinition' => $definition,
362
                'request' => $request,
363
            ]
364
        );
365
    }
366
367 5
    private function validateRequest(RequestInterface $request, RequestDefinition $definition)
368
    {
369 5
        if (false === $this->config['validateRequest']) {
370 1
            return;
371
        }
372
373 4
        $this->messageValidator->validateRequest($request, $definition);
374 4
        if ($this->messageValidator->hasViolations()) {
375 1
            throw new RequestViolations(
376 1
                $this->messageValidator->getViolations()
377
            );
378
        }
379 3
    }
380
381
    private function validateResponse(ResponseInterface $response, RequestDefinition $definition)
382
    {
383
        if (false === $this->config['validateResponse']) {
384
            return;
385
        }
386
387
        $this->messageValidator->validateResponse($response, $definition);
388
        if ($this->messageValidator->hasViolations()) {
389
            throw new ResponseViolations(
390 4
                $this->messageValidator->getViolations()
391
            );
392 4
        }
393 2
    }
394
}
395