> ## Documentation Index
> Fetch the complete documentation index at: https://sonamu.cartanova.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# SoException

> Base class for Sonamu exception handling system

`SoException` is the abstract base class that all Sonamu exception classes inherit from. It can include HTTP status codes, messages, and additional payloads.

## Type Definition

```typescript theme={null}
abstract class SoException extends Error {
  constructor(
    public readonly statusCode: number,
    public message: LocalizedString,
    public payload?: unknown,
  );
}
```

## Properties

### statusCode

```typescript theme={null}
readonly statusCode: number
```

HTTP response status code. When an exception is thrown, Fastify responds with this code.

### message

```typescript theme={null}
message: LocalizedString;
```

Exception message. Uses the `LocalizedString` type, delivering localized strings through Sonamu's i18n system.

### payload

```typescript theme={null}
payload?: unknown
```

Optional additional data. Used to convey detailed information related to the exception.

**Usage Example:**

```typescript theme={null}
throw new BadRequestException("Invalid email format", {
  field: "email",
  providedValue: "invalid-email",
  expectedFormat: "user@example.com",
});
```

## Utility Functions

### isSoException()

Checks whether a given value is an instance of `SoException`.

```typescript theme={null}
function isSoException(err: unknown): err is SoException;
```

**Usage Example:**

```typescript theme={null}
try {
  // Some operation
} catch (err) {
  if (isSoException(err)) {
    console.log(`Status: ${err.statusCode}`);
    console.log(`Message: ${err.message}`);
    console.log(`Payload:`, err.payload);
  } else {
    // Handle other error types
  }
}
```

## Built-in Exception Classes

Sonamu provides several built-in exception classes for common HTTP error scenarios.

### BadRequestException (400)

Used when there are issues with request parameters or other request details.

```typescript theme={null}
class BadRequestException extends SoException
```

**Usage Example:**

```typescript theme={null}
@api()
async createUser(email: string, password: string) {
  if (!email.includes("@")) {
    throw new BadRequestException("Invalid email format");
  }

  if (password.length < 8) {
    throw new BadRequestException("Password must be at least 8 characters", {
      minLength: 8,
      providedLength: password.length
    });
  }

  // User creation logic
}
```

### UnauthorizedException (401)

Used when login is required but the user is logged out, or when access permission is lacking.

```typescript theme={null}
class UnauthorizedException extends SoException
```

**Usage Example:**

```typescript theme={null}
@api()
async getMyProfile(ctx: Context) {
  if (!ctx.user) {
    throw new UnauthorizedException("Login required");
  }

  return this.findById(ctx.user.id);
}
```

### NotFoundException (404)

Used when accessing a non-existent record.

```typescript theme={null}
class NotFoundException extends SoException
```

**Usage Example:**

```typescript theme={null}
@api()
async getUserById(id: number) {
  const user = await this.findById(id);

  if (!user) {
    throw new NotFoundException(`User not found (ID: ${id})`);
  }

  return user;
}
```

### InternalServerErrorException (500)

Used when internal processing logic errors occur (including external API calls).

```typescript theme={null}
class InternalServerErrorException extends SoException
```

**Usage Example:**

```typescript theme={null}
@api()
async processPayment(orderId: number) {
  try {
    const result = await externalPaymentAPI.charge(orderId);
    return result;
  } catch (err) {
    throw new InternalServerErrorException(
      "Error occurred during payment processing",
      { orderId, originalError: err }
    );
  }
}
```

### ServiceUnavailableException (503)

Used when processing is impossible in the current state.

```typescript theme={null}
class ServiceUnavailableException extends SoException
```

**Usage Example:**

```typescript theme={null}
@api()
async getRecommendations() {
  if (!this.mlServiceAvailable) {
    throw new ServiceUnavailableException(
      "Recommendation service is temporarily unavailable"
    );
  }

  return this.fetchRecommendations();
}
```

### TargetNotFoundException (520)

Used when there is no target for the operation.

```typescript theme={null}
class TargetNotFoundException extends SoException
```

**Usage Example:**

```typescript theme={null}
@api()
async deleteUserPosts(userId: number) {
  const posts = await this.findByUserId(userId);

  if (posts.length === 0) {
    throw new TargetNotFoundException(
      `No posts to delete (User ID: ${userId})`
    );
  }

  await this.deleteMany(posts.map(p => p.id));
}
```

### AlreadyProcessedException (541)

Used when attempting duplicate processing of an already processed request.

```typescript theme={null}
class AlreadyProcessedException extends SoException
```

**Usage Example:**

```typescript theme={null}
@api()
async processOrder(orderId: number) {
  const order = await this.findById(orderId);

  if (order.status === "processed") {
    throw new AlreadyProcessedException(
      `Order has already been processed (ID: ${orderId})`,
      { processedAt: order.processedAt }
    );
  }

  // Order processing logic
}
```

### DuplicateRowException (542)

Used when duplicate requests are made where duplicates are not allowed.

```typescript theme={null}
class DuplicateRowException extends SoException
```

**Usage Example:**

```typescript theme={null}
@api()
async createUser(email: string, username: string) {
  const existingUser = await this.findByEmail(email);

  if (existingUser) {
    throw new DuplicateRowException(
      "Email already registered",
      { field: "email", value: email }
    );
  }

  // User creation logic
}
```

## Exception Handling Flow

Sonamu automatically processes thrown exceptions and converts them into appropriate HTTP responses:

```typescript theme={null}
// Exception thrown in API method
@api()
async createPost(title: string) {
  if (!title) {
    throw new BadRequestException("Title is required");
  }
  // ...
}

// Client receives:
// HTTP 400 Bad Request
// {
//   "statusCode": 400,
//   "message": "Title is required"
// }
```

When payload is present:

```typescript theme={null}
throw new BadRequestException("Invalid data", {
  errors: [
    { field: "email", message: "Email format is invalid" },
    { field: "age", message: "Age must be greater than 0" },
  ],
});

// Client response:
// HTTP 400 Bad Request
// {
//   "statusCode": 400,
//   "message": "Invalid data",
//   "payload": {
//     "errors": [
//       { "field": "email", "message": "Email format is invalid" },
//       { "field": "age", "message": "Age must be greater than 0" }
//     ]
//   }
// }
```

## Exceptions vs Guards

For simple authentication checks, it's better to use Guards:

```typescript theme={null}
// Using Guards (recommended)
@api({ guards: ["user"] })
async getMyData(ctx: Context) {
  // ctx.user is guaranteed
  return this.findById(ctx.user!.id);
}

// Manual check (for more complex logic)
@api()
async updateProfile(ctx: Context, data: ProfileData) {
  if (!ctx.user) {
    throw new UnauthorizedException("Login required");
  }

  if (ctx.user.id !== data.userId) {
    throw new UnauthorizedException("You can only modify your own profile");
  }

  // Profile update logic
}
```

## Related Documentation

* [Custom Exceptions](/en/api-reference/exceptions/custom-errors)
* [Using Guards](/en/api-development/guards)
* [Error Handling](/en/api-development/error-handling)
