# Error Handling System

Standardized error handling for all ERP-Suite backends (NestJS and Express).

## Overview

This module provides:
- **Base error classes** with consistent structure
- **HTTP-specific errors** for common status codes
- **NestJS exception filter** for automatic error handling
- **Express middleware** for error handling
- **Request tracking** with request IDs
- **Structured logging** with severity levels

## Installation

The error handling module is part of `@erp-suite/core`:

```typescript
import {
  // Base types
  BaseError,
  ErrorResponse,

  // HTTP errors
  BadRequestError,
  UnauthorizedError,
  ForbiddenError,
  NotFoundError,
  ConflictError,
  ValidationError,
  InternalServerError,

  // NestJS
  GlobalExceptionFilter,

  // Express
  createErrorMiddleware,
  errorMiddleware,
  notFoundMiddleware,
} from '@erp-suite/core';
```

## Quick Start

### NestJS Integration

```typescript
// main.ts
import { NestFactory } from '@nestjs/core';
import { GlobalExceptionFilter } from '@erp-suite/core';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Register global exception filter
  app.useGlobalFilters(new GlobalExceptionFilter());

  await app.listen(3000);
}
```

### Express Integration

```typescript
// index.ts
import express from 'express';
import { createErrorMiddleware, notFoundMiddleware } from '@erp-suite/core';

const app = express();

// Your routes here
app.use('/api', routes);

// 404 handler (before error middleware)
app.use(notFoundMiddleware);

// Error handler (must be last)
app.use(createErrorMiddleware());

app.listen(3000);
```

## Error Classes

### HTTP Errors

All HTTP error classes extend `BaseError` and include:
- `statusCode`: HTTP status code
- `error`: Error type string
- `message`: Human-readable message
- `details`: Optional additional context

#### Available Error Classes

| Class | Status Code | Usage |
|-------|-------------|-------|
| `BadRequestError` | 400 | Invalid request parameters |
| `UnauthorizedError` | 401 | Missing or invalid authentication |
| `ForbiddenError` | 403 | Authenticated but insufficient permissions |
| `NotFoundError` | 404 | Resource doesn't exist |
| `ConflictError` | 409 | Resource conflict (e.g., duplicate) |
| `ValidationError` | 422 | Validation failed |
| `InternalServerError` | 500 | Unexpected server error |

#### Usage Examples

```typescript
// Not Found
throw new NotFoundError('User not found', { userId: '123' });

// Validation
throw new ValidationError('Invalid input', {
  errors: [
    { field: 'email', message: 'Invalid format' },
    { field: 'age', message: 'Must be 18+' }
  ]
});

// Unauthorized
throw new UnauthorizedError('Invalid token');

// Conflict
throw new ConflictError('Email already exists', { email: 'user@example.com' });
```

### Custom Domain Errors

Create custom errors for your domain:

```typescript
import { BaseError } from '@erp-suite/core';

export class InsufficientBalanceError extends BaseError {
  readonly statusCode = 400;
  readonly error = 'Insufficient Balance';

  constructor(required: number, available: number) {
    super('Insufficient balance for this operation', {
      required,
      available,
      deficit: required - available,
    });
  }
}

// Usage
throw new InsufficientBalanceError(100, 50);
```

## Error Response Format

All errors are converted to this standardized format:

```typescript
interface ErrorResponse {
  statusCode: number;      // HTTP status code
  error: string;           // Error type
  message: string;         // Human-readable message
  details?: object;        // Optional additional context
  timestamp: string;       // ISO 8601 timestamp
  path?: string;          // Request path
  requestId?: string;     // Request tracking ID
}
```

### Example Response

```json
{
  "statusCode": 404,
  "error": "Not Found",
  "message": "User not found",
  "details": {
    "userId": "999"
  },
  "timestamp": "2025-12-12T10:30:00.000Z",
  "path": "/api/users/999",
  "requestId": "550e8400-e29b-41d4-a716-446655440000"
}
```

## NestJS Details

### Global Filter Registration

**Option 1: In main.ts** (Recommended for simple cases)
```typescript
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new GlobalExceptionFilter());
```

**Option 2: As Provider** (Recommended for DI support)
```typescript
import { APP_FILTER } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: GlobalExceptionFilter,
    },
  ],
})
export class AppModule {}
```

### Using in Controllers/Services

```typescript
@Injectable()
export class UsersService {
  async findById(id: string): Promise<User> {
    const user = await this.repository.findOne({ where: { id } });

    if (!user) {
      throw new NotFoundError('User not found', { userId: id });
    }

    return user;
  }
}

@Controller('users')
export class UsersController {
  @Get(':id')
  async getUser(@Param('id') id: string): Promise<User> {
    // Errors are automatically caught and formatted
    return this.usersService.findById(id);
  }
}
```

### Request ID Tracking

```typescript
import { Injectable, NestMiddleware } from '@nestjs/common';
import { randomUUID } from 'crypto';

@Injectable()
export class RequestIdMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const requestId = req.headers['x-request-id'] || randomUUID();
    req.headers['x-request-id'] = requestId;
    res.setHeader('X-Request-ID', requestId);
    next();
  }
}
```

## Express Details

### Middleware Setup

```typescript
import express from 'express';
import {
  createErrorMiddleware,
  notFoundMiddleware,
  ErrorLogger,
} from '@erp-suite/core';

const app = express();

// Body parsing
app.use(express.json());

// Your routes
app.use('/api/users', usersRouter);

// 404 handler (optional but recommended)
app.use(notFoundMiddleware);

// Error middleware (MUST be last)
app.use(createErrorMiddleware({
  logger: customLogger,
  includeStackTrace: process.env.NODE_ENV !== 'production',
}));
```

### Configuration Options

```typescript
interface ErrorMiddlewareOptions {
  logger?: ErrorLogger;           // Custom logger
  includeStackTrace?: boolean;    // Include stack traces (dev only)
  transformer?: (error, response) => response;  // Custom transformer
}
```

### Custom Logger

```typescript
import { ErrorLogger } from '@erp-suite/core';

class CustomLogger implements ErrorLogger {
  error(message: string, ...meta: any[]): void {
    winston.error(message, ...meta);
  }

  warn(message: string, ...meta: any[]): void {
    winston.warn(message, ...meta);
  }

  log(message: string, ...meta: any[]): void {
    winston.info(message, ...meta);
  }
}

app.use(createErrorMiddleware({
  logger: new CustomLogger(),
}));
```

### Async Route Handlers

Use an async handler wrapper to automatically catch errors:

```typescript
function asyncHandler(fn) {
  return (req, res, next) => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
}

app.get('/users/:id', asyncHandler(async (req, res) => {
  const user = await findUserById(req.params.id);
  res.json(user);
}));
```

### Using in Routes

```typescript
const router = express.Router();

router.get('/:id', async (req, res, next) => {
  try {
    const user = await usersService.findById(req.params.id);
    res.json(user);
  } catch (error) {
    next(error);  // Pass to error middleware
  }
});
```

## Logging

### Log Levels

Errors are automatically logged with appropriate severity:

- **ERROR** (500+): Server errors, unexpected errors
- **WARN** (400-499): Client errors, validation failures
- **INFO** (<400): Informational messages

### Log Format

```typescript
// Error log
[500] Internal Server Error: Database connection failed
{
  "path": "/api/users",
  "requestId": "req-123",
  "details": { ... },
  "stack": "Error: ...\n    at ..."
}

// Warning log
[404] Not Found: User not found
{
  "path": "/api/users/999",
  "requestId": "req-124",
  "details": { "userId": "999" }
}
```

## Best Practices

### 1. Use Specific Error Classes

```typescript
// Good
throw new NotFoundError('User not found', { userId });

// Avoid
throw new Error('Not found');
```

### 2. Include Contextual Details

```typescript
// Good - includes helpful context
throw new ValidationError('Validation failed', {
  errors: [
    { field: 'email', message: 'Invalid format' },
    { field: 'password', message: 'Too short' }
  ]
});

// Less helpful
throw new ValidationError('Invalid input');
```

### 3. Throw Early, Handle Centrally

```typescript
// Service layer - throw errors
async findById(id: string): Promise<User> {
  const user = await this.repository.findOne(id);
  if (!user) {
    throw new NotFoundError('User not found', { userId: id });
  }
  return user;
}

// Controller/Route - let filter/middleware handle
@Get(':id')
async getUser(@Param('id') id: string) {
  return this.service.findById(id);  // Don't try/catch here
}
```

### 4. Don't Expose Internal Details in Production

```typescript
// Good
throw new InternalServerError('Database operation failed');

// Avoid in production
throw new InternalServerError('Connection to PostgreSQL at 10.0.0.5:5432 failed');
```

### 5. Use Request IDs for Tracking

Always include request ID middleware to enable request tracing across logs.

## Migration Guide

### From Manual Error Handling (NestJS)

**Before:**
```typescript
@Get(':id')
async getUser(@Param('id') id: string) {
  try {
    const user = await this.service.findById(id);
    return user;
  } catch (error) {
    throw new HttpException('User not found', HttpStatus.NOT_FOUND);
  }
}
```

**After:**
```typescript
@Get(':id')
async getUser(@Param('id') id: string) {
  return this.service.findById(id);  // Service throws NotFoundError
}

// In service
async findById(id: string) {
  const user = await this.repository.findOne(id);
  if (!user) {
    throw new NotFoundError('User not found', { userId: id });
  }
  return user;
}
```

### From Manual Error Handling (Express)

**Before:**
```typescript
app.get('/users/:id', async (req, res) => {
  try {
    const user = await findUser(req.params.id);
    res.json(user);
  } catch (error) {
    res.status(500).json({ error: 'Internal error' });
  }
});
```

**After:**
```typescript
app.get('/users/:id', asyncHandler(async (req, res) => {
  const user = await findUser(req.params.id);  // Throws NotFoundError
  res.json(user);
}));

// Error middleware handles it automatically
```

## Testing

### Testing Error Responses (NestJS)

```typescript
it('should return 404 when user not found', async () => {
  const response = await request(app.getHttpServer())
    .get('/users/999')
    .expect(404);

  expect(response.body).toMatchObject({
    statusCode: 404,
    error: 'Not Found',
    message: 'User not found',
    details: { userId: '999' },
  });

  expect(response.body.timestamp).toBeDefined();
  expect(response.body.path).toBe('/users/999');
});
```

### Testing Error Responses (Express)

```typescript
it('should return 404 when user not found', async () => {
  const response = await request(app)
    .get('/api/users/999')
    .expect(404);

  expect(response.body).toMatchObject({
    statusCode: 404,
    error: 'Not Found',
    message: 'User not found',
  });
});
```

### Testing Custom Errors

```typescript
describe('InsufficientBalanceError', () => {
  it('should create error with correct details', () => {
    const error = new InsufficientBalanceError(100, 50);

    expect(error.statusCode).toBe(400);
    expect(error.message).toBe('Insufficient balance for this operation');
    expect(error.details).toEqual({
      required: 100,
      available: 50,
      deficit: 50,
    });
  });
});
```

## Examples

See detailed integration examples:
- **NestJS**: `nestjs-integration.example.ts`
- **Express**: `express-integration.example.ts`

## Files

```
errors/
├── base-error.ts                    # Base error class and types
├── http-errors.ts                   # HTTP-specific error classes
├── error-filter.ts                  # NestJS exception filter
├── error-middleware.ts              # Express error middleware
├── index.ts                         # Module exports
├── README.md                        # This file
├── nestjs-integration.example.ts    # NestJS examples
└── express-integration.example.ts   # Express examples
```

## Support

For questions or issues, contact the ERP-Suite development team.
