Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/Exceptions/AuthenticationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace ZammadAPIClient\Exceptions;

/**
* Thrown when the Zammad API returns HTTP 401 Unauthorized.
*
* Common causes:
* - The API token is invalid, expired, or has been revoked.
* - The token was created for a different Zammad instance.
* - The `X-On-Behalf-Of` user ID does not exist or the token owner lacks
* impersonation rights.
*
* Resolution: verify the token in the Zammad admin panel under
* *Avatar → Profile → Token Access* and ensure the required permissions are
* granted.
*/
final class AuthenticationException extends \RuntimeException implements ZammadException
{
}
25 changes: 25 additions & 0 deletions src/Exceptions/NetworkException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace ZammadAPIClient\Exceptions;

/**
* Thrown when a transport-level error prevents the request from completing.
*
* Unlike HTTP error status codes (which are caught and mapped to typed
* exceptions in {@see \ZammadAPIClient\Core\RequestHandler::dispatch()}),
* this exception covers failures that occur before or after an HTTP response
* is received:
* - DNS resolution failure.
* - TLS handshake error or certificate verification failure.
* - Connection refused or timed out.
* - Unexpected HTTP status codes that do not map to a specific exception
* (i.e. 3xx without redirect following, or unrecognised 4xx).
*
* The original PSR-18 `ClientExceptionInterface` is preserved as the
* `$previous` exception and can be accessed via {@see self::getPrevious()}.
*/
final class NetworkException extends \RuntimeException implements ZammadException
{
}
22 changes: 22 additions & 0 deletions src/Exceptions/NotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace ZammadAPIClient\Exceptions;

/**
* Thrown when the Zammad API returns HTTP 404 Not Found.
*
* Indicates that no resource with the requested ID exists in Zammad.
* Possible causes:
* - The resource was deleted (tickets/users can be deleted via the API or UI).
* - The ID is correct but the authenticated user lacks visibility due to group
* or permission restrictions (Zammad returns 404 for permission-hidden resources
* to avoid information leakage).
* - A typo in the resource path or ID.
*
* The exception message includes the URI of the failing request for debugging.
*/
final class NotFoundException extends \RuntimeException implements ZammadException
{
}
34 changes: 34 additions & 0 deletions src/Exceptions/RateLimitException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace ZammadAPIClient\Exceptions;

/**
* Thrown when Zammad's rate limit is exceeded and all automatic retries fail.
*
* {@see \ZammadAPIClient\Core\RetryAfterMiddleware} intercepts HTTP 429
* responses and retries the request automatically up to the configured
* `$maxRetries` limit, honouring the `Retry-After` response header.
* This exception is only raised when all retry attempts are exhausted
* and the server still returns 429.
*
* The {@see self::$retryAfterSeconds} property reflects the last
* `Retry-After` value received, giving the caller the opportunity to
* implement custom back-off logic or queue the request for later execution.
*
* The HTTP status code (429) is set as the exception code.
*/
final class RateLimitException extends \RuntimeException implements ZammadException
{
/**
* @param string $message Human-readable rate-limit message.
* @param int $retryAfterSeconds Seconds to wait before retrying, from the last Retry-After header.
*/
public function __construct(
string $message,
public readonly int $retryAfterSeconds,
) {
parent::__construct($message, 429);
}
}
25 changes: 25 additions & 0 deletions src/Exceptions/ServerErrorException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace ZammadAPIClient\Exceptions;

/**
* Thrown when the Zammad API returns an HTTP 5xx status code.
*
* A 5xx response means the server received the request but encountered an
* unexpected internal error processing it. The client request itself was
* technically valid; the problem is on the server side.
*
* Common causes:
* - Zammad application bug or unhandled exception.
* - Underlying database or background-job failure.
* - Zammad server is in maintenance mode or being restarted.
*
* The HTTP status code is included in the exception message (e.g. "Server error: 503").
* No automatic retry is performed for 5xx errors — only HTTP 429 is retried
* by {@see \ZammadAPIClient\Core\RetryAfterMiddleware}.
*/
final class ServerErrorException extends \RuntimeException implements ZammadException
{
}
34 changes: 34 additions & 0 deletions src/Exceptions/ValidationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace ZammadAPIClient\Exceptions;

/**
* Thrown when the Zammad API returns HTTP 422 Unprocessable Entity.
*
* Indicates that the request payload was syntactically valid (parseable JSON)
* but failed Zammad's business-logic validation. The response body carries a
* human-readable `error` string and an optional `details` map; both are
* preserved for the caller to inspect or display.
*
* Common causes:
* - A required field is missing (e.g. no `title` on a ticket).
* - A field value violates a constraint (e.g. unknown state name).
* - A referenced relation does not exist (e.g. non-existent group ID).
*
* The HTTP status code (422) is set as the exception code.
*/
final class ValidationException extends \RuntimeException implements ZammadException
{
/**
* @param string $message Human-readable error summary from the `error` field.
* @param array<int|string, mixed> $errors Detailed per-field validation messages from `details`.
*/
public function __construct(
string $message,
public readonly array $errors = [],
) {
parent::__construct($message, 422);
}
}
25 changes: 25 additions & 0 deletions src/Exceptions/ZammadException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace ZammadAPIClient\Exceptions;

/**
* Marker interface for all exceptions thrown by the Zammad API client.
*
* Catching `ZammadException` allows callers to handle any client-specific
* error without knowing the concrete subtype. All concrete exceptions extend
* `RuntimeException` and implement this interface, so they can be caught as
* either `\RuntimeException` or `ZammadException`.
*
* Exception hierarchy:
* - {@see AuthenticationException} — HTTP 401: invalid token or insufficient permissions.
* - {@see NotFoundException} — HTTP 404: the requested resource does not exist.
* - {@see ValidationException} — HTTP 422: the request payload was rejected by the API.
* - {@see RateLimitException} — HTTP 429: rate limit reached (after all retries exhausted).
* - {@see ServerErrorException} — HTTP 5xx: unexpected error on the Zammad server side.
* - {@see NetworkException} — transport-level failure (DNS, TLS, connection refused, etc.).
*/
interface ZammadException extends \Throwable
{
}
Loading