Skip to content

Commit 0f18425

Browse files
author
Mohamed Khaled
committed
Implement semantic exception hierarchy with proper inheritance patterns
1 parent 4314134 commit 0f18425

File tree

3 files changed

+102
-134
lines changed

3 files changed

+102
-134
lines changed

src/Providers/Http/Exception/ClientException.php

Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44

55
namespace WordPress\AiClient\Providers\Http\Exception;
66

7+
use Psr\Http\Message\RequestInterface;
8+
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
9+
use WordPress\AiClient\Providers\Http\DTO\Request;
710
use WordPress\AiClient\Providers\Http\DTO\Response;
11+
use WordPress\AiClient\Providers\Http\Utilities\ErrorMessageExtractor;
812

913
/**
1014
* Exception thrown for 4xx HTTP client errors.
@@ -14,8 +18,35 @@
1418
*
1519
* @since n.e.x.t
1620
*/
17-
class ClientException extends RequestException
21+
class ClientException extends InvalidArgumentException
1822
{
23+
/**
24+
* The request that failed.
25+
*
26+
* @var Request|null
27+
*/
28+
protected ?Request $request = null;
29+
30+
/**
31+
* Returns the request that failed as our Request DTO.
32+
*
33+
* @since n.e.x.t
34+
*
35+
* @return Request
36+
* @throws \RuntimeException If no request is available
37+
*/
38+
public function getRequest(): Request
39+
{
40+
if ($this->request === null) {
41+
throw new \RuntimeException(
42+
'Request object not available. This exception was directly instantiated. ' .
43+
'Use a factory method that provides request context.'
44+
);
45+
}
46+
47+
return $this->request;
48+
}
49+
1950
/**
2051
* Creates a ClientException from a 400 Bad Request response.
2152
*
@@ -30,6 +61,43 @@ public static function fromBadRequestResponse(string $errorDetail = 'Invalid req
3061
return new self($message, 400);
3162
}
3263

64+
/**
65+
* Creates a ClientException from a bad request.
66+
*
67+
* @since n.e.x.t
68+
*
69+
* @param RequestInterface $psrRequest The PSR-7 request that failed.
70+
* @param string $errorDetail Details about what made the request bad.
71+
* @return self
72+
*/
73+
public static function fromBadRequest(
74+
RequestInterface $psrRequest,
75+
string $errorDetail = 'Invalid request parameters'
76+
): self {
77+
$request = Request::fromPsrRequest($psrRequest);
78+
$message = sprintf('Bad request to %s (400): %s', $request->getUri(), $errorDetail);
79+
80+
$exception = new self($message, 400);
81+
$exception->request = $request;
82+
return $exception;
83+
}
84+
85+
/**
86+
* Creates a ClientException from a bad request to a specific URI.
87+
*
88+
* @since n.e.x.t
89+
*
90+
* @param string $uri The URI that was requested.
91+
* @param string $errorDetail Details about what made the request bad.
92+
* @return self
93+
*
94+
* @deprecated Use fromBadRequest() with RequestInterface for better type safety
95+
*/
96+
public static function fromBadRequestToUri(string $uri, string $errorDetail = 'Invalid request parameters'): self
97+
{
98+
return new self(sprintf('Bad request to %s (400): %s', $uri, $errorDetail), 400);
99+
}
100+
33101
/**
34102
* Creates a ClientException from a client error response (4xx).
35103
*
@@ -48,28 +116,10 @@ public static function fromClientError(Response $response): self
48116
$response->getStatusCode()
49117
);
50118

51-
// Handle common error formats in API responses
52-
$data = $response->getData();
53-
if (
54-
is_array($data) &&
55-
isset($data['error']) &&
56-
is_array($data['error']) &&
57-
isset($data['error']['message']) &&
58-
is_string($data['error']['message'])
59-
) {
60-
$errorMessage .= ' - ' . $data['error']['message'];
61-
} elseif (
62-
is_array($data) &&
63-
isset($data['error']) &&
64-
is_string($data['error'])
65-
) {
66-
$errorMessage .= ' - ' . $data['error'];
67-
} elseif (
68-
is_array($data) &&
69-
isset($data['message']) &&
70-
is_string($data['message'])
71-
) {
72-
$errorMessage .= ' - ' . $data['message'];
119+
// Extract error message from response data using centralized utility
120+
$extractedError = ErrorMessageExtractor::extractFromResponseData($response->getData());
121+
if ($extractedError !== null) {
122+
$errorMessage .= ' - ' . $extractedError;
73123
}
74124

75125
return new self($errorMessage, $response->getStatusCode());

src/Providers/Http/Exception/NetworkException.php

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44

55
namespace WordPress\AiClient\Providers\Http\Exception;
66

7-
use Psr\Http\Client\NetworkExceptionInterface;
87
use Psr\Http\Message\RequestInterface;
8+
use WordPress\AiClient\Common\Exception\RuntimeException;
9+
use WordPress\AiClient\Providers\Http\DTO\Request;
910

1011
/**
1112
* Exception thrown for network-related errors.
@@ -15,54 +16,55 @@
1516
*
1617
* @since n.e.x.t
1718
*/
18-
class NetworkException extends RequestException implements NetworkExceptionInterface
19+
class NetworkException extends RuntimeException
1920
{
2021
/**
2122
* The request that failed.
2223
*
23-
* @var RequestInterface|null
24+
* @var Request|null
2425
*/
25-
private ?RequestInterface $request = null;
26+
protected ?Request $request = null;
27+
28+
/**
29+
* Returns the request that failed as our Request DTO.
30+
*
31+
* @since n.e.x.t
32+
*
33+
* @return Request
34+
* @throws \RuntimeException If no request is available
35+
*/
36+
public function getRequest(): Request
37+
{
38+
if ($this->request === null) {
39+
throw new \RuntimeException(
40+
'Request object not available. This exception was directly instantiated. ' .
41+
'Use a factory method that provides request context.'
42+
);
43+
}
44+
45+
return $this->request;
46+
}
2647

2748
/**
2849
* Creates a NetworkException from a PSR-18 network exception.
2950
*
3051
* @since n.e.x.t
3152
*
32-
* @param RequestInterface $request The request that failed.
53+
* @param RequestInterface $psrRequest The PSR-7 request that failed.
3354
* @param \Throwable $networkException The PSR-18 network exception.
3455
* @return self
3556
*/
36-
public static function fromPsr18NetworkException(RequestInterface $request, \Throwable $networkException): self
57+
public static function fromPsr18NetworkException(RequestInterface $psrRequest, \Throwable $networkException): self
3758
{
59+
$request = Request::fromPsrRequest($psrRequest);
3860
$message = sprintf(
3961
'Network error occurred while sending request to %s: %s',
40-
(string) $request->getUri(),
62+
$request->getUri(),
4163
$networkException->getMessage()
4264
);
4365

4466
$exception = new self($message, 0, $networkException);
4567
$exception->request = $request;
4668
return $exception;
4769
}
48-
49-
/**
50-
* Returns the request that failed.
51-
*
52-
* @since n.e.x.t
53-
*
54-
* @return RequestInterface
55-
* @throws \RuntimeException If no request is available (when directly instantiated)
56-
*/
57-
public function getRequest(): RequestInterface
58-
{
59-
if ($this->request === null) {
60-
throw new \RuntimeException(
61-
'Request object not available. This exception was directly instantiated. ' .
62-
'Use fromPsr18NetworkException() factory method for PSR-18 compliance.'
63-
);
64-
}
65-
66-
return $this->request;
67-
}
6870
}

src/Providers/Http/Exception/RequestException.php

Lines changed: 0 additions & 84 deletions
This file was deleted.

0 commit comments

Comments
 (0)