Skip to content

Commit 90e2baf

Browse files
dapolachclaude
andcommitted
refactor(openapi): stop redirecting inline enum schemas onto domain enums
Now that model.mustache renders a real enumOuterClass for a top-level enum schema (previous commit), the generator can synthesize its own DTO-side enum per module instead of being pointed at the hand-written domain enum via schemaMappings. Removed the enum-only mapping entries for Gender, DeactivationReason, DrivingLicenseGroup, TrainerLevel, RefereeLevel (members) and EventStatus (events), and added explicit DTO<->domain conversions at the REST boundary (MemberMapper, UpdateMemberRequestMapper, MemberController, EventController) so the domain enums stay untouched and fully decoupled from the wire representation. Authority stays mapped: it's used pervasively outside the DTO layer (@HasAuthority annotations, security interceptors, JWT claims, OAuth2 scopes), so redirecting it would force Authority/DTO conversions into core security code for no benefit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent bc687f5 commit 90e2baf

9 files changed

Lines changed: 84 additions & 29 deletions

File tree

backend/build.gradle.kts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -430,17 +430,24 @@ openApiModule(
430430
"RegisterMemberRequest",
431431
"AddressRequest",
432432
"SuspendMembershipRequest",
433-
"UpdateMemberRequest"
433+
"UpdateMemberRequest",
434+
// Enum schemas — must be listed explicitly so the generator emits them; without a
435+
// schemaMapping redirecting them onto the domain enum, they need `models` to know to
436+
// generate the type at all (see the mapping comment below for why they're no longer mapped).
437+
"Gender",
438+
"UpdateMemberRequest_gender",
439+
"DeactivationReason",
440+
"DrivingLicenseGroup",
441+
"TrainerLicenseDto_level",
442+
"RefereeLicenseDto_level"
434443
),
435444
mappings = mapOf(
436-
"Gender" to "com.klabis.members.domain.Gender",
437-
// UpdateMemberRequest.gender inlines the enum rather than $ref-ing Gender, so that it keeps
438-
// both its JsonNullable wrapper and x-klabis-authority. See the comment in members.yaml.
439-
"UpdateMemberRequest_gender" to "com.klabis.members.domain.Gender",
440-
"DeactivationReason" to "com.klabis.members.domain.DeactivationReason",
441-
"DrivingLicenseGroup" to "com.klabis.members.domain.DrivingLicenseGroup",
442-
"TrainerLicenseDto_level" to "com.klabis.members.domain.TrainerLevel",
443-
"RefereeLicenseDto_level" to "com.klabis.members.domain.RefereeLevel",
445+
// Gender/DeactivationReason/DrivingLicenseGroup/TrainerLicenseDto_level/RefereeLicenseDto_level
446+
// used to be redirected here onto the hand-written domain enums (Gender, DeactivationReason,
447+
// DrivingLicenseGroup, TrainerLevel, RefereeLevel). Now that model.mustache renders a real
448+
// enumOuterClass for a promoted/$ref'd top-level enum, the generator synthesizes its own DTO
449+
// enum for each of these instead — see MemberMapper/UpdateMemberRequestMapper for the explicit
450+
// conversion between the generated DTO enum and the domain enum at the REST boundary.
444451
// getMember's application/json response references MemberDetailsResponse directly (see
445452
// members.yaml) — no envelope redirection needed since the schema name already matches the
446453
// Java type. listMembers still needs one: its application/json response is a bare array
@@ -536,10 +543,16 @@ openApiModule(
536543
"UpdateEventRankingRequest",
537544
"EntryFeeRequest",
538545
"CreateEventRequest",
539-
"CreateEventCategoryRequest"
546+
"CreateEventCategoryRequest",
547+
// Enum schema — must be listed explicitly so the generator emits it (see the mapping comment
548+
// below for why it's no longer redirected onto the domain enum).
549+
"EventStatus"
540550
),
541551
mappings = mapOf(
542-
"EventStatus" to "com.klabis.events.domain.EventStatus",
552+
// EventStatus used to be redirected here onto the hand-written domain enum
553+
// com.klabis.events.domain.EventStatus. Now that model.mustache renders a real enumOuterClass
554+
// for a top-level enum schema, the generator synthesizes its own DTO enum instead — see
555+
// EventDto/EventSummaryDto/EventController for the explicit conversion to/from the domain enum.
543556
// listEvents is genuinely paginated (x-spring-paginated: true) — no array shape carries
544557
// pagination metadata, so both the envelope and the named array sibling stay mapped onto
545558
// Page<T> (finance module precedent: removing either one degrades the generated return type

backend/src/main/java/com/klabis/events/infrastructure/restapi/EventController.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import com.klabis.events.domain.Event;
2020
import com.klabis.events.domain.EventFilter;
2121
import com.klabis.events.domain.EventRegistration;
22-
import com.klabis.events.domain.EventStatus;
2322
import com.klabis.members.*;
2423
import com.klabis.members.infrastructure.restapi.MembersApi;
2524
import jakarta.annotation.Nullable;
@@ -189,7 +188,7 @@ private EventFilter buildFilter(
189188
List<UUID> eventTypeId,
190189
CurrentUserData currentUser) {
191190

192-
EventFilter filter = status != null ? EventFilter.byStatus(status) : EventFilter.none();
191+
EventFilter filter = status != null ? EventFilter.byStatus(toDomainStatus(status)) : EventFilter.none();
193192

194193
if (q != null) {
195194
filter = filter.withFulltext(q);
@@ -243,6 +242,10 @@ private EventFilter buildFilter(
243242
return filter;
244243
}
245244

245+
private static com.klabis.events.domain.EventStatus toDomainStatus(EventStatus status) {
246+
return status == null ? null : com.klabis.events.domain.EventStatus.valueOf(status.name());
247+
}
248+
246249
private void validateSortFields(Sort sort) {
247250
final var allowedSortFields = Set.of(
248251
"id",
@@ -418,7 +421,7 @@ static MemberId resolveMemberId(Authentication auth) {
418421
}
419422

420423
static boolean shouldOfferRegistration(Event event) {
421-
return event.getStatus() == EventStatus.ACTIVE && event.areRegistrationsOpen();
424+
return event.getStatus() == com.klabis.events.domain.EventStatus.ACTIVE && event.areRegistrationsOpen();
422425
}
423426

424427
static boolean isCoordinatorOrHasManageAuthority(Authentication auth, Event event) {
@@ -492,7 +495,7 @@ public void process(EntityModel<EventDto> dtoModel, Event event) {
492495
klabisLinkTo(methodOn(EventsApi.class).listEvents(null, null, null, null, null, null, null, null, null, null, null, null))
493496
.ifPresent(link -> dtoModel.add(link.withRel("collection")));
494497

495-
if (event.getStatus() != EventStatus.DRAFT) {
498+
if (event.getStatus() != com.klabis.events.domain.EventStatus.DRAFT) {
496499
klabisLinkTo(methodOn(EventRegistrationsApi.class).listRegistrations(eventId, null))
497500
.ifPresent(link -> dtoModel.add(link.withRel("registrations").expand()));
498501
}

backend/src/main/java/com/klabis/members/infrastructure/restapi/MemberController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public ResponseEntity<Void> suspendMember(
9797

9898
var command = new Member.SuspendMembership(
9999
currentUserId,
100-
request.reason(),
100+
memberMapper.deactivationReasonToDomain(request.reason()),
101101
request.note()
102102
);
103103

backend/src/main/java/com/klabis/members/infrastructure/restapi/MemberMapper.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,23 @@ interface MemberMapper {
3030
@Mapping(target = "birthNumber", expression = "java(member.getBirthNumber() != null ? member.getBirthNumber().value() : null)")
3131
@Mapping(target = "bankAccountNumber", expression = "java(member.getBankAccountNumber() != null ? member.getBankAccountNumber().value() : null)")
3232
@Mapping(target = "suspendedBy", expression = "java(member.getSuspendedBy() != null ? member.getSuspendedBy().uuid().toString() : null)")
33+
@Mapping(target = "gender", source = "gender")
34+
@Mapping(target = "drivingLicenseGroup", source = "drivingLicenseGroup")
35+
@Mapping(target = "suspensionReason", source = "suspensionReason")
3336
MemberDetailsResponse toDetailsResponse(Member member);
3437

3538
AddressResponse addressToResponse(Address address);
3639

40+
Gender genderToDto(com.klabis.members.domain.Gender gender);
41+
42+
com.klabis.members.domain.Gender genderToDomain(Gender gender);
43+
44+
DrivingLicenseGroup drivingLicenseGroupToDto(com.klabis.members.domain.DrivingLicenseGroup drivingLicenseGroup);
45+
46+
DeactivationReason deactivationReasonToDto(com.klabis.members.domain.DeactivationReason reason);
47+
48+
com.klabis.members.domain.DeactivationReason deactivationReasonToDomain(DeactivationReason reason);
49+
3750
default GuardianDTO guardianToResponse(GuardianInformation guardian) {
3851
if (guardian == null) {
3952
return null;
@@ -56,11 +69,15 @@ default GuardianDTO guardianToResponse(GuardianInformation guardian) {
5669

5770
RefereeLicenseDto refereeLicenseToDto(RefereeLicense refereeLicense);
5871

72+
TrainerLicenseDtoLevel trainerLevelToDto(com.klabis.members.domain.TrainerLevel level);
73+
74+
RefereeLicenseDtoLevel refereeLevelToDto(com.klabis.members.domain.RefereeLevel level);
75+
5976
default RegistrationPort.RegisterNewMember toRegisterNewMemberCommand(
6077
RegisterMemberRequest request, UserId registeredBy) {
6178
return new RegistrationPort.RegisterNewMember(
6279
createPersonalInformation(request.firstName(), request.lastName(),
63-
request.dateOfBirth(), request.gender(), request.nationality()),
80+
request.dateOfBirth(), genderToDomain(request.gender()), request.nationality()),
6481
request.address() != null ? new Address(request.address().street(), request.address().city(),
6582
request.address().postalCode(), request.address().country()) : null,
6683
EmailAddress.of(request.email()),
@@ -78,7 +95,7 @@ default PersonalInformation createPersonalInformation(
7895
String firstName,
7996
String lastName,
8097
LocalDate dateOfBirth,
81-
Gender gender,
98+
com.klabis.members.domain.Gender gender,
8299
String nationality
83100
) {
84101
return PersonalInformation.of(firstName, lastName, dateOfBirth, nationality, gender);

backend/src/main/java/com/klabis/members/infrastructure/restapi/UpdateMemberRequestMapper.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,16 @@ static Member.UpdateMember toCommand(UpdateMemberRequest request, UserId updated
2222
unwrap(request.nationality()),
2323
map(request.bankAccountNumber(), UpdateMemberRequestMapper::toBankAccountNumber),
2424
map(request.identityCard(), dto -> IdentityCard.of(dto.cardNumber(), dto.validityDate())),
25-
request.drivingLicenseGroup(),
25+
map(request.drivingLicenseGroup(), UpdateMemberRequestMapper::toDrivingLicenseGroup),
2626
map(request.medicalCourse(), UpdateMemberRequestMapper::toMedicalCourse),
27-
map(request.trainerLicense(), dto -> TrainerLicense.of(dto.level(), dto.validityDate())),
28-
map(request.refereeLicense(), dto -> RefereeLicense.of(dto.level(), dto.validityDate())),
27+
map(request.trainerLicense(), dto -> TrainerLicense.of(toTrainerLevel(dto.level()), dto.validityDate())),
28+
map(request.refereeLicense(), dto -> RefereeLicense.of(toRefereeLevel(dto.level()), dto.validityDate())),
2929
request.dietaryRestrictions(),
3030
map(request.guardian(), UpdateMemberRequestMapper::toGuardianInformation),
3131
unwrap(request.firstName()),
3232
unwrap(request.lastName()),
3333
unwrap(request.dateOfBirth()),
34-
unwrap(request.gender()),
34+
toGender(unwrap(request.gender())),
3535
map(request.birthNumber(), UpdateMemberRequestMapper::toBirthNumber),
3636
updatedBy
3737
);
@@ -74,6 +74,22 @@ private static BirthNumber toBirthNumber(String value) {
7474
return value.isBlank() ? null : BirthNumber.of(value);
7575
}
7676

77+
private static com.klabis.members.domain.DrivingLicenseGroup toDrivingLicenseGroup(DrivingLicenseGroup dto) {
78+
return dto == null ? null : com.klabis.members.domain.DrivingLicenseGroup.valueOf(dto.name());
79+
}
80+
81+
private static com.klabis.members.domain.Gender toGender(UpdateMemberRequestGender dto) {
82+
return dto == null ? null : com.klabis.members.domain.Gender.valueOf(dto.name());
83+
}
84+
85+
private static com.klabis.members.domain.TrainerLevel toTrainerLevel(TrainerLicenseDtoLevel dto) {
86+
return dto == null ? null : com.klabis.members.domain.TrainerLevel.valueOf(dto.name());
87+
}
88+
89+
private static com.klabis.members.domain.RefereeLevel toRefereeLevel(RefereeLicenseDtoLevel dto) {
90+
return dto == null ? null : com.klabis.members.domain.RefereeLevel.valueOf(dto.name());
91+
}
92+
7793
private static GuardianInformation toGuardianInformation(GuardianDTO dto) {
7894
return new GuardianInformation(
7995
dto.firstName(),

backend/src/test/java/com/klabis/events/infrastructure/restapi/EventControllerTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import com.klabis.events.application.MemberRegistrationSanctionPort;
1616

1717
import com.klabis.events.domain.*;
18+
import com.klabis.events.domain.EventStatus;
1819
import com.klabis.members.MemberAccommodationDto;
1920
import com.klabis.members.MemberDto;
2021
import com.klabis.members.MemberId;

backend/src/test/java/com/klabis/members/infrastructure/restapi/MemberControllerApiTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import com.klabis.members.*;
1010
import com.klabis.members.application.*;
1111
import com.klabis.members.domain.*;
12+
import com.klabis.members.domain.Gender;
13+
import com.klabis.members.domain.DeactivationReason;
1214
import com.klabis.groups.common.domain.FamilyGroupFilter;
1315
import com.klabis.groups.common.domain.TrainingGroupFilter;
1416
import org.junit.jupiter.api.Disabled;

backend/src/test/java/com/klabis/members/infrastructure/restapi/MemberMappingTests.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ void shouldMapValidTrainerLicenseCorrectly() {
139139
TrainerLicenseDto dto = testedSubject.trainerLicenseToDto(license);
140140

141141
assertThat(dto).isNotNull();
142-
assertThat(dto.level()).isEqualTo(TrainerLevel.T1);
142+
assertThat(dto.level()).isEqualTo(TrainerLicenseDtoLevel.T1);
143143
assertThat(dto.validityDate()).isEqualTo(LocalDate.of(2026, 12, 31));
144144
}
145145

@@ -157,7 +157,7 @@ void shouldMapTrainerLicenseWithTodayValidityDate() {
157157
TrainerLicense license = new TrainerLicense(TrainerLevel.T3, today);
158158
TrainerLicenseDto dto = testedSubject.trainerLicenseToDto(license);
159159

160-
assertThat(dto.level()).isEqualTo(TrainerLevel.T3);
160+
assertThat(dto.level()).isEqualTo(TrainerLicenseDtoLevel.T3);
161161
assertThat(dto.validityDate()).isEqualTo(today);
162162
}
163163
}
@@ -173,7 +173,7 @@ void shouldMapValidRefereeLicenseCorrectly() {
173173
RefereeLicenseDto dto = testedSubject.refereeLicenseToDto(license);
174174

175175
assertThat(dto).isNotNull();
176-
assertThat(dto.level()).isEqualTo(RefereeLevel.R2);
176+
assertThat(dto.level()).isEqualTo(RefereeLicenseDtoLevel.R2);
177177
assertThat(dto.validityDate()).isEqualTo(LocalDate.of(2026, 12, 31));
178178
}
179179

@@ -253,7 +253,7 @@ void shouldMapMemberWithAllFieldsToDetailsResponse() {
253253
.withMedicalCourse(medicalCourse)
254254
.withTrainerLicense(trainerLicense)
255255
.withChipNumber("CHIP123")
256-
.withDrivingLicenseGroup(DrivingLicenseGroup.B)
256+
.withDrivingLicenseGroup(com.klabis.members.domain.DrivingLicenseGroup.B)
257257
.withDietaryRestrictions("No restrictions")
258258
.build();
259259

@@ -274,8 +274,9 @@ void shouldMapMemberWithAllFieldsToDetailsResponse() {
274274
assertThat(dto.medicalCourse()).isNotNull();
275275
assertThat(dto.medicalCourse().completionDate()).isEqualTo(LocalDate.of(2024, 1, 1));
276276
assertThat(dto.trainerLicense()).isNotNull();
277-
assertThat(dto.trainerLicense().level()).isEqualTo(TrainerLevel.T2);
278-
assertThat(dto.drivingLicenseGroup()).isEqualTo(DrivingLicenseGroup.B);
277+
assertThat(dto.trainerLicense().level()).isEqualTo(TrainerLicenseDtoLevel.T2);
278+
assertThat(dto.drivingLicenseGroup()).isEqualTo(
279+
com.klabis.members.infrastructure.restapi.DrivingLicenseGroup.B);
279280
assertThat(dto.dietaryRestrictions()).isEqualTo("No restrictions");
280281
}
281282

@@ -350,7 +351,7 @@ void shouldMapMemberWithOnlyTrainerLicense() {
350351
assertThat(dto.identityCard()).isNull();
351352
assertThat(dto.medicalCourse()).isNull();
352353
assertThat(dto.trainerLicense()).isNotNull();
353-
assertThat(dto.trainerLicense().level()).isEqualTo(TrainerLevel.T1);
354+
assertThat(dto.trainerLicense().level()).isEqualTo(TrainerLicenseDtoLevel.T1);
354355
}
355356

356357
@Test

backend/src/test/java/com/klabis/members/infrastructure/restapi/UpdateMemberApiTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import com.klabis.members.application.ManagementPort;
1111
import com.klabis.members.application.MemberNotFoundException;
1212
import com.klabis.members.domain.*;
13+
import com.klabis.members.domain.Gender;
14+
import com.klabis.members.domain.DrivingLicenseGroup;
1315
import org.junit.jupiter.api.DisplayName;
1416
import org.junit.jupiter.api.Nested;
1517
import org.junit.jupiter.api.Test;

0 commit comments

Comments
 (0)