Passed
Pull Request — master (#13)
by Guillem
02:10 queued 49s
created

SwaggerSchemaFactory::resolveSchemaFile()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 31
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6.9683

Importance

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