RequestSwaggerValidator::validateValueBySchema()   B
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 32
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 32
ccs 0
cts 20
cp 0
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 19
nc 2
nop 3
crap 12
1
<?php
2
3
namespace Phrest\API;
4
5
/**
6
 * @todo validate consumes/produces = json
7
 */
8
class RequestSwaggerValidator
9
{
10
    static private $errorCodeMapping = [
11
        'additionalItems' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_ADDITIONAL_ITEMS,
12
        'additionalProp' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_ADDITIONAL_PROP,
13
        'allOf' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_ALL_OF,
14
        'anyOf' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_ANY_OF,
15
        'dependencies' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_DEPENDENCIES,
16
        'disallow' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_DISALLOW,
17
        'divisibleBy' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_DIVISIBLE_BY,
18
        'enum' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_ENUM,
19
        'exclusiveMaximum' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_EXCLUSIVE_MAXIMUM,
20
        'exclusiveMinimum' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_EXCLUSIVE_MINIMUM,
21
        'format' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_FORMAT,
22
        'maximum' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_MAXIMUM,
23
        'maxItems' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_MAX_ITEMS,
24
        'maxLength' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_MAX_LENGTH,
25
        'maxProperties' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_MAX_PROPERTIES,
26
        'minimum' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_MINIMUM,
27
        'minItems' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_MIN_ITEMS,
28
        'minLength' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_MIN_LENGTH,
29
        'minProperties' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_MIN_PROPERTIES,
30
        'missingMaximum' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_MISSING_MAXIMUM,
31
        'missingMinimum' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_MISSING_MINIMUM,
32
        'multipleOf' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_MULTIPLE_OF,
33
        'not' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_NOT,
34
        'oneOf' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_ONE_OF,
35
        'pattern' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_PATTERN,
36
        'pregex' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_PREGEX,
37
        'required' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_REQUIRED,
38
        'requires' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_REQUIRES,
39
        'schema' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_SCHEMA,
40
        'type' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_TYPE,
41
        'uniqueItems' => \Phrest\API\ErrorCodes::REQUEST_VALIDATION_UNIQUE_ITEMS,
42
    ];
43
44
    static private $moreFields = [
45
        'minItems',
46
        'maxItems',
47
        'additionalItems',
48
        'enum',
49
        'format',
50
        'minimum',
51
        'maximum',
52
        'divisibleBy',
53
        'multipleOf',
54
        'pregex',
55
        'minProperties',
56
        'maxProperties',
57
        'maxLength',
58
        'minLength',
59
        'pattern',
60
    ];
61
62
    /**
63
     * @var \Phrest\Swagger
64
     */
65
    private $swagger;
66
67
    /**
68
     * @var \JsonSchema\Validator
69
     */
70
    private $validator;
71
72
    public function __construct(\Phrest\Swagger $swagger, \JsonSchema\Validator $validator)
73
    {
74
        $this->swagger = $swagger;
75
        $this->validator = $validator;
76
    }
77
78
    /**
79
     * @param \Psr\Http\Message\ServerRequestInterface $request
80
     * @param string $operationId
81
     * @return RequestSwaggerData
82
     * @throws \Phrest\Http\Exception
83
     */
84
    public function validate(\Psr\Http\Message\ServerRequestInterface $request, string $operationId): RequestSwaggerData
85
    {
86
        $parameters = $this->swagger->getParameters($operationId);
87
88
        // @todo own required validation - json-schema throws type error (NULL != string, number, ...)
89
90
        $errors = [];
91
92
        // validate query parameters
93
        $queryValues = [];
94
        foreach ($parameters->getQueryParameters() as $parameterName => $parameter) {
95
            $required = $parameter['required'] ?? false;
96
            $value = $request->getQueryParams()[$parameterName] ?? ($parameter['default'] ?? null);
97
            if(!is_null($value) || $required) {
98
                $errors = array_merge(
99
                    $errors,
100
                    $this->validateValueBySchema($value, $parameter, '{query}/' . $parameterName)
101
                );
102
            }
103
            $queryValues[$parameterName] = $value;
104
        }
105
106
        // validate header parameters
107
        $headerValues = [];
108
        foreach ($parameters->getHeaderParameters() as $parameterName => $parameter) {
109
            $required = $parameter['required'] ?? false;
110
            $value = $request->getHeader($parameterName)[0] ?? ($parameter['default'] ?? null);
111
            if(!is_null($value) || $required) {
112
                $errors = array_merge(
113
                    $errors,
114
                    $this->validateValueBySchema($value, $parameter, '{header}/' . $parameterName)
115
                );
116
            }
117
            $headerValues[$parameterName] = $value;
118
        }
119
120
        // validate path parameters
121
        $pathValues = [];
122
        foreach ($parameters->getPathParameters() as $parameterName => $parameter) {
123
            /* @todo default: does this make sense? */
124
            $value = $request->getAttribute($parameterName, $parameter['default'] ?? null);
125
            $errors = array_merge(
126
                $errors,
127
                $this->validateValueBySchema($value, $parameter, '{path}/' . $parameterName)
128
            );
129
            $pathValues[$parameterName] = $value;
130
        }
131
132
        // validate body
133
        $bodyValue = null;
134
        $bodyParameter = $parameters->getBodyParameter();
135
        if (count($bodyParameter)) {
136
            $value = $request->getAttribute(\Phrest\Middleware\JsonRequestBody::JSON_OBJECT_ATTRIBUTE);
137
            $errors = array_merge(
138
                $errors,
139
                $this->validateValueBySchema($value, $bodyParameter['schema'], '{body}')
140
            );
141
            $bodyValue = $value;
142
        }
143
144
        if (count($errors)) {
145
            throw \Phrest\Http\Exception::BadRequest(
146
                new \Phrest\API\Error(
147
                    \Phrest\API\ErrorCodes::REQUEST_PARAMETER_VALIDATION,
148
                    'request parameter validation failed. OperationId: '.$operationId,
149
                    ...$errors
150
                )
151
            );
152
        }
153
154
        return new RequestSwaggerData($bodyValue, $queryValues, $pathValues, $headerValues);
155
    }
156
157
    private function validateValueBySchema(&$value, $schema, $fieldName)
158
    {
159
        $this->validator->reset();
160
161
        $this->validator->validate(
162
            $value,
163
            $schema,
164
            \JsonSchema\Constraints\Constraint::CHECK_MODE_NORMAL
165
            | \JsonSchema\Constraints\Constraint::CHECK_MODE_APPLY_DEFAULTS
166
            | \JsonSchema\Constraints\Constraint::CHECK_MODE_COERCE_TYPES
167
        );
168
169
        $errors = [];
170
        if (!$this->validator->isValid()) {
171
            foreach ($this->validator->getErrors() as $validatorError) {
172
                $more = array_intersect_key(
173
                    $validatorError,
174
                    array_flip(self::$moreFields)
175
                );
176
177
                $errors[] = new \Phrest\API\ErrorEntry(
178
                    self::$errorCodeMapping[$validatorError['constraint']] ?? \Phrest\API\ErrorCodes::UNKNOWN,
179
                    $fieldName . $validatorError['pointer'],
180
                    $validatorError['message'],
181
182
                    /** @todo: find better format - custom formatter? */
183
                    json_encode($more)
184
                );
185
            }
186
        }
187
188
        return $errors;
189
    }
190
}
191