Skip to content

Commit 944a6bc

Browse files
committed
Propagate API Gateway errors with custom exception
Introduces ApiGatewayException to propagate API Gateway error responses and status codes to the client. Updates ApiGatewayClient to throw this exception on failed responses, registers a renderable handler in the service provider, and adds a test to verify error propagation.
1 parent 3b55e9a commit 944a6bc

File tree

5 files changed

+100
-10
lines changed

5 files changed

+100
-10
lines changed

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ This package provides a robust foundation for building Laravel-based microservic
2424
Easily enable, disable, or rename middleware via configuration, and use convenient groups like `microservice.auth` group for full authentication and authorization in one step.
2525

2626
- **HTTP Client Macros:**
27-
Pre-configured HTTP clients for communicating with your API Gateway or other services. When a request is available, these macros automatically forward the current correlation ID header.
27+
Pre-configured HTTP clients for communicating with your API Gateway or other services. They automatically forward the current correlation ID header and propagate gateway errors to the caller.
2828

2929
- **Ready-to-publish Configuration:**
3030
All settings are customizable via a single config file, making it easy to adapt the package to your environment.
@@ -112,8 +112,6 @@ Route::middleware(['jwt.auth'])->group(function () {
112112
}
113113
```
114114

115-
---
116-
117115
### LoadAccess
118116

119117
**Alias**: the string you assign to `load_access` in `middleware_aliases`, e.g. `load.access`.
@@ -298,6 +296,12 @@ When enabled (default), visiting `/api/health` returns:
298296
}
299297
```
300298

299+
### HTTP Client Macros
300+
301+
Use `Http::apiGateway()` and its related macros for service-to-service calls. Each macro forwards the current correlation ID header when available.
302+
303+
If the gateway responds with an error (for example, a `503`), an `ApiGatewayException` is thrown and automatically returned to the client with the same status code.
304+
301305
---
302306

303307
## Public Release and Roadmap
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Kroderdev\LaravelMicroserviceCore\Exceptions;
4+
5+
use Symfony\Component\HttpKernel\Exception\HttpException;
6+
7+
class ApiGatewayException extends HttpException
8+
{
9+
protected array $data;
10+
11+
public function __construct(int $statusCode, array $data = [], string $message = '', \Throwable $previous = null, array $headers = [], int $code = 0)
12+
{
13+
parent::__construct($statusCode, $message ?: 'API Gateway Error', $previous, $headers, $code);
14+
$this->data = $data;
15+
}
16+
17+
public function getData(): array
18+
{
19+
return $this->data;
20+
}
21+
}

src/Providers/MicroserviceServiceProvider.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22

33
namespace Kroderdev\LaravelMicroserviceCore\Providers;
44

5-
use Illuminate\Foundation\Http\Kernel;
5+
use Illuminate\Contracts\Debug\ExceptionHandler;
66
use Illuminate\Routing\Router;
77
use Illuminate\Support\Facades\Gate;
88
use Illuminate\Support\Facades\Http;
99
use Illuminate\Support\ServiceProvider;
1010
use Kroderdev\LaravelMicroserviceCore\Contracts\AccessUserInterface;
1111
use Kroderdev\LaravelMicroserviceCore\Contracts\ApiGatewayClientInterface;
12+
use Kroderdev\LaravelMicroserviceCore\Exceptions\ApiGatewayException;
1213
use Kroderdev\LaravelMicroserviceCore\Http\HealthCheckController;
1314
use Kroderdev\LaravelMicroserviceCore\Http\Middleware\LoadAccess;
1415
use Kroderdev\LaravelMicroserviceCore\Http\Middleware\PermissionMiddleware;
@@ -115,7 +116,7 @@ public function boot(Router $router): void
115116
->withHeaders($correlation ? [$header => $correlation] : [])
116117
->baseUrl(config('microservice.api_gateway.url'))
117118
->timeout(5)
118-
->retry(2, 100);
119+
->retry(2, 100, throw: false);
119120
});
120121

121122
Http::macro('apiGatewayDirect', function () {
@@ -139,7 +140,7 @@ public function boot(Router $router): void
139140
->withHeaders($correlation ? [$header => $correlation] : [])
140141
->baseUrl(config('microservice.api_gateway.url'))
141142
->timeout(5)
142-
->retry(2, 100);
143+
->retry(2, 100, throw: false);
143144
});
144145

145146
Http::macro('apiGatewayDirectWithToken', function (string $token) {
@@ -153,5 +154,10 @@ public function boot(Router $router): void
153154
->baseUrl(config('microservice.api_gateway.url'))
154155
->timeout(5);
155156
});
157+
158+
// Exceptions
159+
$this->app->make(ExceptionHandler::class)->renderable(function (ApiGatewayException $e, $request) {
160+
return response()->json($e->getData(), $e->getStatusCode());
161+
});
156162
}
157163
}

src/Services/ApiGatewayClient.php

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Illuminate\Http\Client\PendingRequest;
66
use Illuminate\Support\Facades\Http;
77
use Kroderdev\LaravelMicroserviceCore\Contracts\ApiGatewayClientInterface;
8+
use Kroderdev\LaravelMicroserviceCore\Exceptions\ApiGatewayException;
89

910
class ApiGatewayClient implements ApiGatewayClientInterface
1011
{
@@ -37,21 +38,43 @@ public static function directWithToken(string $token): static
3738

3839
public function get(string $uri, array $query = [])
3940
{
40-
return $this->http->get($uri, $query);
41+
return $this->handleResponse(
42+
$this->http->get($uri, $query)
43+
);
4144
}
4245

4346
public function post(string $uri, array $data = [])
4447
{
45-
return $this->http->post($uri, $data);
48+
return $this->handleResponse(
49+
$this->http->delete($uri)
50+
);
4651
}
4752

4853
public function put(string $uri, array $data = [])
4954
{
50-
return $this->http->put($uri, $data);
55+
return $this->handleResponse(
56+
$this->http->delete($uri)
57+
);
5158
}
5259

5360
public function delete(string $uri)
5461
{
55-
return $this->http->delete($uri);
62+
return $this->handleResponse(
63+
$this->http->delete($uri)
64+
);
65+
}
66+
67+
protected function handleResponse($response)
68+
{
69+
if (is_object($response) && method_exists($response, 'failed') && $response->failed()) {
70+
$data = method_exists($response, 'json') ? $response->json() : [];
71+
72+
throw new ApiGatewayException(
73+
method_exists($response, 'status') ? $response->status() : 500,
74+
is_array($data) ? $data : []
75+
);
76+
}
77+
78+
return $response;
5679
}
5780
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace Tests\Services;
4+
5+
use Illuminate\Support\Facades\Http;
6+
use Illuminate\Support\Facades\Route;
7+
use Orchestra\Testbench\TestCase;
8+
use Kroderdev\LaravelMicroserviceCore\Contracts\ApiGatewayClientInterface;
9+
use Kroderdev\LaravelMicroserviceCore\Providers\MicroserviceServiceProvider;
10+
use Kroderdev\LaravelMicroserviceCore\Services\ApiGatewayClient;
11+
12+
class ApiGatewayErrorTest extends TestCase
13+
{
14+
protected function getPackageProviders($app)
15+
{
16+
return [MicroserviceServiceProvider::class];
17+
}
18+
19+
protected function setUp(): void
20+
{
21+
parent::setUp();
22+
Http::fake(['*' => Http::response(['error' => 'unavailable'], 503)]);
23+
24+
Route::get('/gateway-error', function () {
25+
return app(ApiGatewayClient::class)->get('/fail');
26+
});
27+
}
28+
29+
/** @test */
30+
public function propagates_gateway_status_code()
31+
{
32+
$this->get('/gateway-error')
33+
->assertStatus(503)
34+
->assertJson(['error' => 'unavailable']);
35+
}
36+
}

0 commit comments

Comments
 (0)