-
Notifications
You must be signed in to change notification settings - Fork 0
Exception Handling
lpachecob edited this page Mar 24, 2026
·
1 revision
BOUNDLY v0.9.0 includes standardized exception handling and API response formats.
Infrastructure\FrameworkCore\Exceptions\
Infrastructure\FrameworkCore\Enums\ErrorCode.php
Infrastructure\FrameworkCore\Traits\ApiResponse.phpBOUNDLY uses a consistent JSON response format:
{
"success": true,
"data": {
"id": 1,
"name": "John Doe",
"email": "john@example.com"
},
"meta": {
"timestamp": "2026-03-24T12:00:00Z"
}
}{
"success": false,
"error": {
"code": "ERR_VALIDATION_FAILED",
"message": "The given data was invalid.",
"details": {
"email": ["The email field is required."]
}
},
"meta": {
"timestamp": "2026-03-24T12:00:00Z",
"request_id": "req_abc123"
}
}BOUNDLY provides standardized error codes:
| Code | HTTP Status | Description |
|---|---|---|
ERR_RESOURCE_NOT_FOUND |
404 | Requested resource doesn't exist |
ERR_RESOURCE_NOT_DEFINED |
404 | Entity not registered in BOUNDLY |
ERR_VALIDATION_FAILED |
422 | Request validation failed |
ERR_UNAUTHORIZED |
401 | Authentication required |
ERR_FORBIDDEN |
403 | Insufficient permissions |
ERR_RATE_LIMITED |
429 | Rate limit exceeded |
ERR_SERVER_ERROR |
500 | Internal server error |
ERR_METHOD_NOT_ALLOWED |
405 | HTTP method not supported |
ERR_INVALID_CONTENT_TYPE |
415 | Unsupported content type |
ERR_REQUEST_TOO_LARGE |
415 | Request payload too large |
ERR_BRUTE_FORCE_BLOCKED |
429 | Account temporarily blocked |
ERR_API_KEY_INVALID |
401 | Invalid API key |
ERR_API_KEY_MISSING |
401 | API key not provided |
ERR_API_KEY_INSUFFICIENT_SCOPES |
403 | API key lacks permissions |
ERR_SUSPICIOUS_INPUT |
400 | Malicious input detected |
ERR_CORS_VIOLATION |
403 | CORS policy violation |
ERR_IP_RESTRICTED |
403 | IP address blocked |
ERR_SIGNATURE_INVALID |
401 | Request signature invalid |
ERR_TIER_LIMIT_EXCEEDED |
429 | Tier rate limit exceeded |
ERR_SERVICE_UNAVAILABLE |
503 | Service temporarily unavailable |
Base exception for all API errors:
use Infrastructure\FrameworkCore\Exceptions\ApiException;
use Infrastructure\FrameworkCore\Enums\ErrorCode;
throw new ApiException(
message: 'Custom error message',
errorCode: ErrorCode::RESOURCE_NOT_FOUND,
httpStatus: 404,
details: ['resource_id' => 123]
);use Infrastructure\FrameworkCore\Exceptions\NotFoundException;
use Infrastructure\FrameworkCore\Exceptions\ValidationException;
use Infrastructure\FrameworkCore\Exceptions\UnauthorizedException;
use Infrastructure\FrameworkCore\Exceptions\ForbiddenException;
// 404 Not Found
throw new NotFoundException('User not found');
// 422 Validation Error
throw new ValidationException([
'email' => ['The email field is required.'],
'name' => ['The name must be at least 3 characters.'],
]);
// 401 Unauthorized
throw new UnauthorizedException('Authentication required');
// 403 Forbidden
throw new ForbiddenException('You do not have permission to access this resource');The ApiResponse trait provides fluent response methods:
use Infrastructure\FrameworkCore\Traits\ApiResponse;
use Infrastructure\FrameworkCore\Enums\ErrorCode;
class UserController extends Controller
{
use ApiResponse;
public function index()
{
$users = User::all();
return $this->success($users);
}
public function store(Request $request)
{
$user = User::create($request->validated());
return $this->created($user);
}
public function show($id)
{
$user = User::find($id);
if (!$user) {
return $this->notFound('User not found', ErrorCode::RESOURCE_NOT_FOUND);
}
return $this->success($user);
}
public function update(Request $request, $id)
{
$user = User::find($id);
if (!$user) {
return $this->notFound('User not found');
}
$user->update($request->validated());
return $this->success($user);
}
public function destroy($id)
{
$user = User::find($id);
if (!$user) {
return $this->notFound('User not found');
}
$user->delete();
return $this->deleted();
}
}| Method | HTTP Status | Use Case |
|---|---|---|
success($data) |
200 | Successful GET, PUT, PATCH |
created($data) |
201 | Successful POST |
deleted() |
204 | Successful DELETE |
notFound($message, $code) |
404 | Resource not found |
error($message, $code, $status, $details) |
varies | Generic error |
validationError($errors) |
422 | Validation failed |
unauthorized($message) |
401 | Authentication required |
forbidden($message) |
403 | Permission denied |
Register custom exception handling in app/Exceptions/Handler.php:
use Infrastructure\FrameworkCore\Exceptions\ApiException;
use Infrastructure\FrameworkCore\Traits\ApiResponse;
class Handler extends ExceptionHandler
{
use ApiResponse;
public function render($request, Throwable $e)
{
if ($request->expectsJson() || $request->is('api/*')) {
if ($e instanceof ApiException) {
return $this->error(
$e->getMessage(),
$e->getErrorCode(),
$e->getStatusCode(),
$e->getDetails()
);
}
if ($e instanceof ModelNotFoundException) {
return $this->notFound('Resource not found');
}
if ($e instanceof ValidationException) {
return $this->validationError($e->errors());
}
// Generic server error
return $this->error(
config('app.debug') ? $e->getMessage() : 'Internal server error',
ErrorCode::SERVER_ERROR,
500
);
}
return parent::render($request, $e);
}
}| Status | Name | When to Use |
|---|---|---|
| 200 | OK | Successful GET, PUT, PATCH |
| 201 | Created | Successful POST |
| 204 | No Content | Successful DELETE |
| 400 | Bad Request | Malformed request |
| 401 | Unauthorized | Authentication required |
| 403 | Forbidden | Permission denied |
| 404 | Not Found | Resource doesn't exist |
| 422 | Unprocessable Entity | Validation failed |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Unexpected error |
| 503 | Service Unavailable | Temporary outage |
public function test_validation_error_returns_correct_format()
{
$response = $this->postJson('/api/users', []);
$response->assertStatus(422)
->assertJson([
'success' => false,
'error' => [
'code' => 'ERR_VALIDATION_FAILED',
],
]);
}
public function test_not_found_returns_correct_format()
{
$response = $this->getJson('/api/users/99999');
$response->assertStatus(404)
->assertJson([
'success' => false,
'error' => [
'code' => 'ERR_RESOURCE_NOT_FOUND',
],
]);
}- Always return JSON for API routes - BOUNDLY handles this automatically
-
Use specific exceptions - Don't use generic
Exceptionfor API errors -
Include error codes - Always provide the
codefield for client handling -
Provide details - Include actionable information in
details - Don't expose internals - In production, hide stack traces and internal paths
Next Step: Roadmap 🚀