LogoSkills

openapi:mapper

Auto-generate OpenAPI Response to Domain Entity Mapper

ํ•ญ๋ชฉ๋‚ด์šฉ
Invoke/openapi:mapper
Aliases/mapper, /mapper:create
Categorygood-teacher-workflow
Complexitymoderate

/openapi:mapper โ€” API ์‘๋‹ต์„ ์šฐ๋ฆฌ ์•ฑ ๋ฐ์ดํ„ฐ๋กœ ๋ฐ”๊ฟ”์ฃผ๋Š” "๋ณ€ํ™˜๊ธฐ" ๋งŒ๋“ค๊ธฐ#

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

์„œ๋ฒ„(API)๊ฐ€ ๋ณด๋‚ด์ฃผ๋Š” ๋ฐ์ดํ„ฐ๋Š” ์šฐ๋ฆฌ ์•ฑ์ด ๋ฐ”๋กœ ์“ฐ๊ธฐ์—” ๋ชจ์–‘์ด ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ์ด ๋ช…๋ น์€ ๊ทธ ๋‘˜ ์‚ฌ์ด๋ฅผ ์ด์–ด์ฃผ๋Š” ๋ฒˆ์—ญ๊ธฐ(Mapper) ์ฝ”๋“œ๋ฅผ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค. ์™ธ๊ตญ์—์„œ ์˜จ ์„œ๋ฅ˜๋ฅผ ์šฐ๋ฆฌ ์–‘์‹์— ๋งž๊ฒŒ ์˜ฎ๊ฒจ ์ ์–ด์ฃผ๋Š” ๋ฒˆ์—ญ๊ฐ€๋ฅผ ๋‘๋Š” ๊ฒƒ๊ณผ ๊ฐ™์•„์š”.

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

  • ์ƒˆ API ํ™”๋ฉด(์—”๋“œํฌ์ธํŠธ)์„ ๋ถ™์ด๋ฉด์„œ, ์„œ๋ฒ„ ์‘๋‹ต์„ ์•ฑ ๋ฐ์ดํ„ฐ ํ˜•ํƒœ๋กœ ์˜ฎ๊ธฐ๋Š” ์ฝ”๋“œ๊ฐ€ ํ•„์š”ํ•  ๋•Œ
  • ๋ฐ์ดํ„ฐ ๊ณ„์ธต์„ ๋งŒ๋“œ๋Š” /feature:data ์ž‘์—…์„ ์‹œ์ž‘ํ•˜๊ธฐ ์ง์ „ ์ค€๋น„ ๋‹จ๊ณ„๋กœ ๋ฏธ๋ฆฌ ๋ณ€ํ™˜๊ธฐ๋ฅผ ๋งŒ๋“ค์–ด ๋‘˜ ๋•Œ

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

{feature_name}_mapper.dart ๋ผ๋Š” ๋ณ€ํ™˜๊ธฐ ํŒŒ์ผ ํ•˜๋‚˜๋ฅผ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค. ์œ„์น˜๋Š” ๋ณดํ†ต:

feature/{location}/{feature_name}/lib/src/data/mappers/{feature}_mapper.dart

์ด ํŒŒ์ผ์ด ์•Œ์•„์„œ ์ฒ˜๋ฆฌํ•ด ์ฃผ๋Š” ๊ฒƒ๋“ค:

  • ๋นˆ ๊ฐ’ ์•ˆ์ „ ์ฒ˜๋ฆฌ โ€” ์„œ๋ฒ„ ๊ฐ’์ด ๋น„์–ด ์žˆ์–ด๋„ ์•ฑ์ด ๋ฉˆ์ถ”์ง€ ์•Š๋„๋ก ๊ธฐ๋ณธ๊ฐ’์„ ์ฑ„์›Œ ์ค๋‹ˆ๋‹ค (์˜ˆ: ๋น„์–ด ์žˆ์œผ๋ฉด ๋นˆ ๋ฌธ์ž์—ด, ๋นˆ ๋ชฉ๋ก).
  • ๋ชฉ๋กยท์ค‘์ฒฉ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ โ€” ํ•™์ƒ ๋ชฉ๋ก, ์„ ์ƒ๋‹˜ ์ •๋ณด์ฒ˜๋Ÿผ ์•ˆ์— ๋˜ ๋“ค์–ด ์žˆ๋Š” ๋ฐ์ดํ„ฐ๊นŒ์ง€ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  • ์—๋Ÿฌ ์ •๋ฆฌ โ€” ์„œ๋ฒ„ ์˜ค๋ฅ˜(๊ถŒํ•œ ์—†์Œ, ๋ชป ์ฐพ์Œ, ๋„คํŠธ์›Œํฌ ๋ฌธ์ œ ๋“ฑ)๋ฅผ ์•ฑ์ด ์ดํ•ดํ•˜๋Š” ํ˜•ํƒœ๋กœ ๊น”๋”ํ•˜๊ฒŒ ๋ฐ”๊ฟ” ์ค๋‹ˆ๋‹ค.

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

# ๊ธฐ๋ณธํ˜•: ๊ธฐ๋Šฅ ์ด๋ฆ„, ์„œ๋ฒ„ ์‘๋‹ต ํƒ€์ž…, ์•ฑ ๋ฐ์ดํ„ฐ ํƒ€์ž… ์ˆœ์„œ๋กœ ์ ์Šต๋‹ˆ๋‹ค
/openapi:mapper {feature_name} {response_type} {entity_type}

# ์˜ˆ์‹œ 1 โ€” ํ•™๊ธ‰(classroom) ๋ณ€ํ™˜๊ธฐ ๋งŒ๋“ค๊ธฐ
/openapi:mapper classroom ClassResponse ClassroomClassInfo

# ์˜ˆ์‹œ 2 โ€” ํ•™์ƒ(student) ๋ณ€ํ™˜๊ธฐ ๋งŒ๋“ค๊ธฐ
/openapi:mapper student StudentResponse StudentInfo
  • feature_name: ๊ธฐ๋Šฅ ๋ชจ๋“ˆ ์ด๋ฆ„ (์˜ˆ: classroom, student)
  • response_type: ์„œ๋ฒ„๊ฐ€ ์ฃผ๋Š” ์‘๋‹ต ํƒ€์ž… ์ด๋ฆ„ (์˜ˆ: ClassResponse)
  • entity_type: ์•ฑ์—์„œ ์“ฐ๋Š” ๋ฐ์ดํ„ฐ ํƒ€์ž… ์ด๋ฆ„ (์˜ˆ: ClassroomClassInfo)

์„ธ ๊ฐ€์ง€ ๋ชจ๋‘ ๊ผญ ์ ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

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

  1. ์„œ๋ฒ„ ์‘๋‹ต ์‚ดํŽด๋ณด๊ธฐ โ€” ์„œ๋ฒ„๊ฐ€ ์–ด๋–ค ํ•ญ๋ชฉ๋“ค์„ ์–ด๋–ค ํ˜•ํƒœ๋กœ ์ฃผ๋Š”์ง€(๋นˆ ๊ฐ’์ด ์˜ฌ ์ˆ˜ ์žˆ๋Š”์ง€ ๋“ฑ) ๋จผ์ € ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  2. ์•ฑ ๋ฐ์ดํ„ฐ ์‚ดํŽด๋ณด๊ธฐ โ€” ์šฐ๋ฆฌ ์•ฑ์ด ๊ธฐ๋Œ€ํ•˜๋Š” ํ•ญ๋ชฉ๊ณผ ํ˜•ํƒœ, ๊ผญ ํ•„์š”ํ•œ ๊ฐ’์ด ๋ฌด์—‡์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  3. ๋ณ€ํ™˜๊ธฐ ์ฝ”๋“œ ๋งŒ๋“ค๊ธฐ โ€” ๋‘˜์„ ๋งž์ถฐ์„œ, ๋นˆ ๊ฐ’๊นŒ์ง€ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ณ€ํ™˜๊ธฐ ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  4. ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ์—ฐ๊ฒฐ โ€” ์ด๋ ‡๊ฒŒ ๋งŒ๋“  ๋ณ€ํ™˜๊ธฐ๋Š” ๊ณง๋ฐ”๋กœ /feature:data ์ž‘์—…์—์„œ ๊ทธ๋Œ€๋กœ ์“ฐ์ž…๋‹ˆ๋‹ค.

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

Triggers#

  • When a Mapper to convert OpenAPI Response to Domain Entity is needed
  • When mapping logic for a new API endpoint is needed
  • When creating a Mapper before /feature:data

Context Trigger Pattern#

/openapi:mapper {feature_name} {response_type} {entity_type}

Parameters#

ParameterRequiredDescriptionExample
feature_nameโœ…Feature module name (snake_case)classroom, student
response_typeโœ…OpenAPI Response type nameClassResponse, StudentResponse
entity_typeโœ…Domain Entity type nameClassroomClassInfo, StudentInfo

Behavioral Flow#

1. OpenAPI Response Analysis#

# Check Response type in OpenAPI package
find package/openapi -name  " *.dart "   | xargs grep -l  " {response_type} "

Analysis targets:

  • Response field list
  • Field types (nullable or not)
  • Nested object types

2. Domain Entity Analysis#

# Check Domain Entity
find feature -path  " */domain/entity/* "   -name  " *.dart "   | xargs grep -l  " {entity_type} "

Analysis targets:

  • Entity field list
  • Field types and defaults
  • Required/optional fields

3. Mapper Class Generation#

import 'package:core/core.dart';
import 'package:dependencies/dependencies.dart';
import 'package:openapi/api.dart';

/// Mapper that converts {Feature} API Response to Domain Entity
abstract final class {Feature}Mapper {
  /// Convert {Response}Response to {Entity}
  static {Entity} from{Response}Response({Response}Response response) {
    return {Entity}(
      // Required fields: null-safe conversion
      id: response.id?.toString() ?? '',
      name: response.name ?? '',

      // Date fields
      createdAt: response.createdAt ?? DateTime.now(),

      // Nested object fields
      teacher: response.teacher != null
          ? fromTeacherResponse(response.teacher!)
          : null,

      // List fields
      students: response.students
              ?.map(fromStudentResponse)
              .toList() ??
          [],

      // Enum fields
      status: _mapStatus(response.status),
    );
  }

  /// Convert {Response}Response list
  static List<{Entity}> from{Response}ResponseList(
    List<{Response}Response>? responses,
  ) {
    return responses?.map(from{Response}Response).toList() ?? [];
  }

  /// Convert nested object (TeacherResponse โ†’ TeacherInfo)
  static TeacherInfo fromTeacherResponse(TeacherResponse response) {
    return TeacherInfo(
      id: response.id?.toString() ?? '',
      name: response.name ?? '',
      // ...
    );
  }

  /// Enum conversion helper
  static {Entity}Status _mapStatus({Response}Status? status) {
    return switch (status) {
      {Response}Status.active => {Entity}Status.active,
      {Response}Status.inactive => {Entity}Status.inactive,
      _ => {Entity}Status.unknown,
    };
  }

  /// Convert API error to Failure
  static Failure mapException(Object error, StackTrace stackTrace) {
    Log.e('API Error: $error', stackTrace: stackTrace);
    if (error is DioException) {
      final statusCode = error.response?.statusCode;
      final message = error.message ?? 'Network error occurred';

      return switch (statusCode) {
        400 => ValidationFailure(message, error: error, stackTrace: stackTrace),
        401 => AuthFailure(message, error: error, stackTrace: stackTrace),
        403 => PermissionFailure(message, error: error, stackTrace: stackTrace),
        404 => NotFoundFailure(message, error: error, stackTrace: stackTrace),
        _ => NetworkFailure(message, error: error, stackTrace: stackTrace),
      };
    }
    return UnexpectedFailure(
      error.toString(),
      error: error is Exception ? error : null,
      stackTrace: stackTrace,
    );
  }
}

Output File#

feature/{location}/{feature_name}/lib/src/data/mappers/{feature}_mapper.dart

Mapping Rules#

Conversion Patterns by Field Type#

Response TypeEntity TypeConversion Pattern
int?Stringresponse.id?.toString() ?? ''
String?Stringresponse.name ?? ''
DateTime?DateTimeresponse.createdAt ?? DateTime.now()
List<T>?List<T>responses?.map(fromT).toList() ?? []
Nested?Entity?response.nested != null ? fromNested(response.nested!) : null
Enum?Enum_mapEnum(response.status)

Null Safety Principles#

  1. Required fields: Provide default values

    id: response.id?.toString() ?? '',
    name: response.name ?? '',
    
  2. Optional fields: Keep nullable

    description: response.description,
    avatarUrl: response.avatarUrl,
    
  3. List fields: Default to empty list

    items: response.items?.map(fromItem).toList() ?? [],
    
  4. Date fields: Use current time or nullable

    createdAt: response.createdAt ?? DateTime.now(),
    updatedAt: response.updatedAt,  // When nullable allowed
    

Examples#

Classroom Mapper Generation#

/openapi:mapper classroom ClassResponse ClassroomClassInfo

Generated result:

abstract final class ClassroomMapper {
  static ClassroomClassInfo fromClassResponse(ClassResponse response) {
    return ClassroomClassInfo(
      classId: response.classId?.toString() ?? '',
      name: response.name ?? '',
      description: response.description ?? '',
      teacherId: response.teacherId?.toString() ?? '',
      joinCode: response.joinCode ?? '',
      memberCount: response.memberCount ?? 0,
      createdAt: response.createdAt ?? DateTime.now(),
    );
  }

  static Failure mapException(Object error, StackTrace stackTrace) { ... }
}

Student Mapper Generation#

/openapi:mapper student StudentResponse StudentInfo

Integration with /feature:data#

/openapi:mapper is used as a preprocessing step for /feature:data:

1. /openapi:mapper classroom ClassResponse ClassroomClassInfo
   โ†’ data/mappers/classroom_mapper.dart generated

2. /feature:data classroom ClassroomClassInfo
   โ†’ Repository, Mixin use ClassroomMapper

Checklist#

  • OpenAPI Response type analysis
  • Domain Entity type analysis
  • Field mapping table creation
  • Mapper class generation (abstract final class)
  • from{Response}Response() method implementation
  • from{Response}ResponseList() method implementation
  • Nested object conversion methods implementation
  • Enum conversion helper implementation
  • mapException() method implementation
  • Null-safe field mapping verification
  • Add export to data.dart

Reference Documents#

  • .claude/references/patterns/repository-patterns.md - Repository patterns
  • feature/application/classroom/lib/src/data/mappers/classroom_mapper.dart - Reference implementation