Completed
Pull Request — master (#16)
by Woody
02:32
created

MessageValidator::validateQueryParameters()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 11
ccs 9
cts 9
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 2
crap 2
1
<?php
2
namespace ElevenLabs\Api\Validator;
3
4
use ElevenLabs\Api\Decoder\DecoderInterface;
5
use ElevenLabs\Api\Decoder\DecoderUtils;
6
use ElevenLabs\Api\Definition\MessageDefinition;
7
use ElevenLabs\Api\Definition\RequestDefinition;
8
use ElevenLabs\Api\Normalizer\QueryParamsNormalizer;
9
use JsonSchema\Validator;
10
use JsonSchema\Constraints\Constraint as JsonSchemaConstraint;
11
use Psr\Http\Message\MessageInterface;
12
use Psr\Http\Message\RequestInterface;
13
use Psr\Http\Message\ResponseInterface;
14
use Psr\Http\Message\ServerRequestInterface;
15
use Rize\UriTemplate;
16
17
/**
18
 * Provide validation methods to validate HTTP messages
19
 */
20
class MessageValidator
21
{
22
    /** @var Validator */
23
    private $validator;
24
    /** @var array */
25
    private $violations = [];
26
    /** @var DecoderInterface */
27
    private $decoder;
28
29 8
    public function __construct(Validator $validator, DecoderInterface $decoder)
30
    {
31 8
        $this->validator = $validator;
32 8
        $this->decoder = $decoder;
33 8
    }
34
35 1
    public function validateRequest(RequestInterface $request, RequestDefinition $definition)
36
    {
37 1
        if ($definition->hasBodySchema()) {
38 1
            $contentTypeValid = $this->validateContentType($request, $definition);
39 1
            if ($contentTypeValid && in_array($request->getMethod(), ['PUT', 'PATCH', 'POST'])) {
40 1
                $this->validateMessageBody($request, $definition);
41
            }
42
        }
43
44 1
        $this->validateHeaders($request, $definition);
45 1
        $this->validatePathParameters($request, $definition);
46 1
        $this->validateQueryParameters($request, $definition);
47 1
    }
48
49 1
    public function validateResponse(ResponseInterface $response, RequestDefinition $definition)
50
    {
51 1
        $responseDefinition = $definition->getResponseDefinition($response->getStatusCode());
52 1
        if ($responseDefinition->hasBodySchema()) {
53 1
            $contentTypeValid = $this->validateContentType($response, $responseDefinition);
54 1
            if ($contentTypeValid) {
55 1
                $this->validateMessageBody($response, $responseDefinition);
56
            }
57
        }
58
59 1
        $this->validateHeaders($response, $responseDefinition);
60 1
    }
61
62 3
    public function validateMessageBody(MessageInterface $message, MessageDefinition $definition)
63
    {
64 3
        if ($message instanceof ServerRequestInterface) {
65
            $bodyString = json_encode((array) $message->getParsedBody());
66
        } else {
67 3
            $bodyString = (string) $message->getBody();
68
        }
69 3
        if ($bodyString !== '' && $definition->hasBodySchema()) {
70 3
            $contentType = $message->getHeaderLine('Content-Type');
71 3
            $decodedBody = $this->decoder->decode(
72 3
                $bodyString,
73 3
                DecoderUtils::extractFormatFromContentType($contentType)
74
            );
75
76 3
            $this->validate($decodedBody, $definition->getBodySchema(), 'body');
77
        }
78 3
    }
79
80 3
    public function validateHeaders(MessageInterface $message, MessageDefinition $definition)
81
    {
82 3
        if ($definition->hasHeadersSchema()) {
83
            // Transform each header values into a string
84 3
            $headers = array_map(
85
                function (array $values) {
86 1
                    return implode(', ', $values);
87 3
                },
88 3
                $message->getHeaders()
89
            );
90
91 3
            $this->validate(
92 3
                (object) array_change_key_case($headers, CASE_LOWER),
93 3
                $definition->getHeadersSchema(),
94 3
                'header'
95
            );
96
        }
97 3
    }
98
99
    /**
100
     * Validate an HTTP message content-type against a message definition
101
     *
102
     * @param MessageInterface $message
103
     * @param MessageDefinition $definition
104
     *
105
     * @return bool When the content-type is valid
106
     */
107 4
    public function validateContentType(MessageInterface $message, MessageDefinition $definition)
108
    {
109 4
        $contentType = $message->getHeaderLine('Content-Type');
110 4
        $contentTypes = $definition->getContentTypes();
111
112 4
        if (!in_array($contentType, $contentTypes, true)) {
113 2
            if ($contentType === '') {
114 1
                $violationMessage = 'Content-Type should not be empty';
115 1
                $constraint = 'required';
116
            } else {
117 1
                $violationMessage = sprintf(
118 1
                    '%s is not a supported content type, supported: %s',
119 1
                    $message->getHeaderLine('Content-Type'),
120 1
                    implode(', ', $contentTypes)
121
                );
122 1
                $constraint = 'enum';
123
            }
124
125 2
            $this->addViolation(
126 2
                new ConstraintViolation(
127 2
                    'Content-Type',
128 2
                    $violationMessage,
129 2
                    $constraint,
130 2
                    'header'
131
                )
132
            );
133
134 2
            return false;
135
        }
136
137 2
        return true;
138
    }
139
140 2
    public function validatePathParameters(RequestInterface $request, RequestDefinition $definition)
141
    {
142 2
        if ($definition->hasPathParametersSchema()) {
143 2
            $template = new UriTemplate();
144 2
            $params = $template->extract($definition->getPathTemplate(), $request->getUri()->getPath(), false);
145 2
            $schema = $definition->getPathParametersSchema();
146
147 2
            $this->validate(
148 2
                (object) $params,
149 2
                $schema,
150 2
                'path'
151
            );
152
        }
153 2
    }
154
155 2
    public function validateQueryParameters(RequestInterface $request, RequestDefinition $definition)
156
    {
157 2
        if ($definition->hasQueryParametersSchema()) {
158 2
            parse_str($request->getUri()->getQuery(), $queryParams);
159 2
            $schema = $definition->getQueryParametersSchema();
160 2
            $queryParams = QueryParamsNormalizer::normalize($queryParams, $schema);
161
162 2
            $this->validate(
163 2
                (object) $queryParams,
164 2
                $schema,
165 2
                'query'
166
            );
167
        }
168 2
    }
169
170
    /**
171
     * @return bool
172
     */
173 8
    public function hasViolations()
174
    {
175 8
        return !empty($this->violations);
176
    }
177
178
    /**
179
     * @return ConstraintViolation[]
180
     */
181 7
    public function getViolations()
182
    {
183 7
        return $this->violations;
184
    }
185
186
    /**
187
     * @param mixed $data
188
     * @param \stdClass $schema
189
     * @param string $location (possible values: query, path, body, headers)
190
     */
191 6
    protected function validate($data, $schema, $location)
192
    {
193 6
        $this->validator->coerce($data, $schema);
194 6
        if (! $this->validator->isValid()) {
195 5
            $violations = array_map(
196
                function ($error) use ($location) {
197 5
                    return new ConstraintViolation(
198 5
                        $error['property'],
199 5
                        $error['message'],
200 5
                        $error['constraint'],
201 5
                        $location
202
                    );
203 5
                },
204 5
                $this->validator->getErrors()
205
            );
206
207 5
            foreach ($violations as $violation) {
208 5
                $this->addViolation($violation);
209
            }
210
        }
211
212 6
        $this->validator->reset();
213 6
    }
214
215 7
    protected function addViolation(ConstraintViolation $violation)
216
    {
217 7
        $this->violations[] = $violation;
218 7
    }
219
}
220