1 <?php
2 /**
3 * Copyright 2019 Klarna AB
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 * File containing the ResponseValidator class.
18 */
19
20 namespace Klarna\Rest\Transport;
21
22 use Klarna\Rest\Transport\ApiResponse;
23 use Klarna\Rest\Transport\Exception\ConnectorException;
24
25 /**
26 * HTTP response validator helper class.
27 */
28 class ResponseValidator
29 {
30 /**
31 * HTTP response to validate against.
32 *
33 * @var ApiResponse
34 */
35 protected $response;
36
37 /**
38 * Constructs a response validator instance.
39 *
40 * @param ApiResponse $response Response to validate
41 */
42 public function __construct(ApiResponse $response)
43 {
44 $this->response = $response;
45 }
46
47 /**
48 * Gets the response object.
49 *
50 * @return ApiResponse
51 */
52 public function getResponse()
53 {
54 return $this->response;
55 }
56
57 /**
58 * Asserts the HTTP response status code.
59 *
60 * @param string|string[] $status Expected status code(s)
61 *
62 * @throws \RuntimeException If status code does not match
63 *
64 * @return self
65 */
66 public function status($status)
67 {
68 $httpStatus = (string) $this->response->getStatus();
69 if (is_array($status) && !in_array($httpStatus, $status)) {
70 throw new \RuntimeException(
71 "Unexpected response status code: {$httpStatus}"
72 );
73 }
74
75 if (is_string($status) && $httpStatus !== $status) {
76 throw new \RuntimeException(
77 "Unexpected response status code: {$httpStatus}"
78 );
79 }
80
81 return $this;
82 }
83
84 /**
85 * Asserts the Content-Type header. Checks partial matching.
86 * Validation PASSES in the following cases:
87 * Content-Type: application/json
88 * $mediaType = 'application/json'
89 *
90 * Content-Type: application/json; charset=utf-8
91 * $mediaType = 'application/json'
92 *
93 * Validation FAILS in the following cases:
94 * Content-Type: plain/text
95 * $mediaType = 'application/json'
96 *
97 * Content-Type: application/json; charset=utf-8
98 * $mediaType = 'application/json; charset=cp-1251'
99 *
100 * @param string $mediaType Expected media type. RegExp rules can be used.
101 *
102 * @throws \RuntimeException If Content-Type header is missing
103 * @throws \RuntimeException If Content-Type header does not match
104 *
105 * @return self
106 */
107 public function contentType($mediaType)
108 {
109 $contentType = $this->response->getHeader('Content-Type');
110 if (empty($contentType)) {
111 throw new \RuntimeException('Response is missing a Content-Type header');
112 }
113 $mediaFound = false;
114 foreach ($contentType as $type) {
115 if (preg_match('#' . $mediaType . '#', $type)) {
116 $mediaFound = true;
117 break;
118 }
119 }
120
121 if (!$mediaFound) {
122 throw new \RuntimeException(
123 'Unexpected Content-Type header received: '
124 . implode(',', $contentType) . '. Expected: ' . $mediaType
125 );
126 }
127
128 return $this;
129 }
130
131 /**
132 * Gets the decoded JSON response.
133 *
134 * @throws \RuntimeException If the response body is not in JSON format
135 * @throws \InvalidArgumentException If the JSON cannot be parsed
136 *
137 * @return array
138 */
139 public function getJson()
140 {
141 return \json_decode($this->response->getBody(), true);
142 }
143
144 /**
145 * Gets response body.
146 *
147 * @throws \RuntimeException If the response body is not in JSON format
148 * @throws \InvalidArgumentException If the JSON cannot be parsed
149 *
150 * @return StreamInterface the body as a stream
151 */
152 public function getBody()
153 {
154 return $this->response->getBody();
155 }
156
157 /**
158 * Gets the Location header.
159 *
160 * @throws \RuntimeException If the Location header is missing
161 *
162 * @return string
163 */
164 public function getLocation()
165 {
166 $location = $this->response->getHeader('Location');
167 if (empty($location)) {
168 throw new \RuntimeException('Response is missing a Location header');
169 }
170 return $location[0];
171 }
172
173
174 /**
175 * Asserts and analyze the response. Checks if the reponse has SUCCESSFULL family
176 * and try to parse the Klarna error message if possbile.
177 *
178 * @throws ConnectorException if response has non-2xx HTTP CODE and contains
179 * a <a href="https://developers.klarna.com/api/#errors">Error</a>
180 * @throws \RuntimeException if response has non-2xx HTTP CODE and body is not parsable
181 *
182 * @return void
183 */
184 public function expectSuccessfull()
185 {
186 if ($this->isSuccessfull()) {
187 return $this;
188 }
189
190 $data = json_decode($this->response->getBody(), true);
191 if (is_array($data) && array_key_exists('error_code', $data)) {
192 throw new ConnectorException($data, $this->response->getStatus());
193 }
194
195 throw new \RuntimeException(
196 'Unexpected reponse HTTP status ' . $this->response->getStatus() .
197 '. Excepted HTTP status should be in 2xx range',
198 $this->response->getStatus()
199 );
200 }
201
202 public function isSuccessfull()
203 {
204 $status = $this->response->getStatus();
205 return $status >= 200 && $status < 300;
206 }
207 }
208