Completed
Pull Request — develop (#273)
by Samuel
25:26 queued 13:47
created

SwaggerStrategy::process()   B

Complexity

Conditions 5
Paths 2

Size

Total Lines 30
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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