Completed
Pull Request — master (#13)
by Guillem
01:26
created

SwaggerSchemaFactory   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 255
Duplicated Lines 0 %

Test Coverage

Coverage 81.34%

Importance

Changes 0
Metric Value
wmc 46
eloc 130
dl 0
loc 255
rs 8.72
c 0
b 0
f 0
ccs 109
cts 134
cp 0.8134

7 Methods

Rating   Name   Duplication   Size   Complexity  
A createSchema() 0 13 4
A resolveSchemaFile() 0 31 5
A createResponseDefinition() 0 25 5
A containsBodyParametersLocations() 0 13 5
A createParameter() 0 24 6
F createRequestDefinitions() 0 77 14
B guessSupportedContentTypes() 0 28 7

How to fix   Complexity   

Complex Class

Complex classes like SwaggerSchemaFactory often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SwaggerSchemaFactory, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace ElevenLabs\Api\Factory;
3
4
use _HumbugBox90c4dcb919ed\Symfony\Component\Console\Exception\LogicException;
0 ignored issues
show
Bug introduced by
The type _HumbugBox90c4dcb919ed\S...xception\LogicException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
5
use ElevenLabs\Api\Definition\RequestDefinition;
6
use ElevenLabs\Api\Definition\Parameter;
7
use ElevenLabs\Api\Definition\Parameters;
8
use ElevenLabs\Api\Definition\RequestDefinitions;
9
use ElevenLabs\Api\Definition\ResponseDefinition;
10
use ElevenLabs\Api\Schema;
11
use ElevenLabs\Api\JsonSchema\Uri\YamlUriRetriever;
12
use JsonSchema\RefResolver;
13
use JsonSchema\Uri\UriResolver;
14
use JsonSchema\Uri\UriRetriever;
15
use Symfony\Component\Yaml\Yaml;
16
17
/**
18
 * Create a schema definition from a Swagger file
19
 */
20
class SwaggerSchemaFactory implements SchemaFactory
21
{
22
    /**
23
     * @param string $schemaFile (must start with a scheme: file://, http://, https://, etc...)
24
     * @return Schema
25
     */
26 8
    public function createSchema($schemaFile)
27
    {
28 8
        $schema = $this->resolveSchemaFile($schemaFile);
29
30 8
        $host = (isset($schema->host)) ? $schema->host : null;
31 8
        $basePath = (isset($schema->basePath)) ? $schema->basePath : '';
32 8
        $schemes = (isset($schema->schemes)) ? $schema->schemes : ['http'];
33
34 8
        return new Schema(
35 8
            $this->createRequestDefinitions($schema),
36 7
            $basePath,
37 7
            $host,
38 7
            $schemes
39
        );
40
    }
41
42
    /**
43
     *
44
     * @param string $schemaFile
45
     *
46
     * @return object
47
     */
48 8
    protected function resolveSchemaFile($schemaFile)
49
    {
50 8
        $extension = pathinfo($schemaFile, PATHINFO_EXTENSION);
51 8
        switch ($extension) {
52 8
            case 'yml':
53 8
            case 'yaml':
54
                if (!class_exists(Yaml::class)) {
55
                    throw new \InvalidArgumentException(
56
                        'You need to require the "symfony/yaml" component in order to parse yml files'
57
                    );
58
                }
59
                $uriRetriever = new YamlUriRetriever();
60
                break;
61 8
            case 'json':
62 8
                $uriRetriever = new UriRetriever();
63 8
                break;
64
            default:
65
                throw new \InvalidArgumentException(
66
                    sprintf(
67
                        'file "%s" does not provide a supported extension choose either json, yml or yaml',
68
                        $schemaFile
69
                    )
70
                );
71
        }
72
73 8
        $refResolver = new RefResolver(
74 8
            $uriRetriever,
75 8
            new UriResolver()
76
        );
77
78 8
        return $refResolver->resolve($schemaFile);
79
    }
80
81
    /**
82
     * @param \stdClass $schema
83
     * @return RequestDefinitions
84
     */
85 8
    protected function createRequestDefinitions(\stdClass $schema)
86
    {
87 8
        $definitions = [];
88 8
        $defaultConsumedContentTypes = [];
89 8
        $defaultProducedContentTypes = [];
90
91 8
        if (isset($schema->consumes)) {
92 5
            $defaultConsumedContentTypes = $schema->consumes;
93
        }
94 8
        if (isset($schema->produces)) {
95 5
            $defaultProducedContentTypes = $schema->produces;
96
        }
97
98 8
        $basePath = isset($schema->basePath) ? $schema->basePath : '';
99
100 8
        foreach ($schema->paths as $pathTemplate => $methods) {
101 8
            foreach ($methods as $method => $definition) {
102 8
                $method = strtoupper($method);
103 8
                $contentTypes = $defaultConsumedContentTypes;
104 8
                if (isset($definition->consumes)) {
105 5
                    $contentTypes = $definition->consumes;
106
                }
107
108 8
                if (!isset($definition->operationId)) {
109
                    throw new \LogicException(
110
                        sprintf(
111
                            'You need to provide an operationId for %s %s',
112
                            $method,
113
                            $pathTemplate
114
                        )
115
                    );
116
                }
117
118 8
                if (empty($contentTypes) && $this->containsBodyParametersLocations($definition)) {
119 3
                    $contentTypes = $this->guessSupportedContentTypes($definition, $pathTemplate);
120
                }
121
122 7
                if (!isset($definition->responses)) {
123
                    throw new \LogicException(
124
                        sprintf(
125
                            'You need to specify at least one response for %s %s',
126
                            $method,
127
                            $pathTemplate
128
                        )
129
                    );
130
                }
131
132 7
                if (!isset($definition->parameters)) {
133 5
                    $definition->parameters = [];
134
                }
135
136 7
                $requestParameters = [];
137 7
                foreach ($definition->parameters as $parameter) {
138 7
                    $requestParameters[] = $this->createParameter($parameter);
139
                }
140
141 7
                $responseDefinitions = [];
142 7
                foreach ($definition->responses as $statusCode => $response) {
143 7
                    $responseDefinitions[] = $this->createResponseDefinition(
144 7
                        $statusCode,
145 7
                        $defaultProducedContentTypes,
146 7
                        $response
147
                    );
148
                }
149
150 7
                $definitions[] = new RequestDefinition(
151 7
                    $method,
152 7
                    $definition->operationId,
153 7
                    $basePath.$pathTemplate,
154 7
                    new Parameters($requestParameters),
155 7
                    $contentTypes,
156 7
                    $responseDefinitions
157
                );
158
            }
159
        }
160
161 7
        return new RequestDefinitions($definitions);
162
    }
163
164
    /**
165
     * @return bool
166
     */
167 3
    private function containsBodyParametersLocations(\stdClass $definition)
168
    {
169 3
        if (!isset($definition->parameters)) {
170
            return false;
171
        }
172
173 3
        foreach ($definition->parameters as $parameter) {
174 3
            if (isset($parameter->in) && \in_array($parameter->in, Parameter::BODY_LOCATIONS, true)) {
175 3
                return true;
176
            }
177
        }
178
179
        return false;
180
    }
181
182
    /**
183
     * @param \stdClass $definition
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