Skip to content

Exception Handling

lpachecob edited this page Mar 24, 2026 · 1 revision

⚠️ Exception Handling & API Responses

BOUNDLY v0.9.0 includes standardized exception handling and API response formats.


📍 Location

Infrastructure\FrameworkCore\Exceptions\
Infrastructure\FrameworkCore\Enums\ErrorCode.php
Infrastructure\FrameworkCore\Traits\ApiResponse.php

🎯 Response Format

BOUNDLY uses a consistent JSON response format:

Success Response

{
  "success": true,
  "data": {
    "id": 1,
    "name": "John Doe",
    "email": "john@example.com"
  },
  "meta": {
    "timestamp": "2026-03-24T12:00:00Z"
  }
}

Error Response

{
  "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"
  }
}

🚨 Error Codes

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

💥 Exception Classes

ApiException

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]
);

Specialized Exceptions

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');

🎨 Using the ApiResponse Trait

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();
    }
}

Response Methods

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

🔧 Custom Exception Handler

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);
    }
}

📝 HTTP Status Codes Reference

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

🧪 Testing Error Responses

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',
            ],
        ]);
}

📋 Best Practices

  1. Always return JSON for API routes - BOUNDLY handles this automatically
  2. Use specific exceptions - Don't use generic Exception for API errors
  3. Include error codes - Always provide the code field for client handling
  4. Provide details - Include actionable information in details
  5. Don't expose internals - In production, hide stack traces and internal paths

Next Step: Roadmap 🚀

Clone this wiki locally