Error handling
Everything the SDK throws extends S5wException, and the exception’s class says what happened.
Branch on the class; the message text is for logs and may change.
Three kinds of failure
| What happened | Exception | Will a retry help? |
|---|---|---|
| the server did not answer: no network, DNS, refused connection, timeout | TransportException |
yes, later |
| the server answered, but not like S5W: not JSON, wrong fields | ResponseException |
no — wrong address or an incompatible version |
| the server refused: an error status | ApiException and its subclasses |
depends on the status, see below |
The full hierarchy and the status mapping — in the Exceptions reference.
The key doesn’t work, or the server is down
These are different situations, and they call for different reactions: a dead key has to be replaced, an unreachable server — waited for.
use Flytachi\Winter\SDK\S5w\Exception\ForbiddenException;
use Flytachi\Winter\SDK\S5w\Exception\TransportException;
use Flytachi\Winter\SDK\S5w\Exception\UnauthorizedException;
try {
$s5w->check();
} catch (UnauthorizedException) {
// 401: the server does not know this key — a configuration mistake
} catch (ForbiddenException) {
// 403: the key is known but disabled or expired — a new one is needed, a retry won't help
} catch (TransportException $e) {
// the server did not answer — can be retried later
if ($e->isTimeout()) {
// specifically a timeout, not a refused connection
}
}For application health checks there is a short form:
if (!$s5w->isValid()) {
// the key was refused (401 or 403)
}isValid() returns false only when the server refused the key. If the server is
unreachable it still throws TransportException — “couldn’t find out” is not “no”.
ForbiddenException also happens with a working key: when a BASIC key is used for folders(),
files() or uploads(), which need FULL.
No such file or folder
use Flytachi\Winter\SDK\S5w\Exception\NotFoundException;
try {
$file = $s5w->files()->get($id);
} catch (NotFoundException) {
return ResponseEntity::notFound();
}Often it is simpler not to catch at all:
$file = $s5w->files()->find($id); // null instead of an exception
$folder = $s5w->folders()->find('x');A file from someone else’s bucket looks exactly like a missing one: the server does not tell the two apart on purpose.
A missing folder is 422, not 404
If upload(), list() or move() names a folder that does not exist, the server answers 422:
it is a mistake in the request’s parameters, not “resource not found”. Catch ValidationException.
Validation errors
ValidationException (422) comes when parameters fail validation. It carries per-field errors —
handy to show next to a form:
use Flytachi\Winter\SDK\S5w\Exception\ValidationException;
try {
$s5w->folders()->create($request->name);
} catch (ValidationException $e) {
$e->errors();
// ["name" => ["must not be blank", "size must be between 1 and 100", …]]
$e->errorsOf('name');
// ["must not be blank", "size must be between 1 and 100", …]
}Sometimes the error is about the whole request rather than one field — no such folder, image
options for a non-image, a hash mismatch when completing an upload. Then errors() is empty and
the cause is in the message:
S5W v1/files: 422 Folder "no-such" does not exist
S5W v1/files: 422 Image options were given, but this file is not an imageThe name is taken
ConflictException (409) — when creating or renaming a folder with a taken name, and when
renaming or moving a file into a folder that already has a file with that name.
use Flytachi\Winter\SDK\S5w\Exception\ConflictException;
try {
$s5w->files()->rename($file, 'report.pdf');
} catch (ConflictException) {
$s5w->files()->rename($file, 'report-' . date('Ymd') . '.pdf');
}An upload never conflicts: on a name clash the server stores the file as report (1).pdf itself.
Not enough space
PayloadTooLargeException (413) on an upload means the bucket’s quota is full:
use Flytachi\Winter\SDK\S5w\Exception\PayloadTooLargeException;
try {
$s5w->files()->upload($path, folder: 'backups');
} catch (PayloadTooLargeException) {
$free = $s5w->check()->storage->free;
$logger->error("The bucket is full, {$free} bytes free");
}Chunked-upload failures
A chunked upload survives short failures by itself: it retries a chunk up to three times, each time asking the server how much it got. Only two special exceptions come out:
use Flytachi\Winter\SDK\S5w\Exception\UploadInterruptedException;
use Flytachi\Winter\SDK\S5w\Exception\UploadUnconfirmedException;
try {
$file = $s5w->uploads()->send($path, folder: 'videos');
} catch (UploadInterruptedException $e) {
// the retries ran out; the session lives 24 hours — it can be continued
$file = $s5w->uploads()->resume($e->sessionId(), $path);
} catch (UploadUnconfirmedException $e) {
// every byte was sent and assembly requested, but the answer was lost and the session is gone:
// the file may have been created, but its id is unknown
$logger->error("Check the file {$e->name()} by hand");
}| Exception | Meaning | What to do |
|---|---|---|
UploadInterruptedException |
the network or the server never answered after every attempt | resume($e->sessionId(), $path) later |
UploadUnconfirmedException |
the answer to assembly was lost, the session is gone | find the file by name in the folder, or upload again if a duplicate is fine |
any other ApiException |
the server refused the data itself (quota, folder, chunk size) | fix the cause; the SDK has already cancelled the session |
Why the SDK does not look the file up itself on UploadUnconfirmedException: a name search could
find someone else’s file with the same name, and guessing is worse than saying so honestly. More
in Upload pipeline.
Catch everything
use Flytachi\Winter\SDK\S5w\Exception\ApiException;
use Flytachi\Winter\SDK\S5w\S5wException;
try {
$s5w->files()->upload($path);
} catch (ApiException $e) {
$logger->warning($e->getMessage(), [
'status' => $e->status(), // 404, 409, 422…
'method' => $e->method(), // "POST"
'path' => $e->path(), // "v1/files"
'errors' => $e->errors(),
]);
} catch (S5wException $e) {
// network, an unexpected answer, a file that can't be read from disk
$logger->error($e->getMessage());
}On every exception getPrevious() holds the original cause, if there was one — the HTTP client’s
exception on a broken connection, for example.
Retries: what to retry and what not
| Exception | Retry? |
|---|---|
TransportException, ServerException (5xx) |
yes, after a pause |
UploadInterruptedException |
yes, through resume() |
UnauthorizedException, ForbiddenException |
no — another key is needed |
ValidationException, ConflictException, NotFoundException |
no — other parameters are needed |
PayloadTooLargeException |
no, until space is freed |
ResponseException |
no — check the server address |
Ordinary calls can be retried automatically on a network failure — the retries parameter of
S5wConfig. Mind that delete() is not idempotent: if the first attempt deleted the file and the
answer was lost, the retry gets 404. A single-request upload is never retried, even with
retries — a retry could store the file twice.
See also
- Exceptions — hierarchy, statuses, methods
- Large files — continuing after a failure
- The server’s error format