SwaggerSchemaFactory::createParameter()   A
last analyzed

Complexity

Conditions 6
Paths 16

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 6.105

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 24
ccs 6
cts 7
cp 0.8571
rs 9.2222
c 0
b 0
f 0
cc 6
nc 16
nop 1
crap 6.105
1
<?php
2
namespace ElevenLabs\Api\Factory;
3
4
use ElevenLabs\Api\Definition\RequestDefinition;
5
use ElevenLabs\Api\Definition\Parameter;
6
use ElevenLabs\Api\Definition\Parameters;
7
use ElevenLabs\Api\Definition\RequestDefinitions;
8
use ElevenLabs\Api\Definition\ResponseDefinition;
9
use ElevenLabs\Api\Schema;
10
use ElevenLabs\Api\JsonSchema\Uri\YamlUriRetriever;
11
use JsonSchema\RefResolver;
12
use JsonSchema\Uri\UriResolver;
13
use JsonSchema\Uri\UriRetriever;
14
use Symfony\Component\Yaml\Yaml;
15
16
/**
17
 * Create a schema definition from a Swagger file
18
 */
19
class SwaggerSchemaFactory implements SchemaFactory
20
{
21 17
    /**
22
     * @param string $schemaFile (must start with a scheme: file://, http:// or https://)
23 17
     *
24
     * @return Schema
25 16
     */
26 16
    public function createSchema($schemaFile)
27 13
    {
28 13
        $schema = $this->resolveSchemaFile($schemaFile);
29 13
30
        $host = isset($schema->host) ? $schema->host : null;
31
        $basePath = isset($schema->basePath) ? $schema->basePath : '';
32
        $schemes = isset($schema->schemes) ? $schema->schemes : ['http'];
33 17
34
        return new Schema(
35 17
            $this->createRequestDefinitions($schema),
36 17
            $basePath,
37 17
            $host,
38 17
            $schemes
39 1
        );
40
    }
41
42
    /**
43
     *
44
     * @param string $schemaFile
45
     *
46
     * @return object
47 1
     */
48 1
    protected function resolveSchemaFile($schemaFile)
49 16
    {
50 15
        $extension = pathinfo($schemaFile, PATHINFO_EXTENSION);
51 15
        switch ($extension) {
52
            case 'yml':
53 1
            case 'yaml':
54 1
                if (!class_exists(Yaml::class)) {
55 1
                    // @codeCoverageIgnoreStart
56 1
                    throw new \InvalidArgumentException(
57
                        'You need to require the "symfony/yaml" component in order to parse yml files'
58
                    );
59
                    // @codeCoverageIgnoreEnd
60
                }
61 16
62 16
                $uriRetriever = new YamlUriRetriever();
63 16
                break;
64
            case 'json':
65
                $uriRetriever = new UriRetriever();
66 16
                break;
67
            default:
68
                throw new \InvalidArgumentException(
69
                    sprintf(
70
                        'file "%s" does not provide a supported extension choose either json, yml or yaml',
71 16
                        $schemaFile
72
                    )
73 16
                );
74
        }
75
76 16
        $refResolver = new RefResolver(
77
            $uriRetriever,
78 16
            new UriResolver()
79 16
        );
80 9
81
        return $refResolver->resolve($schemaFile);
82 16
    }
83 16
84
    /**
85
     * @param \stdClass $schema
86 16
     * @return RequestDefinitions
87
     */
88 16
    protected function createRequestDefinitions(\stdClass $schema)
89
    {
90 16
        $definitions = [];
91 16
        $defaultConsumedContentTypes = [];
92 16
        $defaultProducedContentTypes = [];
93
94 16
        if (isset($schema->consumes)) {
95 1
            $defaultConsumedContentTypes = $schema->consumes;
96
        }
97 16
        if (isset($schema->produces)) {
98 1
            $defaultProducedContentTypes = $schema->produces;
99
        }
100
101 16
        $basePath = isset($schema->basePath) ? $schema->basePath : '';
102
103 16
        foreach ($schema->paths as $pathTemplate => $methods) {
104 16
            foreach ($methods as $method => $definition) {
105 16
                $method = strtoupper($method);
106 16
                $contentTypes = $defaultConsumedContentTypes;
107 16
                if (isset($definition->consumes)) {
108 9
                    $contentTypes = $definition->consumes;
109
                }
110
111 16
                if (!isset($definition->operationId)) {
112 1
                    throw new \LogicException(
113 1
                        sprintf(
114 1
                            'You need to provide an operationId for %s %s',
115 1
                            $method,
116 1
                            $pathTemplate
117
                        )
118
                    );
119
                }
120
121 15
                if (empty($contentTypes) && $this->containsBodyParametersLocations($definition)) {
122 12
                    $contentTypes = $this->guessSupportedContentTypes($definition, $pathTemplate);
123
                }
124
125 14
                if (!isset($definition->responses)) {
126 1
                    throw new \LogicException(
127 1
                        sprintf(
128 1
                            'You need to specify at least one response for %s %s',
129 1
                            $method,
130 1
                            $pathTemplate
131
                        )
132
                    );
133
                }
134
135 13
                if (!isset($definition->parameters)) {
136 2
                    $definition->parameters = [];
137
                }
138
139 13
                $requestParameters = [];
140 13
                foreach ($definition->parameters as $parameter) {
141 11
                    $requestParameters[] = $this->createParameter($parameter);
142
                }
143
144 13
                $responseContentTypes = $defaultProducedContentTypes;
145 13
                if (isset($definition->produces)) {
146 11
                    $responseContentTypes = $definition->produces;
147
                }
148
149 13
                $responseDefinitions = [];
150 13
                foreach ($definition->responses as $statusCode => $response) {
151 13
                    $responseDefinitions[] = $this->createResponseDefinition(
152 13
                        $statusCode,
153 13
                        $responseContentTypes,
154 13
                        $response
155
                    );
156
                }
157
158 13
                $definitions[] = new RequestDefinition(
159 13
                    $method,
160 13
                    $definition->operationId,
161 13
                    $basePath.$pathTemplate,
162 13
                    new Parameters($requestParameters),
163 13
                    $contentTypes,
164 13
                    $responseDefinitions
165
                );
166
            }
167
        }
168
169 13
        return new RequestDefinitions($definitions);
170
    }
171
172 14
    /**
173
     * @return bool
174 14
     */
175 2
    private function containsBodyParametersLocations(\stdClass $definition)
176
    {
177
        if (!isset($definition->parameters)) {
178 12
            return false;
179 12
        }
180 12
181
        foreach ($definition->parameters as $parameter) {
182
            if (isset($parameter->in) && \in_array($parameter->in, Parameter::BODY_LOCATIONS, true)) {
183
                return true;
184 9
            }
185
        }
186
187
        return false;
188
    }
189
190 12
    /**
191
     * @param \stdClass $definition
192 12
     * @param string $pathTemplate
193
     *
194
     * @return array
195
     */
196 12
    private function guessSupportedContentTypes(\stdClass $definition, $pathTemplate)
197 12
    {
198 12
        if (!isset($definition->parameters)) {
199 12
            return [];
200
        }
201
202
        $bodyLocations = [];
203 12
        foreach ($definition->parameters as $parameter) {
204 1
            if (isset($parameter->in) && \in_array($parameter->in, Parameter::BODY_LOCATIONS, true)) {
205 1
                $bodyLocations[] = $parameter->in;
206 1
            }
207 1
        }
208 1
209
        if (count($bodyLocations) > 1) {
210
            throw new \LogicException(
211
                sprintf(
212
                    'Parameters cannot have %s locations at the same time in %s',
213 11
                    implode(' and ', $bodyLocations),
214 11
                    $pathTemplate
215
                )
216
            );
217
        }
218
219
        if (count($bodyLocations) === 1) {
220
            return [Parameter::BODY_LOCATIONS_TYPES[current($bodyLocations)]];
221
        }
222
223
        return [];
224 13
    }
225
226 13
    protected function createResponseDefinition($statusCode, array $defaultProducedContentTypes, \stdClass $response)
227 13
    {
228 9
        $allowedContentTypes = $defaultProducedContentTypes;
229 9
        $parameters = [];
230 9
        if (isset($response->schema)) {
231
            $parameters[] = $this->createParameter((object) [
232 9
                'in' => 'body',
233
                'name' => 'body',
234
                'required' => true,
235
                'schema' => $response->schema
236 13
            ]);
237 9
        }
238 9
239 9
        if (isset($response->headers)) {
240 9
            foreach ($response->headers as $headerName => $schema) {
241 9
                $schema->in = 'header';
242
                $schema->name = $headerName;
243
                $schema->required = true;
244
                $parameters[] = $this->createParameter($schema);
245 13
            }
246
        }
247
248 11
        return new ResponseDefinition($statusCode, $allowedContentTypes, new Parameters($parameters));
249
    }
250 11
251 11
    /**
252 11
     * Create a Parameter from a swagger parameter
253 11
     *
254 11
     * @param \stdClass $parameter
255
     *
256 11
     * @return Parameter
257 11
     */
258 11
    protected function createParameter(\stdClass $parameter)
259 11
    {
260
        $parameter = get_object_vars($parameter);
261
        $location = $parameter['in'];
262 11
        $name = $parameter['name'];
263 11
        $schema = isset($parameter['schema']) ? $parameter['schema'] : new \stdClass();
264
        $required = isset($parameter['required']) ? $parameter['required'] : false;
265
266
        unset($parameter['in']);
267 11
        unset($parameter['name']);
268
        unset($parameter['required']);
269
        unset($parameter['schema']);
270
271 11
        // Every remaining parameter may be json schema properties
272
        foreach ($parameter as $key => $value) {
273
            $schema->{$key} = $value;
274
        }
275
276
        // It's not relevant to validate file type
277
        if (isset($schema->format) && $schema->format === 'file') {
278
            $schema = null;
279
        }
280
281
        return new Parameter($location, $name, $required, $schema);
282
    }
283
}
284