| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| Invoke | /openapi:mapper |
| Aliases | /mapper, /mapper:create |
| Category | good-teacher-workflow |
| Complexity | moderate |
/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)
์ธ ๊ฐ์ง ๋ชจ๋ ๊ผญ ์ ์ด์ผ ํฉ๋๋ค.
์์์ ๋ฌด์จ ์ผ์ด ๋ฒ์ด์ง๋์#
- ์๋ฒ ์๋ต ์ดํด๋ณด๊ธฐ โ ์๋ฒ๊ฐ ์ด๋ค ํญ๋ชฉ๋ค์ ์ด๋ค ํํ๋ก ์ฃผ๋์ง(๋น ๊ฐ์ด ์ฌ ์ ์๋์ง ๋ฑ) ๋จผ์ ํ์ธํฉ๋๋ค.
- ์ฑ ๋ฐ์ดํฐ ์ดํด๋ณด๊ธฐ โ ์ฐ๋ฆฌ ์ฑ์ด ๊ธฐ๋ํ๋ ํญ๋ชฉ๊ณผ ํํ, ๊ผญ ํ์ํ ๊ฐ์ด ๋ฌด์์ธ์ง ํ์ธํฉ๋๋ค.
- ๋ณํ๊ธฐ ์ฝ๋ ๋ง๋ค๊ธฐ โ ๋์ ๋ง์ถฐ์, ๋น ๊ฐ๊น์ง ์์ ํ๊ฒ ์ฒ๋ฆฌํ๋ ๋ณํ๊ธฐ ์ฝ๋๋ฅผ ์์ฑํฉ๋๋ค.
- ๋ค์ ๋จ๊ณ๋ก ์ฐ๊ฒฐ โ ์ด๋ ๊ฒ ๋ง๋ ๋ณํ๊ธฐ๋ ๊ณง๋ฐ๋ก
/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#
| 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#
/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#
/openapi:mapper student StudentResponse StudentInfoIntegration 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 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