Completed
Push — master ( 14fe81...675c2f )
by Guillem
02:29
created

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