From 4bba4cf63157cdfb5d1eb5f7791c5d428985739f Mon Sep 17 00:00:00 2001 From: mvanhorn Date: Sat, 27 Jun 2026 02:16:02 -0700 Subject: [PATCH] Fail build when timefold.application.version is missing Validate the resolved application version in the Quarkus build-time processor and fail fast with a clear, actionable error naming the missing timefold.application.version property, instead of letting a null/blank version fall through to a vague runtime error. Fixes #2393 --- .../TimefoldModelDescriptorProcessor.java | 14 ++++++-- .../ApplicationVersionValidationTest.java | 36 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/ApplicationVersionValidationTest.java diff --git a/service/quarkus/deployment/src/main/java/ai/timefold/solver/service/quarkus/deployment/TimefoldModelDescriptorProcessor.java b/service/quarkus/deployment/src/main/java/ai/timefold/solver/service/quarkus/deployment/TimefoldModelDescriptorProcessor.java index 33f4de10b4..e7eb25a33f 100644 --- a/service/quarkus/deployment/src/main/java/ai/timefold/solver/service/quarkus/deployment/TimefoldModelDescriptorProcessor.java +++ b/service/quarkus/deployment/src/main/java/ai/timefold/solver/service/quarkus/deployment/TimefoldModelDescriptorProcessor.java @@ -509,6 +509,14 @@ protected static void validateModelId(String modelId) { } } + protected static void validateApplicationVersion(String version) { + if (version == null || version.trim().isEmpty()) { + throw new IllegalArgumentException( + "The application version is missing. Set the '" + APPLICATION_VERSION_PROPERTY + + "' property (e.g. in application.properties) so the build can proceed."); + } + } + private void generateModelDescriptor(String modelId, String model, OpenAPI openAPI, Path outputDirectory, Optional restResource, @@ -527,8 +535,10 @@ private void generateModelDescriptor(String modelId, String model, OpenAPI openA descriptor.setModel(model); descriptor.setName( config.getOptionalValue(APPLICATION_NAME_PROPERTY, String.class).orElse(openAPI.getInfo().getTitle())); - descriptor.setVersion( - config.getOptionalValue(APPLICATION_VERSION_PROPERTY, String.class).orElse(openAPI.getInfo().getVersion())); + String applicationVersion = + config.getOptionalValue(APPLICATION_VERSION_PROPERTY, String.class).orElse(openAPI.getInfo().getVersion()); + validateApplicationVersion(applicationVersion); + descriptor.setVersion(applicationVersion); descriptor.setResourceType(getResourceTypeFromRestResource(restResource)); descriptor.setDescription(config.getOptionalValue(APPLICATION_DESCRIPTION_PROPERTY, String.class) .orElse(openAPI.getInfo().getDescription())); diff --git a/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/ApplicationVersionValidationTest.java b/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/ApplicationVersionValidationTest.java new file mode 100644 index 0000000000..6a1486a74b --- /dev/null +++ b/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/ApplicationVersionValidationTest.java @@ -0,0 +1,36 @@ +package ai.timefold.solver.service.quarkus.deployment; + +import static ai.timefold.solver.service.quarkus.deployment.TimefoldModelDescriptorProcessor.validateApplicationVersion; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +class ApplicationVersionValidationTest { + + @Test + void validVersionDoesNotThrow() { + assertThatCode(() -> validateApplicationVersion("v1")).doesNotThrowAnyException(); + } + + @Test + void nullVersionThrows() { + assertThatThrownBy(() -> validateApplicationVersion(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("timefold.application.version"); + } + + @Test + void emptyVersionThrows() { + assertThatThrownBy(() -> validateApplicationVersion("")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("timefold.application.version"); + } + + @Test + void blankVersionThrows() { + assertThatThrownBy(() -> validateApplicationVersion(" ")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("timefold.application.version"); + } +}