/cc-dev:openapi:mapper — API 응답을 우리 앱 데이터로 바꿔주는 "변환기" 만들기#
| 항목 | 내용 |
|---|---|
| 실행 명령 | /cc-dev:openapi:mapper |
| 분류 | 가이드 |
| 난이도 | ●●○ 보통 |
한마디로#
서버(API)가 보내주는 데이터는 우리 앱이 바로 쓰기엔 모양이 다릅니다. 이 명령은 그 둘 사이를 이어주는 번역기(Mapper) 코드를 자동으로 만들어 줍니다. 외국에서 온 서류를 우리 양식에 맞게 옮겨 적어주는 번역가를 두는 것과 같아요.
누가·언제 쓰나요#
- 새 API 화면(엔드포인트)을 붙이면서, 서버 응답을 앱 데이터 형태로 옮기는 코드가 필요할 때
-
데이터 계층을 만드는
/cc-flutter:feature:data작업을 시작하기 직전 준비 단계로 미리 변환기를 만들어 둘 때
무엇을 해주나요#
{feature_name}_mapper.dart 라는 변환기 파일 하나를 만들어 줍니다. 위치는 보통:
feature/{location}/{feature_name}/lib/src/data/mappers/{feature}_mapper.dart
이 파일이 알아서 처리해 주는 것들:
- 빈 값 안전 처리 — 서버 값이 비어 있어도 앱이 멈추지 않도록 기본값을 채워 줍니다 (예: 비어 있으면 빈 문자열, 빈 목록).
- 목록·중첩 데이터 변환 — 학생 목록, 선생님 정보처럼 안에 또 들어 있는 데이터까지 변환합니다.
- 에러 정리 — 서버 오류(권한 없음, 못 찾음, 네트워크 문제 등)를 앱이 이해하는 형태로 깔끔하게 바꿔 줍니다.
어떻게 쓰나요#
# 기본형: 기능 이름, 서버 응답 타입, 앱 데이터 타입 순서로 적습니다
/cc-dev:openapi:mapper {feature_name} {response_type} {entity_type}
# 예시 1 — 학급(classroom) 변환기 만들기
/cc-dev:openapi:mapper classroom ClassResponse ClassroomClassInfo
# 예시 2 — 학생(student) 변환기 만들기
/cc-dev:openapi:mapper student StudentResponse StudentInfo
feature_name: 기능 모듈 이름 (예:classroom,student)response_type: 서버가 주는 응답 타입 이름 (예:ClassResponse)entity_type: 앱에서 쓰는 데이터 타입 이름 (예:ClassroomClassInfo)
세 가지 모두 꼭 적어야 합니다.
안에서 무슨 일이 벌어지나요#
- 서버 응답 살펴보기 — 서버가 어떤 항목들을 어떤 형태로 주는지(빈 값이 올 수 있는지 등) 먼저 확인합니다.
- 앱 데이터 살펴보기 — 우리 앱이 기대하는 항목과 형태, 꼭 필요한 값이 무엇인지 확인합니다.
- 변환기 코드 만들기 — 둘을 맞춰서, 빈 값까지 안전하게 처리하는 변환기 코드를 생성합니다.
- 다음 단계로 연결 — 이렇게 만든 변환기는 곧바로
/cc-flutter: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
/cc-flutter:feature:data
Context Trigger Pattern#
/cc-dev:openapi:mapper {feature_name} {response_type} {entity_type}Parameters#
| Parameter | Required | Description | Example |
|---|---|---|---|
feature_name | ✅ | Feature module name (snake_case) | classroom, student |
response_type | ✅ | OpenAPI Response type name | ClassResponse, StudentResponse |
entity_type | ✅ | Domain Entity type name | ClassroomClassInfo, 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.dartMapping Rules#
Conversion Patterns by Field Type#
| Response Type | Entity Type | Conversion Pattern |
|---|---|---|
int? | String | response.id?.toString() ?? '' |
String? | String | response.name ?? '' |
DateTime? | DateTime | response.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#
Required fields: Provide default values
id: response.id?.toString() ?? '', name: response.name ?? '',Optional fields: Keep nullable
description: response.description, avatarUrl: response.avatarUrl,List fields: Default to empty list
items: response.items?.map(fromItem).toList() ?? [],Date fields: Use current time or nullable
createdAt: response.createdAt ?? DateTime.now(), updatedAt: response.updatedAt, // When nullable allowed
Examples#
Classroom Mapper Generation#
/cc-dev:openapi:mapper classroom ClassResponse ClassroomClassInfoGenerated 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#
/cc-dev:openapi:mapper student StudentResponse StudentInfoIntegration with /cc-flutter:feature:data#
/cc-dev:openapi:mapper is used as a preprocessing step for /cc-flutter:feature:data:
1. /cc-dev:openapi:mapper classroom ClassResponse ClassroomClassInfo
→ data/mappers/classroom_mapper.dart generated
2. /cc-flutter:feature:data classroom ClassroomClassInfo
→ Repository, Mixin use ClassroomMapperChecklist#
- OpenAPI Response type analysis
- Domain Entity type analysis
- Field mapping table creation
- Mapper class generation (
abstract final class) from{Response}Response()method implementationfrom{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 patternsfeature/application/classroom/lib/src/data/mappers/classroom_mapper.dart- Reference implementation