Completed
Push — master ( 2a3e77...34fcab )
by Veaceslav
05:21 queued 03:20
created

UriConstraint::createJsonValidator()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 10
ccs 5
cts 5
cp 1
rs 9.4285
cc 2
eloc 5
nc 2
nop 1
crap 2
1
<?php
2
/**
3
 * This file is part of Rebilly.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 *
8
 * @see http://rebilly.com
9
 */
10
11
namespace Rebilly\OpenAPI\PhpUnit;
12
13
use PHPUnit_Framework_Constraint as Constraint;
14
use Psr\Http\Message\UriInterface;
15
use Rebilly\OpenAPI\JsonSchema\Validator;
16
use Rebilly\OpenAPI\UnexpectedValueException;
17
18
/**
19
 * Constraint that asserts that the URI matches the expected
20
 * allowed schemes, base URI, URI paths and query-params.
21
 */
22
final class UriConstraint extends Constraint
23
{
24
    /**
25
     * @var array
26
     */
27
    private $allowedSchemes;
28
29
    /**
30
     * @var string
31
     */
32
    private $basePath;
33
34
    /**
35
     * @var array
36
     */
37
    private $templateParams;
38
39
    /**
40
     * @var array
41
     */
42
    private $queryParams;
43
44
    /**
45
     * @var string
46
     */
47
    private $host;
48
49
    /**
50
     * @var array
51
     */
52
    private $errors = [];
53
54
    /**
55
     * @var string
56
     */
57
    private $template;
58
59
    /**
60
     * @var Validator
61
     */
62
    private $validator;
63
64
    /**
65
     * @param array $expectedSchemes
66
     * @param string $host
67
     * @param string $basePath
68
     * @param string $template
69
     * @param array $pathParams
70
     * @param array $queryParams
71
     */
72 9
    public function __construct(
73
        array $expectedSchemes,
74
        $host,
75
        $basePath,
76
        $template,
77
        array $pathParams,
78
        array $queryParams
79
    ) {
80 9
        parent::__construct();
81
82 9
        $this->allowedSchemes = array_map('strtolower', $expectedSchemes);
83 9
        $this->host = $host;
84 9
        $this->basePath = $basePath;
85 9
        $this->template = $template;
86 9
        $this->templateParams = array_map([$this, 'normalizeJsonSchema'], $pathParams);
87 9
        $this->queryParams = array_map([$this, 'normalizeJsonSchema'], $queryParams);
88 9
        $this->validator = new Validator('undefined');
89
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94 9
    protected function matches($uri)
95
    {
96 9
        if (!$uri instanceof UriInterface) {
97 1
            throw new UnexpectedValueException('The object should implements UriInterface');
98
        }
99
100 8
        if (!in_array(strtolower($uri->getScheme()), $this->allowedSchemes, true)) {
101 1
            $this->errors[] = [
102 1
                'property' => 'scheme',
103 1
                'message' => 'Unsupported scheme (' . implode(', ', $this->allowedSchemes) . ')',
104
            ];
105
        }
106
107 8
        if ($uri->getHost() !== $this->host) {
108 1
            $this->errors[] = [
109 1
                'property' => 'host',
110 1
                'message' => "Unexpected host ({$this->host})",
111
            ];
112
        }
113
114 8
        if (strpos($uri->getPath(), "{$this->basePath}/") !== 0) {
115 1
            $this->errors[] = [
116 1
                'property' => 'basePath',
117 1
                'message' => "Unexpected base path ({$this->basePath})",
118
            ];
119
        } else {
120 7
            $path = substr($uri->getPath(), strlen($this->basePath) + 1);
121 7
            $actualSegments = $this->splitString('#\/#', $path);
122 7
            $expectedSegments = $this->splitString('#\/#', $this->template);
123
124 7
            if (count($actualSegments) !== count($expectedSegments)) {
125 2
                $this->errors[] = [
126 2
                    'property' => 'path',
127 2
                    'message' => "Unexpected URI path, does not match the template ({$this->template})",
128
                ];
129
            } else {
130 5
                foreach ($expectedSegments as $i => $expectedSegment) {
131 5
                    $actualSegment = $actualSegments[$i];
132
133 5
                    if (strpos($expectedSegment, '{') === false) {
134
                        // Assert path segment
135 5
                        if ($actualSegment !== $expectedSegment) {
136 1
                            $this->errors[] = [
137 1
                                'property' => 'path',
138 5
                                'message' => "Missing path segment ({$expectedSegment})",
139
                            ];
140
                        }
141
                    } else {
142
                        // Assert path param
143 5
                        $pathParamSchema = $this->templateParams[substr($expectedSegment, 1, -1)];
144
145
                        // TODO: Consider to disallow non-string params in path, that make no sense
146 5
                        $actualSegment = $this->normalizeNumericString($actualSegment);
147
148 5
                        $this->errors = array_merge(
149 5
                            $this->errors,
150 5
                            $this->validator->validate($actualSegment, $pathParamSchema, 'path')
151
                        );
152
                    }
153
                }
154
            }
155
        }
156
157 8
        parse_str($uri->getQuery(), $actualQueryParams);
158
159
        // TODO: Assert query params
160 8
        foreach ($this->queryParams as $name => $queryParamSchema) {
161 8
            if (isset($actualQueryParams[$name])) {
162 2
                $actualQueryParam = $actualQueryParams[$name];
163
164
                // TODO: Consider to disallow non-string params in query, that make no sense
165 2
                $actualQueryParam = $this->normalizeNumericString($actualQueryParam);
166
167 2
                $this->errors = array_merge(
168 2
                    $this->errors,
169 2
                    $this->validator->validate($actualQueryParam, $queryParamSchema, 'query')
170
                );
171 6
            } elseif (isset($queryParamSchema->required) && $queryParamSchema->required) {
172 6
                $this->errors[] = [
173 6
                    'property' => 'query',
174 8
                    'message' => "Missing required query param ({$name})",
175
                ];
176
            }
177
        }
178
179 8
        return empty($this->errors);
180
    }
181
182
    /**
183
     * {@inheritdoc}
184
     */
185 7
    protected function failureDescription($other)
186
    {
187 7
        return json_encode((object) $this->normalizeUri($other)) . ' ' . $this->toString();
188
    }
189
190
    /**
191
     * {@inheritdoc}
192
     */
193 7
    protected function additionalFailureDescription($other)
194
    {
195 7
        return $this->validator->serializeErrors($this->errors);
196
    }
197
198
    /**
199
     * {@inheritdoc}
200
     */
201 7
    public function toString()
202
    {
203 7
        return 'matches an specified URI parts';
204
    }
205
206
    /**
207
     * @param UriInterface $uri
208
     *
209
     * @return array
210
     */
211 7
    private static function normalizeUri(UriInterface $uri)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
212
    {
213
        return [
214 7
            'schema' => $uri->getScheme(),
215 7
            'host' => $uri->getHost(),
216 7
            'path' => $uri->getPath(),
217
        ];
218
    }
219
220
    /**
221
     * Ensure schema is object.
222
     *
223
     * @param object|array $schema
224
     *
225
     * @return object
226
     */
227 9
    private static function normalizeJsonSchema($schema)
228
    {
229 9
        return (object) $schema;
230
    }
231
232
    /**
233
     * Cast numeric values, JSON validator does not do it.
234
     *
235
     * @param mixed $value
236
     *
237
     * @return mixed
238
     */
239 5
    private static function normalizeNumericString($value)
240
    {
241 5
        if (is_numeric($value)) {
242 4
            $value += 0;
243
        }
244
245 5
        return $value;
246
    }
247
248
    /**
249
     * Split string to segments by regexp.
250
     *
251
     * @param string $pattern
252
     * @param string $subject
253
     *
254
     * @return array
255
     */
256 7
    private static function splitString($pattern, $subject)
257
    {
258 7
        return preg_split($pattern, $subject, -1, PREG_SPLIT_NO_EMPTY);
259
    }
260
}
261