1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
|
<?php declare(strict_types=1);
namespace OpenCloud\Common\Error;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\ClientException;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Class responsible for building meaningful exceptions. For HTTP problems, it produces a {@see HttpError}
* exception, and supplies a error message with reasonable defaults. For user input problems, it produces a
* {@see UserInputError} exception. For both, the problem is described, a potential solution is offered and
* a link to further information is included.
*
* @package OpenCloud\Common\Error
*/
class Builder
{
/**
* The default domain to use for further link documentation.
*
* @var string
*/
private $docDomain = 'http://docs.php-opencloud.com/en/latest/';
/**
* The HTTP client required to validate the further links.
*
* @var ClientInterface
*/
private $client;
/**
* @param ClientInterface $client
*/
public function __construct(ClientInterface $client = null)
{
$this->client = $client ?: new Client();
}
/**
* Internal method used when outputting headers in the error description.
*
* @param $name
*
* @return string
*/
private function header(string $name): string
{
return sprintf("%s\n%s\n", $name, str_repeat('~', strlen($name)));
}
/**
* Before outputting custom links, it is validated to ensure that the user is not
* directed off to a broken link. If a 404 is detected, it is hidden.
*
* @param $link The proposed link
*
* @return bool
*/
private function linkIsValid(string $link): bool
{
$link = $this->docDomain . $link;
try {
return $this->client->request('HEAD', $link)->getStatusCode() < 400;
} catch (ClientException $e) {
return false;
}
}
/**
* @param MessageInterface $message
*
* @codeCoverageIgnore
* @return string
*/
public function str(MessageInterface $message): string
{
if ($message instanceof RequestInterface) {
$msg = trim($message->getMethod() . ' '
. $message->getRequestTarget())
. ' HTTP/' . $message->getProtocolVersion();
if (!$message->hasHeader('host')) {
$msg .= "\r\nHost: " . $message->getUri()->getHost();
}
} elseif ($message instanceof ResponseInterface) {
$msg = 'HTTP/' . $message->getProtocolVersion() . ' '
. $message->getStatusCode() . ' '
. $message->getReasonPhrase();
}
foreach ($message->getHeaders() as $name => $values) {
$msg .= "\r\n{$name}: " . implode(', ', $values);
}
if ($message->getBody()->getSize() < ini_get('memory_limit')) {
$msg .= "\r\n\r\n" . $message->getBody();
}
return $msg;
}
/**
* Helper method responsible for constructing and returning {@see BadResponseError} exceptions.
*
* @param RequestInterface $request The faulty request
* @param ResponseInterface $response The error-filled response
*
* @return BadResponseError
*/
public function httpError(RequestInterface $request, ResponseInterface $response): BadResponseError
{
$message = $this->header('HTTP Error');
$message .= sprintf("The remote server returned a \"%d %s\" error for the following transaction:\n\n",
$response->getStatusCode(), $response->getReasonPhrase());
$message .= $this->header('Request');
$message .= trim($this->str($request)) . PHP_EOL . PHP_EOL;
$message .= $this->header('Response');
$message .= trim($this->str($response)) . PHP_EOL . PHP_EOL;
$message .= $this->header('Further information');
$message .= $this->getStatusCodeMessage($response->getStatusCode());
$message .= "Visit http://docs.php-opencloud.com/en/latest/http-codes for more information about debugging "
. "HTTP status codes, or file a support issue on https://github.com/php-opencloud/openstack/issues.";
$e = new BadResponseError($message);
$e->setRequest($request);
$e->setResponse($response);
return $e;
}
private function getStatusCodeMessage(int $statusCode): string
{
$errors = [
400 => 'Please ensure that your input values are valid and well-formed. ',
401 => 'Please ensure that your authentication credentials are valid. ',
404 => "Please ensure that the resource you're trying to access actually exists. ",
500 => 'Please try this operation again once you know the remote server is operational. ',
];
return isset($errors[$statusCode]) ? $errors[$statusCode] : '';
}
/**
* Helper method responsible for constructing and returning {@see UserInputError} exceptions.
*
* @param string $expectedType The type that was expected from the user
* @param mixed $userValue The incorrect value the user actually provided
* @param string|null $furtherLink A link to further information if necessary (optional).
*
* @return UserInputError
*/
public function userInputError(string $expectedType, $userValue, string $furtherLink = null): UserInputError
{
$message = $this->header('User Input Error');
$message .= sprintf("%s was expected, but the following value was passed in:\n\n%s\n",
$expectedType, print_r($userValue, true));
$message .= "Please ensure that the value adheres to the expectation above. ";
if ($furtherLink && $this->linkIsValid($furtherLink)) {
$message .= sprintf("Visit %s for more information about input arguments. ", $this->docDomain . $furtherLink);
}
$message .= 'If you run into trouble, please open a support issue on https://github.com/php-opencloud/openstack/issues.';
return new UserInputError($message);
}
}
|