MessageValidator::validateMessageBody()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4.0218

Importance

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