Completed
Pull Request — master (#13)
by
unknown
14:29
created

ApiService::getDefaultValues()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 32
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 7

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 25
c 1
b 0
f 0
nc 7
nop 1
dl 0
loc 32
ccs 6
cts 6
cp 1
crap 7
rs 8.5866
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
                    $params[$key.'['.$k.']'] = $v;
172 1
                }
173 1
                unset($params[$key]);
174
            }
175 1
            if (false !== stripos($key, '_')) {
176
                $params[str_replace('_', '.', $key)] = $param;
177 1
                unset($params[$key]);
178
            }
179
        }
180
181
        return $params;
182
    }
183
184
    private function getBaseUri(): UriInterface
185
    {
186
        // Create a base uri from the API Schema
187 13
        if (null === $this->config['baseUri']) {
188
            $schemes = $this->schema->getSchemes();
189
            if (empty($schemes)) {
190 13
                throw new \LogicException('You need to provide at least on scheme in your API Schema');
191 10
            }
192 10
193 10
            $scheme = null;
194 1
            foreach ($this->schema->getSchemes() as $candidate) {
195
                // Always prefer https
196
                if ('https' === $candidate) {
197 9
                    $scheme = 'https';
198
                }
199 9
                if (null === $scheme && 'http' === $candidate) {
200 8
                    $scheme = 'http';
201
                }
202 9
            }
203 9
204
            if (null === $scheme) {
205
                throw new \RuntimeException('Cannot choose a proper scheme from the API Schema. Supported: https, http');
206 9
            }
207 1
208
            $host = $this->schema->getHost();
209
            if ('' === $host) {
210 8
                throw new \LogicException('The host in the API Schema should not be null');
211 8
            }
212 1
213
            return $this->uriFactory->createUri($scheme.'://'.$host);
214
        } else {
215 7
            return $this->uriFactory->createUri($this->config['baseUri']);
216
        }
217 3
    }
218
219
    private function getConfig(array $config): array
220
    {
221
        $config = array_merge(self::DEFAULT_CONFIG, $config);
222
        Assertion::boolean($config['returnResponse']);
223
        Assertion::boolean($config['validateRequest']);
224 13
        Assertion::boolean($config['validateResponse']);
225
        Assertion::nullOrString($config['baseUri']);
226 13
227 13
        return array_intersect_key($config, self::DEFAULT_CONFIG);
228 13
    }
229 13
230 13
    private function createRequestFromDefinition(RequestDefinition $definition, array $params): RequestInterface
231
    {
232 13
        $contentType = $definition->getContentTypes()[0] ?? 'application/json';
233
        $requestParameters = $definition->getRequestParameters();
234
        list($path, $query, $headers, $body, $formData) = $this->getDefaultValues($requestParameters);
235
        $headers = array_merge(
236
            $headers,
237
            ['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

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