LogoSkills

serverpod-exception-agent

Serverpod exception, validation, and constants class generation expert. Use for error handling pattern implementation

ํ•ญ๋ชฉ๋‚ด์šฉ
Invoke/serverpod:exception
Aliases/backend:exception, /api:error
ToolsRead, Edit, Write, Glob, Grep
Modelsonnet
Skillsserverpod

/serverpod:exception โ€” ๋ฐฑ์—”๋“œ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ์ž๋™ ์„ค๊ณ„ ๋„์šฐ๋ฏธ#

ํ•œ๋งˆ๋””๋กœ#

์„œ๋ฒ„์—์„œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ์„ ๋•Œ "๋ฌด์—‡์ด ์™œ ์ž˜๋ชป๋๋Š”์ง€" ๊น”๋”ํ•˜๊ฒŒ ์•Œ๋ ค์ฃผ๋Š” ์•ˆ๋‚ด ์ฒด๊ณ„๋ฅผ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค. ๊ฑด๋ฌผ๋งˆ๋‹ค ๋น„์ƒ๊ตฌ ํ‘œ์ง€ํŒยท์†Œํ™”๊ธฐยท์•ˆ๋‚ด ๋ฐฉ์†ก์„ ๊ทœ๊ฒฉ๋Œ€๋กœ ์„ค์น˜ํ•ด ๋‘๋Š” ๊ฒƒ๊ณผ ๊ฐ™์•„์š”. ์‚ฌ๊ณ ๊ฐ€ ๋‚˜๋„ ๋‹นํ™ฉํ•˜์ง€ ์•Š๊ณ  ์ •ํ•ด์ง„ ๋ฐฉ์‹๋Œ€๋กœ ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฏธ๋ฆฌ ๊ฐ–์ถฐ ๋‘๋Š” ์ผ์ž…๋‹ˆ๋‹ค.

๋ˆ„๊ฐ€ยท์–ธ์ œ ์“ฐ๋‚˜์š”#

  • ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ƒˆ ๊ธฐ๋Šฅ(Feature)์— ๋“ค์–ด๊ฐˆ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌยท์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆยท์ƒ์ˆ˜ ์ •์˜๋ฅผ ํ•œ ๋ฒˆ์— ๋งŒ๋“ค๊ณ  ์‹ถ์„ ๋•Œ
  • /serverpod:exception ๋ช…๋ น์„ ์ง์ ‘ ์‹คํ–‰ํ•  ๋•Œ
  • ์ „์ฒด ๊ธฐ๋Šฅ์„ ํ•œ๊บผ๋ฒˆ์— ๋งŒ๋“œ๋Š” /feature:create ๊ณผ์ • ์ค‘ ์ž๋™์œผ๋กœ ํ˜ธ์ถœ๋  ๋•Œ (1.5๋‹จ๊ณ„)

๋ฌด์—‡์„ ํ•ด์ฃผ๋‚˜์š”#

ํŠน์ • ๊ธฐ๋Šฅ ํด๋” ์•ˆ์— ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ์— ํ•„์š”ํ•œ ํŒŒ์ผ ๋ฌถ์Œ์„ ๊ทœ๊ฒฉ๋Œ€๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

  • exception ํด๋” โ€” ์˜ค๋ฅ˜ ์ข…๋ฅ˜๋ณ„ ํด๋ž˜์Šค์™€ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ๋ชจ์Œ (์˜ˆ: ๋ฐ์ดํ„ฐ๋ฅผ ๋ชป ์ฐพ์Œ, ์ž…๋ ฅ๊ฐ’์ด ์ž˜๋ชป๋จ, DB ์ž‘์—… ์‹คํŒจ)
  • validation ํด๋” โ€” ์ž…๋ ฅ๊ฐ’์ด ์˜ฌ๋ฐ”๋ฅธ์ง€ ๊ฒ€์‚ฌํ•˜๋Š” Validator
  • constant ํด๋” โ€” HTTP ์ƒํƒœ ์ฝ”๋“œยทํŽ˜์ด์ง€ ํฌ๊ธฐ ๊ฐ™์€ ๊ณ ์ •๊ฐ’ ๋ชจ์Œ
  • helper ํด๋” โ€” ์˜ค๋ฅ˜๋ฅผ ์ผ๊ด€๋˜๊ฒŒ ๊ฐ์‹ธ ์ฃผ๋Š” exception_handler.dart

์ฆ‰, "์–ด๋–ค ์ƒํ™ฉ์—์„œ ์–ด๋–ค ์˜ค๋ฅ˜๋ฅผ ์–ด๋–ป๊ฒŒ ์•Œ๋ฆด์ง€"๋ฅผ ๊ธฐ๋Šฅ๋งˆ๋‹ค ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ํ‘œ์ค€ํ™”ํ•ด ์ค๋‹ˆ๋‹ค.

์–ด๋–ป๊ฒŒ ์“ฐ๋‚˜์š”#

์ด ๋ช…๋ น์€ ๋‘ ๊ฐ€์ง€ ์ •๋ณด(๊ธฐ๋Šฅ ์ด๋ฆ„, ์—”ํ‹ฐํ‹ฐ ์ด๋ฆ„)๋ฅผ ๋ฐ›์•„ ๋™์ž‘ํ•˜๋ฉฐ, ์ผ๋ถ€๋Š” ๋„๊ณ  ์ผค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

# Validator์™€ Constants๋ฅผ ํ•จ๊ป˜ ์ƒ์„ฑ (๊ธฐ๋ณธ ๋™์ž‘)
feature_name = book        (๊ธฐ๋Šฅ ์ด๋ฆ„, ์†Œ๋ฌธ์ž_์–ธ๋”์Šค์ฝ”์–ด)
entity_name  = Book        (์—”ํ‹ฐํ‹ฐ ์ด๋ฆ„, ์ฒซ ๊ธ€์ž ๋Œ€๋ฌธ์ž)

# ๊ฒ€์ฆ ํด๋ž˜์Šค ์ƒ์„ฑ์„ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
include_validator = false

# ์ƒ์ˆ˜ ํด๋ž˜์Šค ์ƒ์„ฑ์„ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
include_constants = false
  • feature_name(ํ•„์ˆ˜): ๊ธฐ๋Šฅ ๋ชจ๋“ˆ ์ด๋ฆ„, ์†Œ๋ฌธ์ž+์–ธ๋”์Šค์ฝ”์–ด ํ˜•์‹
  • entity_name(ํ•„์ˆ˜): ์—”ํ‹ฐํ‹ฐ ์ด๋ฆ„, ์ฒซ ๊ธ€์ž ๋Œ€๋ฌธ์ž ํ˜•์‹
  • include_validator(์„ ํƒ): Validator ์ƒ์„ฑ ์—ฌ๋ถ€, ๊ธฐ๋ณธ๊ฐ’ true
  • include_constants(์„ ํƒ): Constants ์ƒ์„ฑ ์—ฌ๋ถ€, ๊ธฐ๋ณธ๊ฐ’ true

์•ˆ์—์„œ ๋ฌด์Šจ ์ผ์ด ๋ฒŒ์–ด์ง€๋‚˜์š”#

์ •ํ•ด์ง„ ๊ทœ๊ฒฉ(ํŒจํ„ด)์— ๋งž์ถฐ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ํŒŒ์ผ๋“ค์„ ์ฐจ๋ก€๋กœ ๋งŒ๋“ค์–ด ๋‘ก๋‹ˆ๋‹ค.

  1. ์˜ค๋ฅ˜ ์ข…๋ฅ˜ ์ •์˜ โ€” "๋ชป ์ฐพ์Œ / ์ž…๋ ฅ๊ฐ’ ์˜ค๋ฅ˜ / ํŒŒ๋ผ๋ฏธํ„ฐ ์˜ค๋ฅ˜ / DB ์‹คํŒจ"์ฒ˜๋Ÿผ ์ƒํ™ฉ๋ณ„ ์˜ค๋ฅ˜๋ฅผ ๋ถ„๋ฅ˜ํ•˜๊ณ , ๊ฐ๊ฐ์— HTTP ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋ถ™์ž…๋‹ˆ๋‹ค.
  2. ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ์ •๋ฆฌ โ€” ์‚ฌ๋žŒ์ด ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ๋ฉ”์‹œ์ง€ ๋ฌธ๊ตฌ๋ฅผ ํ•œ๊ณณ์— ๋ชจ์•„ ๋‘์–ด, ์–ด๋””์„œ๋“  ๊ฐ™์€ ๋ฌธ๊ตฌ๋ฅผ ์“ฐ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
  3. ๊ณ ์ •๊ฐ’ ์ •๋ฆฌ โ€” HTTP ์ฝ”๋“œ, ํŽ˜์ด์ง€ ํฌ๊ธฐ ํ•œ๊ณ„, ํ•„๋“œ ์ด๋ฆ„ ๋“ฑ ์ž์ฃผ ์“ฐ๋Š” ๊ฐ’์„ ์ƒ์ˆ˜๋กœ ๋ชจ์๋‹ˆ๋‹ค.
  4. ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ โ€” ID๋‚˜ ํŽ˜์ด์ง• ๊ฐ’์ด ๊ทœ์น™์— ๋งž๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ์–ด๊ธ‹๋‚˜๋ฉด ๋ช…ํ™•ํ•œ ์˜ค๋ฅ˜๋ฅผ ๋ƒ…๋‹ˆ๋‹ค.
  5. ์•ˆ์ „ ์‹คํ–‰ ๊ฐ์‹ธ๊ธฐ โ€” ์ž‘์—…์„ safeExecute()๋กœ ๊ฐ์‹ธ, ์˜ˆ์ƒ ๋ชป ํ•œ ์˜ค๋ฅ˜๋„ ์ •ํ•ด์ง„ ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ํ•ด ์ผ๊ด€๋˜๊ฒŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  6. ์ ๊ฒ€ โ€” sealed class ์ ์šฉ, ๋ชจ๋“  ์˜ค๋ฅ˜์— ์ƒํƒœ ์ฝ”๋“œ ํฌํ•จ, ๋ฌธ์„œ ์ฃผ์„ ์ž‘์„ฑ ๋“ฑ ์ฒดํฌ๋ฆฌ์ŠคํŠธ๋กœ ๋งˆ๋ฌด๋ฆฌ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

โš™๏ธ ์ƒ์„ธ ์˜ต์…˜ยท์‹คํ–‰ ๋ช…์„ธ (๊ฐœ๋ฐœ์ž / AI ์—์ด์ „ํŠธ์šฉ)

Role#

Implements the exception handling system for the Serverpod backend.

  • sealed class-based exception definitions
  • Validator class patterns
  • Constants and ErrorMessages separation
  • ExceptionHandler.safeExecute() wrapping

Activation Conditions#

  • Activated on /serverpod:exception command invocation
  • Called in Step 1.5 of /feature:create orchestration

Parameters#

ParameterRequiredDescription
feature_nameโœ…Feature module name (snake_case)
entity_nameโœ…Entity name (PascalCase)
include_validatorโŒGenerate Validator (default: true)
include_constantsโŒGenerate Constants (default: true)

Generated Files#

backend/kobic_server/lib/src/feature/{feature_name}/
โ”œโ”€โ”€ exception/
โ”‚   โ”œโ”€โ”€ exception.dart                    # Export file
โ”‚   โ”œโ”€โ”€ {feature_name}_exception.dart     # Exception classes
โ”‚   โ””โ”€โ”€ {feature_name}_error_messages.dart # Error messages
โ”œโ”€โ”€ validation/
โ”‚   โ”œโ”€โ”€ validation.dart                   # Export file
โ”‚   โ””โ”€โ”€ {feature_name}_validator.dart     # Validation classes
โ”œโ”€โ”€ constant/
โ”‚   โ”œโ”€โ”€ constant.dart                     # Export file
โ”‚   โ””โ”€โ”€ {feature_name}_constants.dart     # Constants classes
โ””โ”€โ”€ helper/
    โ”œโ”€โ”€ helper.dart                       # Export file
    โ””โ”€โ”€ exception_handler.dart            # Exception handler

Import Order (Required)#

// 1. Dart standard libraries
import 'dart:core';

// 2. Common constants
import 'package:kobic_server/src/common/constants/http_status_constants.dart';

// 3. Feature internal constants/messages
import '../constant/constant.dart';

Core Patterns#

1. sealed class Exception Definition#

/// Base class for {Feature}-related exceptions
sealed class {Feature}Exceptions implements Exception {
  const {Feature}Exceptions(
    this.message,
    this.statusCode, [
    this.stackTrace,
  ]);

  final String message;
  final int statusCode;
  final StackTrace? stackTrace;
}

/// {Feature} resource not found
final class {Feature}NotFoundException extends {Feature}Exceptions {
  {Feature}NotFoundException(int resourceId)
      : super(
          {Feature}ErrorMessages.notFound('{feature}', resourceId),
          {Feature}Constants.httpNotFound,
        );
}

/// {Feature} validation failure
final class {Feature}ValidationException extends {Feature}Exceptions {
  {Feature}ValidationException(String reason)
      : super(
          reason,
          {Feature}Constants.httpBadRequest,
        );
}

/// {Feature} parameter validation failure
final class Invalid{Feature}ParameterException extends {Feature}Exceptions {
  Invalid{Feature}ParameterException(String parameter, String reason)
      : super(
          {Feature}ErrorMessages.invalidParameter(parameter, reason),
          {Feature}Constants.httpBadRequest,
        );
}

/// {Feature} database operation failure
final class {Feature}DatabaseOperationException extends {Feature}Exceptions {
  {Feature}DatabaseOperationException(
    String operation,
    String errorMessage, [
    StackTrace? stackTrace,
  ]) : super(
          {Feature}ErrorMessages.databaseOperationFailed(operation, errorMessage),
          {Feature}Constants.httpInternalServerError,
          stackTrace,
        );
}

2. ErrorMessages Class#

/// {Feature} error message definitions
class {Feature}ErrorMessages {
  const {Feature}ErrorMessages._();

  /// Resource not found
  static String notFound(String resource, int id) =>
      '$resource(ID: $id) not found.';

  /// Parameter validation failure
  static String invalidParameter(String param, String reason) =>
      '$param: $reason';

  /// Database operation failure
  static String databaseOperationFailed(String operation, String error) =>
      'Database operation failed ($operation): $error';

  /// Required parameter missing
  static const String idRequired = 'ID is required.';
  static const String idTooSmall = 'ID must be 1 or greater.';

  /// Paging related
  static const String limitTooSmall = 'limit must be at least 1.';
  static const String limitTooLarge = 'limit must be at most 100.';
  static const String offsetNegative = 'offset must be 0 or greater.';
}

3. Constants Class#

/// {Feature} related constants
class {Feature}Constants {
  const {Feature}Constants._();

  // ============ HTTP Status Codes ============
  static const int httpOk = HttpStatusConstants.ok;                        // 200
  static const int httpCreated = HttpStatusConstants.created;              // 201
  static const int httpBadRequest = HttpStatusConstants.badRequest;        // 400
  static const int httpUnauthorized = HttpStatusConstants.unauthorized;    // 401
  static const int httpForbidden = HttpStatusConstants.forbidden;          // 403
  static const int httpNotFound = HttpStatusConstants.notFound;            // 404
  static const int httpInternalServerError = HttpStatusConstants.internalServerError; // 500

  // ============ Paging Constants ============
  static const int defaultPageLimit = 20;
  static const int maxPageLimit = 100;
  static const int minPageLimit = 1;
  static const int defaultOffset = 0;

  // ============ ID Related ============
  static const int minValidId = 1;

  // ============ Field Names (for validation) ============
  static const String fieldId = 'id';
  static const String fieldLimit = 'limit';
  static const String fieldOffset = 'offset';

  // ============ DB Operation Names ============
  static const String dbOperationInsert = '{feature} insert';
  static const String dbOperationUpdate = '{feature} update';
  static const String dbOperationDelete = '{feature} delete';
  static const String dbOperationSelect = '{feature} select';
}

4. Validator Class#

/// {Feature} input validation
class {Feature}Validator {
  const {Feature}Validator._();

  /// Validate ID (optional)
  static void validateId(int? id) {
    if (id != null && id < {Feature}Constants.minValidId) {
      throw Invalid{Feature}ParameterException(
        {Feature}Constants.fieldId,
        {Feature}ErrorMessages.idTooSmall,
      );
    }
  }

  /// Validate ID (required)
  static void validateRequiredId(int? id) {
    if (id == null) {
      throw {Feature}ValidationException({Feature}ErrorMessages.idRequired);
    }
    validateId(id);
  }

  /// Validate paging parameters
  static void validatePagingParams(int limit, int offset) {
    if (limit < {Feature}Constants.minPageLimit) {
      throw Invalid{Feature}ParameterException(
        {Feature}Constants.fieldLimit,
        {Feature}ErrorMessages.limitTooSmall,
      );
    }
    if (limit > {Feature}Constants.maxPageLimit) {
      throw Invalid{Feature}ParameterException(
        {Feature}Constants.fieldLimit,
        {Feature}ErrorMessages.limitTooLarge,
      );
    }
    if (offset < 0) {
      throw Invalid{Feature}ParameterException(
        {Feature}Constants.fieldOffset,
        {Feature}ErrorMessages.offsetNegative,
      );
    }
  }

  /// Null check for object
  static void validateNotNull<T>(T? value, String fieldName) {
    if (value == null) {
      throw Invalid{Feature}ParameterException(
        fieldName,
        '$fieldName is required.',
      );
    }
  }
}

5. ExceptionHandler#

/// Exception handling wrapper
class ExceptionHandler {
  const ExceptionHandler._();

  /// Safe execution wrapper
  static Future<T> safeExecute<T>(
    Future<T> Function() operation, {
    required String operationName,
  }) async {
    try {
      return await operation();
    } on {Feature}Exceptions {
      rethrow;
    } on Exception catch (error, stackTrace) {
      throw {Feature}DatabaseOperationException(
        operationName,
        error.toString(),
        stackTrace,
      );
    }
  }
}

Reference Files#

backend/kobic_server/lib/src/feature/banner/exception/banner_exception.dart
backend/kobic_server/lib/src/feature/banner/constant/banner_constants.dart
backend/kobic_server/lib/src/feature/banner/validation/banner_validator.dart
backend/kobic_server/lib/src/common/constants/http_status_constants.dart

Checklist#

  • Apply sealed class pattern
  • Include statusCode in all exceptions
  • Separate ErrorMessages into methods/constants
  • Reference HttpStatusConstants from Constants
  • Clear messages on Validator failure
  • Apply ExceptionHandler.safeExecute() pattern
  • Write KDoc comments