Passed
Push — master ( 9b5861...5bdc47 )
by Joao
06:11 queued 16s
created

SwaggerBody::matchBool()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
cc 4
nc 3
nop 3
1
<?php
2
3
namespace ByJG\Swagger;
4
5
use ByJG\Swagger\Exception\GenericSwaggerException;
6
use ByJG\Swagger\Exception\InvalidRequestException;
7
use ByJG\Swagger\Exception\NotMatchedException;
8
use InvalidArgumentException;
9
10
abstract class SwaggerBody
11
{
12
    const SWAGGER_PROPERTIES="properties";
13
    const SWAGGER_REQUIRED="required";
14
15
    /**
16
     * @var SwaggerSchema
17
     */
18
    protected $swaggerSchema;
19
20
    protected $structure;
21
22
    protected $name;
23
24
    /**
25
     * OpenApi 2.0 does not describe null values, so this flag defines,
26
     * if match is ok when one of property, which has type, is null
27
     *
28
     * @var bool
29
     */
30
    protected $allowNullValues;
31
32
    /**
33
     * SwaggerRequestBody constructor.
34
     *
35
     * @param SwaggerSchema $swaggerSchema
36
     * @param string $name
37
     * @param array $structure
38
     * @param bool $allowNullValues
39
     */
40
    public function __construct(SwaggerSchema $swaggerSchema, $name, $structure, $allowNullValues = false)
41
    {
42
        $this->swaggerSchema = $swaggerSchema;
43
        $this->name = $name;
44
        if (!is_array($structure)) {
45
            throw new InvalidArgumentException('I expected the structure to be an array');
46
        }
47
        $this->structure = $structure;
48
        $this->allowNullValues = $allowNullValues;
49
    }
50
51
    abstract public function match($body);
52
53
    /**
54
     * @param $name
55
     * @param $schema
56
     * @param $body
57
     * @param $type
58
     * @return bool
59
     * @throws NotMatchedException
60
     */
61
    protected function matchString($name, $schema, $body, $type)
62
    {
63
        if ($type !== 'string') {
64
            return null;
65
        }
66
67
        if (isset($schema['enum']) && !in_array($body, $schema['enum'])) {
68
            throw new NotMatchedException("Value '$body' in '$name' not matched in ENUM. ", $this->structure);
69
        }
70
71
        return true;
72
    }
73
74
    /**
75
     * @param $name
76
     * @param $body
77
     * @param $type
78
     * @return bool
79
     * @throws NotMatchedException
80
     */
81
    protected function matchNumber($name, $body, $type)
82
    {
83
        if ($type !== 'integer' && $type !== 'float' && $type !== 'number') {
84
            return null;
85
        }
86
87
        if (!is_numeric($body)) {
88
            throw new NotMatchedException("Expected '$name' to be numeric, but found '$body'. ", $this->structure);
89
        }
90
91
        return true;
92
    }
93
94
    /**
95
     * @param $name
96
     * @param $body
97
     * @param $type
98
     * @return bool
99
     * @throws NotMatchedException
100
     */
101
    protected function matchBool($name, $body, $type)
102
    {
103
        if ($type !== 'bool' && $type !== 'boolean') {
104
            return null;
105
        }
106
107
        if (!is_bool($body)) {
108
            throw new NotMatchedException("Expected '$name' to be boolean, but found '$body'. ", $this->structure);
109
        }
110
111
        return true;
112
    }
113
114
    /**
115
     * @param $name
116
     * @param $schema
117
     * @param $body
118
     * @param $type
119
     * @return bool
120
     * @throws Exception\DefinitionNotFoundException
121
     * @throws Exception\InvalidDefinitionException
122
     * @throws GenericSwaggerException
123
     * @throws InvalidRequestException
124
     * @throws NotMatchedException
125
     */
126
    protected function matchArray($name, $schema, $body, $type)
127
    {
128
        if ($type !== 'array') {
129
            return null;
130
        }
131
132
        foreach ((array)$body as $item) {
133
            if (!isset($schema['items'])) {  // If there is no type , there is no test.
134
                continue;
135
            }
136
            $this->matchSchema($name, $schema['items'], $item);
137
        }
138
        return true;
139
    }
140
141
    protected function matchTypes($name, $schema, $body)
142
    {
143
        if (!isset($schema['type'])) {
144
            return null;
145
        }
146
147
        $type = $schema['type'];
148
149
        $validators = [
150
            function () use ($name, $body, $type)
151
            {
152
                return $this->matchNull($name, $body, $type);
153
            },
154
155
            function () use ($name, $schema, $body, $type)
156
            {
157
                return $this->matchString($name, $schema, $body, $type);
158
            },
159
160
            function () use ($name, $body, $type)
161
            {
162
                return $this->matchNumber($name, $body, $type);
163
            },
164
165
            function () use ($name, $body, $type)
166
            {
167
                return $this->matchBool($name, $body, $type);
168
            },
169
170
            function () use ($name, $schema, $body, $type)
171
            {
172
                return $this->matchArray($name, $schema, $body, $type);
173
            }
174
        ];
175
176
        foreach ($validators as $validator) {
177
            $result = $validator();
178
            if (!is_null($result)) {
179
                return $result;
180
            }
181
        }
182
183
        return null;
184
    }
185
186
    /**
187
     * @param $name
188
     * @param $schema
189
     * @param $body
190
     * @return bool|null
191
     * @throws Exception\DefinitionNotFoundException
192
     * @throws Exception\InvalidDefinitionException
193
     * @throws GenericSwaggerException
194
     * @throws InvalidRequestException
195
     * @throws NotMatchedException
196
     */
197
    public function matchObjectProperties($name, $schema, $body)
198
    {
199
        if (!isset($schema[self::SWAGGER_PROPERTIES])) {
200
            return null;
201
        }
202
203
        if (!is_array($body)) {
204
            throw new InvalidRequestException(
205
                "I expected an array here, but I got an string. Maybe you did wrong request?",
206
                $body
207
            );
208
        }
209
210
        if (!isset($schema[self::SWAGGER_REQUIRED])) {
211
            $schema[self::SWAGGER_REQUIRED] = [];
212
        }
213
        foreach ($schema[self::SWAGGER_PROPERTIES] as $prop => $def) {
214
            $required = array_search($prop, $schema[self::SWAGGER_REQUIRED]);
215
216
            if (!array_key_exists($prop, $body)) {
217
                if ($required !== false) {
218
                    throw new NotMatchedException("Required property '$prop' in '$name' not found in object");
219
                }
220
                unset($body[$prop]);
221
                continue;
222
            }
223
224
            $this->matchSchema($prop, $def, $body[$prop]);
225
            unset($schema[self::SWAGGER_PROPERTIES][$prop]);
226
            if ($required !== false) {
227
                unset($schema[self::SWAGGER_REQUIRED][$required]);
228
            }
229
            unset($body[$prop]);
230
        }
231
232
        if (count($schema[self::SWAGGER_REQUIRED]) > 0) {
233
            throw new NotMatchedException(
234
                "The required property(ies) '"
235
                . implode(', ', $schema[self::SWAGGER_REQUIRED])
236
                . "' does not exists in the body.",
237
                $this->structure
238
            );
239
        }
240
241
        if (count($body) > 0) {
242
            throw new NotMatchedException(
243
                "The property(ies) '"
244
                . implode(', ', array_keys($body))
245
                . "' has not defined in '$name'",
246
                $body
247
            );
248
        }
249
        return true;
250
    }
251
252
    /**
253
     * @param string $name
254
     * @param $schema
255
     * @param array $body
256
     * @return bool
257
     * @throws Exception\DefinitionNotFoundException
258
     * @throws Exception\InvalidDefinitionException
259
     * @throws GenericSwaggerException
260
     * @throws InvalidRequestException
261
     * @throws NotMatchedException
262
     */
263
    protected function matchSchema($name, $schema, $body)
264
    {
265
        // Match Single Types
266
        if ($this->matchTypes($name, $schema, $body)) {
267
            return true;
268
        }
269
270
        if(!isset($schema['$ref']) && isset($schema['content'])) {
271
            $schema['$ref'] = $schema['content'][key($schema['content'])]['schema']['$ref'];
272
        }
273
274
        // Get References and try to match it again
275
        if (isset($schema['$ref'])) {
276
            $defintion = $this->swaggerSchema->getDefintion($schema['$ref']);
277
            return $this->matchSchema($schema['$ref'], $defintion, $body);
278
        }
279
280
        // Match object properties
281
        if ($this->matchObjectProperties($name, $schema, $body)) {
282
            return true;
283
        }
284
285
        /**
286
         * OpenApi 2.0 does not describe ANY object value
287
         * But there is hack that makes ANY object possible, described in link below
288
         * To make that hack works, we need such condition
289
         * @link https://stackoverflow.com/questions/32841298/swagger-2-0-what-schema-to-accept-any-complex-json-value
290
         */
291
        if ($schema === []) {
292
            return true;
293
        }
294
295
        throw new GenericSwaggerException("Not all cases are defined. Please open an issue about this. Schema: $name");
296
    }
297
298
    /**
299
     * @param $name
300
     * @param $body
301
     * @param $type
302
     * @return bool
303
     * @throws NotMatchedException
304
     */
305
    protected function matchNull($name, $body, $type)
306
    {
307
        if (!is_null($body)) {
308
            return null;
309
        }
310
311
        if (false === $this->swaggerSchema->isAllowNullValues()) {
312
            throw new NotMatchedException(
313
                "Value of property '$name' is null, but should be of type '$type'",
314
                $this->structure
315
            );
316
        }
317
318
        return true;
319
    }
320
}
321