From 0dd5fd4d64b4988419115ab9506074f9ee325bc6 Mon Sep 17 00:00:00 2001 From: Johannes Brodwall Date: Thu, 22 Aug 2024 21:06:15 +0200 Subject: [PATCH] test demonstrating dream code generation with mixins for multiple allOf types * Upgrade openapi-generator version --- README.md | 2 +- pom.xml | 8 +- .../bigExample/.openapi-generator/VERSION | 2 +- .../conversations/.openapi-generator/VERSION | 2 +- .../example/.openapi-generator/VERSION | 2 +- .../fakerestapi/.openapi-generator/VERSION | 2 +- .../geojson/.openapi-generator/VERSION | 2 +- .../.openapi-generator/VERSION | 2 +- .../.openapi-generator/VERSION | 2 +- .../petstore/.openapi-generator/VERSION | 2 +- .../snapshot/poly/.openapi-generator/VERSION | 2 +- .../readOnly/.openapi-generator/VERSION | 2 +- .../reqres-in/.openapi-generator/VERSION | 2 +- .../serverHealth/.openapi-generator/VERSION | 2 +- .../typeHierarchy/.openapi-generator/VERSION | 2 +- .../websockets/.openapi-generator/VERSION | 2 +- .../javalombok/FocusedExampleTest.java | 2 +- .../javalombok/demo/EventSourcingTest.java | 408 ++++++++++++++++++ .../openapi/javalombok/demo/lombok.config | 1 + 19 files changed, 432 insertions(+), 17 deletions(-) create mode 100644 src/test/java/io/github/jhannes/openapi/javalombok/demo/EventSourcingTest.java create mode 100644 src/test/java/io/github/jhannes/openapi/javalombok/demo/lombok.config diff --git a/README.md b/README.md index a2c236c..7630de8 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Add to your `pom.xml`: org.openapitools openapi-generator-maven-plugin - 7.7.0 + 7.8.0 openapi-java diff --git a/pom.xml b/pom.xml index 1e62d7d..acfd39d 100644 --- a/pom.xml +++ b/pom.xml @@ -36,7 +36,7 @@ org.openapitools openapi-generator - 7.7.0 + 7.8.0 provided @@ -45,6 +45,12 @@ 1.3.0 test + + org.assertj + assertj-core + 3.26.3 + test + org.junit.jupiter junit-jupiter diff --git a/snapshotTests/snapshot/bigExample/.openapi-generator/VERSION b/snapshotTests/snapshot/bigExample/.openapi-generator/VERSION index 1985849..09a6d30 100644 --- a/snapshotTests/snapshot/bigExample/.openapi-generator/VERSION +++ b/snapshotTests/snapshot/bigExample/.openapi-generator/VERSION @@ -1 +1 @@ -7.7.0 +7.8.0 diff --git a/snapshotTests/snapshot/conversations/.openapi-generator/VERSION b/snapshotTests/snapshot/conversations/.openapi-generator/VERSION index 1985849..09a6d30 100644 --- a/snapshotTests/snapshot/conversations/.openapi-generator/VERSION +++ b/snapshotTests/snapshot/conversations/.openapi-generator/VERSION @@ -1 +1 @@ -7.7.0 +7.8.0 diff --git a/snapshotTests/snapshot/example/.openapi-generator/VERSION b/snapshotTests/snapshot/example/.openapi-generator/VERSION index 1985849..09a6d30 100644 --- a/snapshotTests/snapshot/example/.openapi-generator/VERSION +++ b/snapshotTests/snapshot/example/.openapi-generator/VERSION @@ -1 +1 @@ -7.7.0 +7.8.0 diff --git a/snapshotTests/snapshot/fakerestapi/.openapi-generator/VERSION b/snapshotTests/snapshot/fakerestapi/.openapi-generator/VERSION index 1985849..09a6d30 100644 --- a/snapshotTests/snapshot/fakerestapi/.openapi-generator/VERSION +++ b/snapshotTests/snapshot/fakerestapi/.openapi-generator/VERSION @@ -1 +1 @@ -7.7.0 +7.8.0 diff --git a/snapshotTests/snapshot/geojson/.openapi-generator/VERSION b/snapshotTests/snapshot/geojson/.openapi-generator/VERSION index 1985849..09a6d30 100644 --- a/snapshotTests/snapshot/geojson/.openapi-generator/VERSION +++ b/snapshotTests/snapshot/geojson/.openapi-generator/VERSION @@ -1 +1 @@ -7.7.0 +7.8.0 diff --git a/snapshotTests/snapshot/infectionTracker/.openapi-generator/VERSION b/snapshotTests/snapshot/infectionTracker/.openapi-generator/VERSION index 1985849..09a6d30 100644 --- a/snapshotTests/snapshot/infectionTracker/.openapi-generator/VERSION +++ b/snapshotTests/snapshot/infectionTracker/.openapi-generator/VERSION @@ -1 +1 @@ -7.7.0 +7.8.0 diff --git a/snapshotTests/snapshot/openid-configuration/.openapi-generator/VERSION b/snapshotTests/snapshot/openid-configuration/.openapi-generator/VERSION index 1985849..09a6d30 100644 --- a/snapshotTests/snapshot/openid-configuration/.openapi-generator/VERSION +++ b/snapshotTests/snapshot/openid-configuration/.openapi-generator/VERSION @@ -1 +1 @@ -7.7.0 +7.8.0 diff --git a/snapshotTests/snapshot/petstore/.openapi-generator/VERSION b/snapshotTests/snapshot/petstore/.openapi-generator/VERSION index 1985849..09a6d30 100644 --- a/snapshotTests/snapshot/petstore/.openapi-generator/VERSION +++ b/snapshotTests/snapshot/petstore/.openapi-generator/VERSION @@ -1 +1 @@ -7.7.0 +7.8.0 diff --git a/snapshotTests/snapshot/poly/.openapi-generator/VERSION b/snapshotTests/snapshot/poly/.openapi-generator/VERSION index 1985849..09a6d30 100644 --- a/snapshotTests/snapshot/poly/.openapi-generator/VERSION +++ b/snapshotTests/snapshot/poly/.openapi-generator/VERSION @@ -1 +1 @@ -7.7.0 +7.8.0 diff --git a/snapshotTests/snapshot/readOnly/.openapi-generator/VERSION b/snapshotTests/snapshot/readOnly/.openapi-generator/VERSION index 1985849..09a6d30 100644 --- a/snapshotTests/snapshot/readOnly/.openapi-generator/VERSION +++ b/snapshotTests/snapshot/readOnly/.openapi-generator/VERSION @@ -1 +1 @@ -7.7.0 +7.8.0 diff --git a/snapshotTests/snapshot/reqres-in/.openapi-generator/VERSION b/snapshotTests/snapshot/reqres-in/.openapi-generator/VERSION index 1985849..09a6d30 100644 --- a/snapshotTests/snapshot/reqres-in/.openapi-generator/VERSION +++ b/snapshotTests/snapshot/reqres-in/.openapi-generator/VERSION @@ -1 +1 @@ -7.7.0 +7.8.0 diff --git a/snapshotTests/snapshot/serverHealth/.openapi-generator/VERSION b/snapshotTests/snapshot/serverHealth/.openapi-generator/VERSION index 1985849..09a6d30 100644 --- a/snapshotTests/snapshot/serverHealth/.openapi-generator/VERSION +++ b/snapshotTests/snapshot/serverHealth/.openapi-generator/VERSION @@ -1 +1 @@ -7.7.0 +7.8.0 diff --git a/snapshotTests/snapshot/typeHierarchy/.openapi-generator/VERSION b/snapshotTests/snapshot/typeHierarchy/.openapi-generator/VERSION index 1985849..09a6d30 100644 --- a/snapshotTests/snapshot/typeHierarchy/.openapi-generator/VERSION +++ b/snapshotTests/snapshot/typeHierarchy/.openapi-generator/VERSION @@ -1 +1 @@ -7.7.0 +7.8.0 diff --git a/snapshotTests/snapshot/websockets/.openapi-generator/VERSION b/snapshotTests/snapshot/websockets/.openapi-generator/VERSION index 1985849..09a6d30 100644 --- a/snapshotTests/snapshot/websockets/.openapi-generator/VERSION +++ b/snapshotTests/snapshot/websockets/.openapi-generator/VERSION @@ -1 +1 @@ -7.7.0 +7.8.0 diff --git a/src/test/java/io/github/jhannes/openapi/javalombok/FocusedExampleTest.java b/src/test/java/io/github/jhannes/openapi/javalombok/FocusedExampleTest.java index 0337224..a384cd5 100644 --- a/src/test/java/io/github/jhannes/openapi/javalombok/FocusedExampleTest.java +++ b/src/test/java/io/github/jhannes/openapi/javalombok/FocusedExampleTest.java @@ -24,7 +24,7 @@ */ public class FocusedExampleTest extends AbstractSnapshotTest { - public static final Path SPEC = SNAPSHOT_ROOT.resolve("input/poly.yaml"); + public static final Path SPEC = SNAPSHOT_ROOT.resolve("input/readOnly.yaml"); public static final Path ROOT_DIR = SPEC.getParent().getParent(); @TestFactory diff --git a/src/test/java/io/github/jhannes/openapi/javalombok/demo/EventSourcingTest.java b/src/test/java/io/github/jhannes/openapi/javalombok/demo/EventSourcingTest.java new file mode 100644 index 0000000..14b8d42 --- /dev/null +++ b/src/test/java/io/github/jhannes/openapi/javalombok/demo/EventSourcingTest.java @@ -0,0 +1,408 @@ +package io.github.jhannes.openapi.javalombok.demo; + +import lombok.Data; +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class EventSourcingTest { + + public enum GenderDto {MALE, FEMALE, OTHER, UNSPECIFIED} + + public interface DataTransferObject { + boolean isEmpty(); + + default List missingRequiredFields() { + return missingRequiredFields(""); + } + + List missingRequiredFields(String prefix); + + T putAll(T o); + + T removeWhereEqual(T o); + } + + public interface PersonInterface extends DataTransferObject { + String getFirstName(); + PersonInterface setFirstName(String firstName); + + String getLastName(); + PersonInterface setLastName(String lastName); + + GenderDto getGender(); + PersonInterface setGender(GenderDto gender); + + AddressDto getAddress(); + PersonInterface setAddress(AddressDto address); + + PersonInterface removeWhereEqual(PersonInterface o); + + PersonInterface putAll(PersonInterface person); + + void removeWhereEqualFromPersonDto(PersonDto o); + + void removeWhereEqualFromPersonSnapshot(PersonSnapshotDto o); + + default void copyToPersonInterface(PersonInterface o) { + if (getFirstName() != null) o.setFirstName(getFirstName()); + if (getLastName() != null) o.setLastName(getLastName()); + if (getGender() != null) o.setGender(getGender()); + if (getAddress() != null) o.setAddress(getAddress()); + } + + default void removeWhereEqualFromPersonInterface(PersonInterface o) { + if (Objects.equals(getAddress(), o.getAddress())) o.setAddress(null); + if (Objects.equals(getFirstName(), o.getFirstName())) o.setFirstName(null); + if (Objects.equals(getLastName(), o.getLastName())) o.setLastName(null); + if (Objects.equals(getGender(), o.getGender())) o.setGender(null); + } + + boolean isEmpty(); + + default boolean isPersonEmpty() { + return getFirstName() == null + && getLastName() == null + && getGender() == null + && getAddress() == null; + } + + void copyToPersonSnapshot(PersonSnapshotDto o); + + void copyToPerson(PersonDto o); + + default List missingRequiredFields() { + var result = new ArrayList(); + if (getAddress() != null) result.addAll(getAddress().missingRequiredFields("address.")); + return result; + } + } + + @Data + public static class AddressDto implements DataTransferObject { + private String street; + private Integer houseNumber; + private String postalCode; + + public List missingRequiredFields(String prefix) { + var result = new ArrayList(); + if (getStreet() == null) result.add(prefix + "street"); + if (getHouseNumber() == null) result.add(prefix + "houseNumber"); + if (getPostalCode() == null) result.add(prefix + "postalCode"); + return result; + } + + @Override + public AddressDto putAll(AddressDto o) { + if (o.getStreet() != null) o.setStreet(getStreet()); + if (o.getHouseNumber() != null) o.setHouseNumber(getHouseNumber()); + if (o.getPostalCode() != null) o.setPostalCode(getPostalCode()); + return this; + } + + @Override + public AddressDto removeWhereEqual(AddressDto o) { + return this; + } + + @Override + public boolean isEmpty() { + return false; + } + } + + public interface ChangeTrackedInterface { + Instant getCreatedAt(); + + ChangeTrackedInterface setCreatedAt(Instant createdAt); + + Instant getUpdatedAt(); + + ChangeTrackedInterface setUpdatedAt(Instant updatedAt); + + ChangeTrackedInterface putAll(ChangeTrackedInterface o); + + void copyToPersonSnapshot(PersonSnapshotDto o); + + void copyToChangeTrackedDto(ChangeTrackedDto changeTrackedDto); + + default void copyToChangeTrackedInterface(ChangeTrackedInterface o) { + if (getCreatedAt() != null) o.setCreatedAt(getCreatedAt()); + if (getUpdatedAt() != null) o.setUpdatedAt(getUpdatedAt()); + } + + default void removeWhereEqualFromChangeTrackedInterface(PersonSnapshotDto o) { + if (Objects.equals(getCreatedAt(), o.getCreatedAt())) o.setCreatedAt(null); + if (Objects.equals(getUpdatedAt(), o.getUpdatedAt())) o.setUpdatedAt(null); + } + + default boolean isChangeTrackedEmpty() { + return getCreatedAt() == null + && getUpdatedAt() == null; + } + } + + @Data + public static class PersonSnapshotDto implements PersonInterface, ChangeTrackedInterface { + private String firstName; + private String lastName; + private GenderDto gender; + private AddressDto address; + private Instant createdAt; + private Instant updatedAt; + + public PersonSnapshotDto putAll(PersonSnapshotDto o) { + o.copyToPersonSnapshot(this); + return this; + } + + @Override + public PersonSnapshotDto putAll(ChangeTrackedInterface o) { + o.copyToPersonSnapshot(this); + return this; + } + + @Override + public PersonSnapshotDto putAll(PersonInterface o) { + o.copyToPersonSnapshot(this); + return this; + } + + @Override + public boolean isEmpty() { + return isPersonEmpty() && isChangeTrackedEmpty(); + } + + @Override + public List missingRequiredFields(String prefix) { + var result = new ArrayList(); + if (getAddress() != null) result.addAll(getAddress().missingRequiredFields(prefix + "address.")); + if (getFirstName() == null) result.add(prefix + "firstName"); + if (getLastName() == null) result.add(prefix + "lastName"); + if (getGender() == null) result.add(prefix + "gender"); + return result; + } + + @Override + public PersonSnapshotDto removeWhereEqual(PersonInterface o) { + o.removeWhereEqualFromPersonSnapshot(this); + return this; + } + + @Override + public void removeWhereEqualFromPersonDto(PersonDto o) { + removeWhereEqualFromPersonInterface(o); + } + + @Override + public void removeWhereEqualFromPersonSnapshot(PersonSnapshotDto o) { + removeWhereEqualFromPersonInterface(o); + removeWhereEqualFromChangeTrackedInterface(o); + } + + @Override + public void copyToPersonSnapshot(PersonSnapshotDto o) { + copyToPersonInterface(o); + copyToChangeTrackedInterface(o); + } + + @Override + public void copyToPerson(PersonDto o) { + copyToPersonInterface(o); + } + + @Override + public void copyToChangeTrackedDto(ChangeTrackedDto o) { + copyToChangeTrackedInterface(o); + } + } + + @Data + public static class PersonDto implements PersonInterface { + private String firstName; + private String lastName; + private GenderDto gender; + private AddressDto address; + + @Override + public List missingRequiredFields(String prefix) { + return new ArrayList<>(); + } + + @Override + public PersonDto putAll(PersonInterface o) { + o.copyToPerson(this); + return this; + } + + @Override + public List missingRequiredFields() { + return new ArrayList<>(); + } + + @Override + public void copyToPersonSnapshot(PersonSnapshotDto o) { + copyToPersonInterface(o); + } + + @Override + public void copyToPerson(PersonDto o) { + copyToPersonInterface(o); + } + + @Override + public PersonDto removeWhereEqual(PersonInterface o) { + o.removeWhereEqualFromPersonDto(this); + return this; + } + + @Override + public void removeWhereEqualFromPersonDto(PersonDto o) { + removeWhereEqualFromPersonInterface(o); + } + + @Override + public void removeWhereEqualFromPersonSnapshot(PersonSnapshotDto o) { + removeWhereEqualFromPersonInterface(o); + } + + @Override + public boolean isEmpty() { + return isPersonEmpty(); + } + } + + @Data + public static class ChangeTrackedDto implements ChangeTrackedInterface { + private Instant createdAt; + private Instant updatedAt; + + public ChangeTrackedDto putAll(ChangeTrackedInterface o) { + o.copyToChangeTrackedDto(this); + return this; + } + + @Override + public void copyToChangeTrackedDto(ChangeTrackedDto o) { + copyToChangeTrackedInterface(o); + } + + @Override + public void copyToPersonSnapshot(PersonSnapshotDto o) { + copyToChangeTrackedInterface(o); + } + } + + @Data + public static class UpdatePersonEventDto { + private Instant timestamp; + private PersonDto person; + } + + @Data + public static class AuditEventDto { + private PersonSnapshotDto originalSnapshot; + private PersonSnapshotDto finalSnapshot; + private ChangeTrackedDto changeTracked; + private PersonDto minimizedDelta; + } + + + private final ZoneOffset offset = ZoneOffset.ofHours(5); + + @Test + void shouldCreateUsefulMethods() { + var day = LocalDate.of(2024, 1, 1); + var createdTime = day.atTime(LocalTime.of(8, 1)).toInstant(offset); + var originalUpdatedTime = day.atTime(LocalTime.of(8, 8)).toInstant(offset); + var finalUpdatedTime = day.atTime(LocalTime.of(8, 8)).toInstant(offset); + + var originalFirstName = "John"; + var unchangedLastName = "Doe"; + var updatedFirstName = "Jane"; + + var originalPerson = new PersonSnapshotDto() + .setCreatedAt(createdTime) + .setUpdatedAt(originalUpdatedTime) + .setFirstName(originalFirstName) + .setLastName(unchangedLastName) + .setGender(GenderDto.MALE); + + var event = new UpdatePersonEventDto().setTimestamp(finalUpdatedTime).setPerson(new PersonDto() + .setFirstName(updatedFirstName) + .setLastName(unchangedLastName) + .setGender(GenderDto.FEMALE) + ); + + var auditEvent = reduce(originalPerson, event); + assertThat(auditEvent.getOriginalSnapshot()) + .isEqualTo(new PersonSnapshotDto() + .setCreatedAt(createdTime) + .setUpdatedAt(originalUpdatedTime) + .setFirstName(originalFirstName) + .setLastName(unchangedLastName) + .setGender(GenderDto.MALE)); + assertThat(auditEvent.getMinimizedDelta()) + .isEqualTo(new PersonDto().setFirstName(updatedFirstName).setGender(GenderDto.FEMALE)); + assertThat(auditEvent.getFinalSnapshot()) + .isEqualTo(new PersonSnapshotDto() + .setCreatedAt(createdTime) + .setUpdatedAt(originalUpdatedTime) + .setFirstName(updatedFirstName) + .setLastName(unchangedLastName) + .setGender(GenderDto.FEMALE)); + + assertThat(reduce(auditEvent.getFinalSnapshot(), event)) + .isNull(); + } + + + @Test + void shouldGenerateMissingRequiredFields() { + assertThatThrownBy(() -> reduce(new PersonSnapshotDto(), new UpdatePersonEventDto() + .setPerson(new PersonDto().setLastName("Doe")) + )).hasMessage("Missing required fields in person snapshot: [firstName, gender]"); + } + + @Test + void shouldGenerateMissingRequiredNestedFields() { + assertThatThrownBy(() -> reduce( + new PersonSnapshotDto().setFirstName("John").setLastName("Doe").setGender(GenderDto.MALE), + new UpdatePersonEventDto().setPerson(new PersonDto().setAddress(new AddressDto().setStreet("A Street").setHouseNumber(1))) + )).hasMessage("Missing required fields in person snapshot: [address.postalCode]"); + } + + + private AuditEventDto reduce(PersonSnapshotDto originalPerson, UpdatePersonEventDto event) { + var changeTracked = new ChangeTrackedDto() + .putAll(originalPerson) + .setUpdatedAt(event.getTimestamp()); + var minimizedDelta = event.getPerson().removeWhereEqual(originalPerson); + if (minimizedDelta.isEmpty()) { + return null; + } + var finalSnapshot = new PersonSnapshotDto() + .putAll(originalPerson) + .putAll(changeTracked) + .putAll(minimizedDelta); + + if (!finalSnapshot.missingRequiredFields().isEmpty()) { + throw new IllegalStateException("Missing required fields in person snapshot: " + finalSnapshot.missingRequiredFields()); + } + + return new AuditEventDto() + .setOriginalSnapshot(originalPerson) + .setChangeTracked(changeTracked) + .setMinimizedDelta(minimizedDelta) + .setFinalSnapshot(finalSnapshot); + } +} diff --git a/src/test/java/io/github/jhannes/openapi/javalombok/demo/lombok.config b/src/test/java/io/github/jhannes/openapi/javalombok/demo/lombok.config new file mode 100644 index 0000000..8571a69 --- /dev/null +++ b/src/test/java/io/github/jhannes/openapi/javalombok/demo/lombok.config @@ -0,0 +1 @@ +lombok.accessors.chain=true