Completed
Pull Request — develop (#273)
by Samuel
23:34 queued 09:33
created

SwaggerStrategy::getServiceSchema()   D

Complexity

Conditions 9
Paths 11

Size

Total Lines 39
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 16.7971

Importance

Changes 4
Bugs 1 Features 1
Metric Value
dl 0
loc 39
ccs 13
cts 24
cp 0.5417
rs 4.9091
c 4
b 1
f 1
cc 9
eloc 24
nc 11
nop 1
crap 16.7971
1
<?php
2
/**
3
 * SwaggerStrategy
4
 */
5
6
namespace Graviton\ProxyBundle\Definition\Loader\DispersalStrategy;
7
8
use Graviton\ProxyBundle\Definition\ApiDefinition;
9
use Swagger\Document;
10
use Swagger\Exception\MissingDocumentPropertyException;
11
use Swagger\Object\AbstractObject;
12
use Swagger\Object\Parameter;
13
use Swagger\Object\Reference;
14
use Swagger\OperationReference;
15
16
/**
17
 * process a swagger.json file and return an APi definition
18
 *
19
 * @author  List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
20
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
21
 * @link    http://swisscom.ch
22
 */
23
class SwaggerStrategy implements DispersalStrategyInterface
24
{
25
    /**
26
     * @var array
27
     */
28
    private $fallbackData = [];
29
30
    /**
31
     *
32
     */
33
    private $document;
34
35
    /**
36
     * constructor
37
     *
38
     * @param Document $document Swagger document parser
39
     */
40 4
    public function __construct(Document $document)
41
    {
42 4
        $this->document = $document;
43 4
    }
44
45
    /**
46
     * process data
47
     *
48
     * @param string $input        JSON information about the swagger service.
49
     * @param array  $fallbackData Set of information to be registered in case the swagger info is not complete.
50
     *
51
     * @return ApiDefinition
52
     */
53 2
    public function process($input, array $fallbackData = [])
54
    {
55 2
        $this->registerFallbackData($fallbackData);
56 1
        $apiDef = new ApiDefinition();
57
58
        /**
59
         * @var \stdClass $swagger
60
         */
61 1
        $swagger = $this->decodeJson($input);
62 1
        if (is_object($swagger)) {
63 1
            $this->document->setDocument($swagger);
64 1
            $this->setBaseValues($apiDef);
65
66 1
            $operations = $this->document->getOperationsById();
67 1
            foreach ($operations as $service) {
68 1
                $path = $this->normalizePath($service->getPath());
69
70 1
                if (in_array(strtolower($service->getMethod()), ['delete', 'patch']) || $apiDef->hasEndpoint($path)) {
71 1
                    continue;
72
                }
73 1
                $apiDef->addEndpoint($path);
74 1
                $apiDef->addSchema(
75 1
                    $path,
76 1
                    $this->getServiceSchema($service)
77 1
                );
78 1
                $apiDef->setOrigin($this->document);
79 1
            }
80 1
        }
81
82 1
        return $apiDef;
83
    }
84
85
    /**
86
     * is input data valid json
87
     *
88
     * @param string $input json string
89
     *
90
     * @return boolean
91
     */
92 2
    public function supports($input)
93
    {
94
        /**
95
         * @var array $swagger
96
         */
97 2
        $swagger = $this->decodeJson($input, true);
98
99 2
        $mandatoryFields = ['swagger', 'info', 'paths', 'version', 'title', 'definitions'];
100 2
        $fields = array_merge(array_keys($swagger), array_keys($swagger['info']));
101 2
        $intersect = array_intersect($mandatoryFields, $fields);
102
103
        // every mandatory field was found in provided json definition.
104 2
        return empty(array_diff($mandatoryFields, $intersect));
105
    }
106
107
    /**
108
     * decode a json string
109
     *
110
     * @param string $input json string
111
     * @param bool   $assoc Force the encoded result to be a hash.
112
     *
113
     * @return array|\stdClass
114
     */
115 3
    private function decodeJson($input, $assoc = false)
116
    {
117 3
        $input = trim($input);
118
119 3
        return json_decode($input, $assoc);
120
    }
121
122
    /**
123
     * set base values
124
     *
125
     * @param ApiDefinition $apiDef API definition
126
     *
127
     * @return void
128
     */
129 1
    private function setBaseValues(ApiDefinition $apiDef)
130
    {
131 1
        $this->registerHost($apiDef);
132 1
        $basePath = $this->document->getBasePath();
133 1
        if (isset($basePath)) {
134
            $apiDef->setBasePath($basePath);
135
        }
136 1
    }
137
138
    /**
139
     * get the schema
140
     *
141
     * @param OperationReference $service service endpoint
142
     *
143
     * @return \stdClass
144
     */
145 1
    private function getServiceSchema($service)
146
    {
147 1
        $operation = $service->getOperation();
148 1
        $schema = new \stdClass();
149 1
        switch (strtolower($service->getMethod())) {
150 1
            case "post":
151 1
            case "put":
152
                try {
153
                    $parameters = $operation->getDocumentObjectProperty('parameters', Parameter\Body::class, true);
154
                } catch (MissingDocumentPropertyException $e) {
155
                    // request has no params
156
                    break;
157
                }
158
                foreach ($parameters as $parameter) {
159
                    /**
160
                     * there is no schema information available, if $action->parameters[0]->in != 'body'
161
                     *
162
                     * @link http://swagger.io/specification/#parameterObject
163
                     */
164
                    if ($parameter instanceof Parameter\Body && $parameter->getIn() === 'body') {
165
                        $ref = $parameter->getDocumentObjectProperty('schema', Reference::class)->getDocument();
166
                        $schema = $this->resolveSchema($ref);
167
                        break;
168
                    }
169
                }
170
                break;
171 1
            case "get":
172
                try {
173 1
                    $response = $operation->getResponses()->getHttpStatusCode(200);
174 1
                } catch (MissingDocumentPropertyException $e) {
175
                    // no response with status code 200 is defined
176
                    break;
177
                }
178 1
                $schema = $this->resolveSchema($response->getSchema()->getDocument());
179 1
                break;
180 1
        }
181
182 1
        return $schema;
183
    }
184
185
    /**
186
     * resolve schema
187
     *
188
     * @param \stdClass $reference reference
189
     *
190
     * @return \stdClass
191
     */
192 1
    private function resolveSchema($reference)
193
    {
194 1
        $schema = $reference;
195 1
        if (property_exists($reference, '$ref')) {
196 1
            $schemaResolver = $this->document->getSchemaResolver();
197 1
            $ref = new Reference($reference);
198 1
            $schema = $schemaResolver->resolveReference($ref)->getDocument();
199 1
        } elseif ($reference->type === 'array' && !empty($reference->items)) {
200
            $schema->items = $this->resolveSchema($reference->items);
201
        }
202
203
        // resolve properties
204 1
        if (!empty($schema->properties)) {
205 1
            $properties = (array) $schema->properties;
206 1
            foreach ($properties as $name => $property) {
207 1
                if (isset($property->type)
208 1
                    && $property->type === 'array'
209 1
                    && isset($property->items)
210 1
                    && property_exists($property->items, '$ref')) {
211
                    $schema->properties->$name->items = $this->resolveSchema($property->items);
212 1
                } elseif (property_exists($property, '$ref')) {
213 1
                    $schema->properties->$name = $this->resolveSchema($property);
214 1
                }
215 1
            }
216 1
        }
217
218 1
        return $schema;
219
    }
220
221
    /**
222
     * Sets the destination host for the api definition.
223
     *
224
     * @param ApiDefinition $apiDef Configuration for the swagger api to be recognized.
225
     *
226
     * @return void
227
     */
228 1
    private function registerHost(ApiDefinition $apiDef)
229
    {
230
        try {
231 1
            $host = $this->document->getHost();
232 1
        } catch (MissingDocumentPropertyException $e) {
233 1
            $host = $this->fallbackData['host'];
234
        }
235 1
        $apiDef->setHost($host);
236 1
    }
237
238
    /**
239
     * Set of information to be used as default if not defined by the swagger configuration.
240
     *
241
     * @param array $fallbackData Set of default information (e.g. host)
242
     *
243
     * @return void
244
     */
245 2
    private function registerFallbackData(array $fallbackData)
246
    {
247 2
        if (!array_key_exists('host', $fallbackData)) {
248 1
            throw new \RuntimeException('Missing mandatory key (host) in fallback data set.');
249
        }
250
251 1
        $this->fallbackData = $fallbackData;
252 1
    }
253
254
    /**
255
     * Normalizes the provided path.
256
     *
257
     * The idea is to consolidate endpoints for GET requests.
258
     *
259
     * <code>
260
     *   /my/path/      » /my/path
261
     *   /my/path/{id}  » /my/path
262
     * </code>
263
     *
264
     * @param string $path path to be normalized
265
     *
266
     * @return string
267
     *
268
     * @todo: determine how to treat endpoints with a variable within the path: /my/path/{id}/special
269
     */
270 1
    private function normalizePath($path)
271
    {
272 1
        return preg_replace('@\/(\{[a-zA-Z]*\})?$@', '', $path);
273
    }
274
}
275