From 5a6b47c94a564e97121af29b85ea792012e6b3ce Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Wed, 1 Jul 2026 13:47:31 +0200 Subject: [PATCH 1/5] feat: add nullable to model config params --- .../definition/internal/descriptor/ModelConfigParameter.java | 2 ++ .../quarkus/deployment/TimefoldModelDescriptorProcessor.java | 5 ++++- .../quarkus/deployment/ModelConfigDescriptorSchemaTest.java | 2 ++ .../modelconfigschema/TestdataModelConfigOverrides.java | 2 +- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/service/definition/src/main/java/ai/timefold/solver/service/definition/internal/descriptor/ModelConfigParameter.java b/service/definition/src/main/java/ai/timefold/solver/service/definition/internal/descriptor/ModelConfigParameter.java index e5f25a2e4c..25f31e185b 100644 --- a/service/definition/src/main/java/ai/timefold/solver/service/definition/internal/descriptor/ModelConfigParameter.java +++ b/service/definition/src/main/java/ai/timefold/solver/service/definition/internal/descriptor/ModelConfigParameter.java @@ -18,6 +18,7 @@ * @param arrayItemType the OpenAPI schema type for array items * @param format the data format which further specifies the schema type * @param schemaTypeRef the OpenAPI schema type reference (used when the schema type is not a primitive type) + * @param nullable whether the parameter accepts {@code null} as a value, as defined by the OpenAPI schema * @param constraintNameRef the constraint name reference (used when the parameter is a constraint weight or any other parameter * related to a constraint) */ @@ -30,6 +31,7 @@ public record ModelConfigParameter( SchemaType arrayItemType, DataFormat format, String schemaTypeRef, + boolean nullable, String constraintNameRef) { } 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 a25d2a0ae1..f10e685b11 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 @@ -1439,6 +1439,7 @@ private ModelConfigParameter resolveModelConfigParameter(FieldInfo fieldInfo) { String description = null; SchemaType type = null; DataFormat format = null; + boolean nullable = false; Optional schemaAnnotationOptional = getSchemaAnnotation(fieldInfo); if (schemaAnnotationOptional.isPresent()) { AnnotationInstance schemaAnnotation = schemaAnnotationOptional.get(); @@ -1450,6 +1451,8 @@ private ModelConfigParameter resolveModelConfigParameter(FieldInfo fieldInfo) { schemaTypeAnnotationValue != null ? SchemaType.valueOf(schemaTypeAnnotationValue.asString()) : null; AnnotationValue formatAnnotationValue = schemaAnnotation.value("format"); format = formatAnnotationValue != null ? DataFormat.fromString(formatAnnotationValue.asString()) : null; + AnnotationValue nullableAnnotationValue = schemaAnnotation.value("nullable"); + nullable = nullableAnnotationValue != null && nullableAnnotationValue.asBoolean(); } name = name != null ? name : fieldInfo.name(); @@ -1474,7 +1477,7 @@ private ModelConfigParameter resolveModelConfigParameter(FieldInfo fieldInfo) { } return new ModelConfigParameter(name, title, description, kind, type, arrayItemType, format, schemaTypeRef, - constraintRef); + nullable, constraintRef); } private DocumentationDescriptor processModelDocumentation() { diff --git a/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/ModelConfigDescriptorSchemaTest.java b/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/ModelConfigDescriptorSchemaTest.java index 01bc90ea82..634aaa3f42 100644 --- a/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/ModelConfigDescriptorSchemaTest.java +++ b/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/ModelConfigDescriptorSchemaTest.java @@ -71,6 +71,7 @@ void detectModelConfigOverrides() throws IOException { softly.assertThat(primitive.format()).isEqualTo(DataFormat.Percentage); softly.assertThat(primitive.kind()).isEqualTo(ParameterKind.PARAMETER); softly.assertThat(primitive.type()).isEqualTo(Schema.SchemaType.INTEGER); + softly.assertThat(primitive.nullable()).isFalse(); ModelConfigParameter primitiveArray = modelConfigDescriptor.configParameters().get(2); softly.assertThat(primitiveArray.id()).isEqualTo("primitiveArrayParam"); @@ -97,6 +98,7 @@ void detectModelConfigOverrides() throws IOException { softly.assertThat(string.kind()).isEqualTo(ParameterKind.PARAMETER); softly.assertThat(string.type()).isEqualTo(Schema.SchemaType.STRING); softly.assertThat(string.schemaTypeRef()).isNull(); + softly.assertThat(string.nullable()).isTrue(); }); } } diff --git a/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/testdata/modelconfigschema/TestdataModelConfigOverrides.java b/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/testdata/modelconfigschema/TestdataModelConfigOverrides.java index 4337c2d05c..d20f7f8404 100644 --- a/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/testdata/modelconfigschema/TestdataModelConfigOverrides.java +++ b/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/testdata/modelconfigschema/TestdataModelConfigOverrides.java @@ -25,7 +25,7 @@ public class TestdataModelConfigOverrides implements ModelConfigOverrides { // Getters added due to serialization in tests when the extension is created (@RegisterExtension) - @Schema(description = "String parameter") + @Schema(description = "String parameter", nullable = true) private String string; public long getConstraintWeight() { From a99237f638e20bdf057dd7a53643027ce8b9abef Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Wed, 1 Jul 2026 13:50:51 +0200 Subject: [PATCH 2/5] feat: add nullable to model config params --- .../modelconfigschema/TestdataModelConfigOverrides.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/testdata/modelconfigschema/TestdataModelConfigOverrides.java b/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/testdata/modelconfigschema/TestdataModelConfigOverrides.java index d20f7f8404..4337c2d05c 100644 --- a/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/testdata/modelconfigschema/TestdataModelConfigOverrides.java +++ b/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/testdata/modelconfigschema/TestdataModelConfigOverrides.java @@ -25,7 +25,7 @@ public class TestdataModelConfigOverrides implements ModelConfigOverrides { // Getters added due to serialization in tests when the extension is created (@RegisterExtension) - @Schema(description = "String parameter", nullable = true) + @Schema(description = "String parameter") private String string; public long getConstraintWeight() { From 4b64113b2ccf88ecacf2221a7dc7d62edbbcdb32 Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Wed, 1 Jul 2026 13:47:31 +0200 Subject: [PATCH 3/5] feat: add nullable to model config params --- .../definition/internal/descriptor/ModelConfigParameter.java | 2 ++ .../quarkus/deployment/TimefoldModelDescriptorProcessor.java | 5 ++++- .../quarkus/deployment/ModelConfigDescriptorSchemaTest.java | 2 ++ .../modelconfigschema/TestdataModelConfigOverrides.java | 2 +- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/service/definition/src/main/java/ai/timefold/solver/service/definition/internal/descriptor/ModelConfigParameter.java b/service/definition/src/main/java/ai/timefold/solver/service/definition/internal/descriptor/ModelConfigParameter.java index e5f25a2e4c..25f31e185b 100644 --- a/service/definition/src/main/java/ai/timefold/solver/service/definition/internal/descriptor/ModelConfigParameter.java +++ b/service/definition/src/main/java/ai/timefold/solver/service/definition/internal/descriptor/ModelConfigParameter.java @@ -18,6 +18,7 @@ * @param arrayItemType the OpenAPI schema type for array items * @param format the data format which further specifies the schema type * @param schemaTypeRef the OpenAPI schema type reference (used when the schema type is not a primitive type) + * @param nullable whether the parameter accepts {@code null} as a value, as defined by the OpenAPI schema * @param constraintNameRef the constraint name reference (used when the parameter is a constraint weight or any other parameter * related to a constraint) */ @@ -30,6 +31,7 @@ public record ModelConfigParameter( SchemaType arrayItemType, DataFormat format, String schemaTypeRef, + boolean nullable, String constraintNameRef) { } 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 a25d2a0ae1..f10e685b11 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 @@ -1439,6 +1439,7 @@ private ModelConfigParameter resolveModelConfigParameter(FieldInfo fieldInfo) { String description = null; SchemaType type = null; DataFormat format = null; + boolean nullable = false; Optional schemaAnnotationOptional = getSchemaAnnotation(fieldInfo); if (schemaAnnotationOptional.isPresent()) { AnnotationInstance schemaAnnotation = schemaAnnotationOptional.get(); @@ -1450,6 +1451,8 @@ private ModelConfigParameter resolveModelConfigParameter(FieldInfo fieldInfo) { schemaTypeAnnotationValue != null ? SchemaType.valueOf(schemaTypeAnnotationValue.asString()) : null; AnnotationValue formatAnnotationValue = schemaAnnotation.value("format"); format = formatAnnotationValue != null ? DataFormat.fromString(formatAnnotationValue.asString()) : null; + AnnotationValue nullableAnnotationValue = schemaAnnotation.value("nullable"); + nullable = nullableAnnotationValue != null && nullableAnnotationValue.asBoolean(); } name = name != null ? name : fieldInfo.name(); @@ -1474,7 +1477,7 @@ private ModelConfigParameter resolveModelConfigParameter(FieldInfo fieldInfo) { } return new ModelConfigParameter(name, title, description, kind, type, arrayItemType, format, schemaTypeRef, - constraintRef); + nullable, constraintRef); } private DocumentationDescriptor processModelDocumentation() { diff --git a/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/ModelConfigDescriptorSchemaTest.java b/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/ModelConfigDescriptorSchemaTest.java index 01bc90ea82..634aaa3f42 100644 --- a/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/ModelConfigDescriptorSchemaTest.java +++ b/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/ModelConfigDescriptorSchemaTest.java @@ -71,6 +71,7 @@ void detectModelConfigOverrides() throws IOException { softly.assertThat(primitive.format()).isEqualTo(DataFormat.Percentage); softly.assertThat(primitive.kind()).isEqualTo(ParameterKind.PARAMETER); softly.assertThat(primitive.type()).isEqualTo(Schema.SchemaType.INTEGER); + softly.assertThat(primitive.nullable()).isFalse(); ModelConfigParameter primitiveArray = modelConfigDescriptor.configParameters().get(2); softly.assertThat(primitiveArray.id()).isEqualTo("primitiveArrayParam"); @@ -97,6 +98,7 @@ void detectModelConfigOverrides() throws IOException { softly.assertThat(string.kind()).isEqualTo(ParameterKind.PARAMETER); softly.assertThat(string.type()).isEqualTo(Schema.SchemaType.STRING); softly.assertThat(string.schemaTypeRef()).isNull(); + softly.assertThat(string.nullable()).isTrue(); }); } } diff --git a/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/testdata/modelconfigschema/TestdataModelConfigOverrides.java b/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/testdata/modelconfigschema/TestdataModelConfigOverrides.java index 4337c2d05c..d20f7f8404 100644 --- a/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/testdata/modelconfigschema/TestdataModelConfigOverrides.java +++ b/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/testdata/modelconfigschema/TestdataModelConfigOverrides.java @@ -25,7 +25,7 @@ public class TestdataModelConfigOverrides implements ModelConfigOverrides { // Getters added due to serialization in tests when the extension is created (@RegisterExtension) - @Schema(description = "String parameter") + @Schema(description = "String parameter", nullable = true) private String string; public long getConstraintWeight() { From 946ac474f7f1fcf93a88bb51e876f46629c33b49 Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Wed, 1 Jul 2026 13:50:51 +0200 Subject: [PATCH 4/5] feat: add nullable to model config params --- .../modelconfigschema/TestdataModelConfigOverrides.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/testdata/modelconfigschema/TestdataModelConfigOverrides.java b/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/testdata/modelconfigschema/TestdataModelConfigOverrides.java index d20f7f8404..4337c2d05c 100644 --- a/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/testdata/modelconfigschema/TestdataModelConfigOverrides.java +++ b/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/testdata/modelconfigschema/TestdataModelConfigOverrides.java @@ -25,7 +25,7 @@ public class TestdataModelConfigOverrides implements ModelConfigOverrides { // Getters added due to serialization in tests when the extension is created (@RegisterExtension) - @Schema(description = "String parameter", nullable = true) + @Schema(description = "String parameter") private String string; public long getConstraintWeight() { From 4aa716a4632c69383ebaa52810777fc92913f5aa Mon Sep 17 00:00:00 2001 From: Diogo Ferreira Date: Tue, 7 Jul 2026 17:41:40 +0200 Subject: [PATCH 5/5] fix: fix unit test related with nullable config parameters --- .../modelconfigschema/TestdataModelConfigOverrides.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/testdata/modelconfigschema/TestdataModelConfigOverrides.java b/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/testdata/modelconfigschema/TestdataModelConfigOverrides.java index 4337c2d05c..d20f7f8404 100644 --- a/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/testdata/modelconfigschema/TestdataModelConfigOverrides.java +++ b/service/quarkus/deployment/src/test/java/ai/timefold/solver/service/quarkus/deployment/testdata/modelconfigschema/TestdataModelConfigOverrides.java @@ -25,7 +25,7 @@ public class TestdataModelConfigOverrides implements ModelConfigOverrides { // Getters added due to serialization in tests when the extension is created (@RegisterExtension) - @Schema(description = "String parameter") + @Schema(description = "String parameter", nullable = true) private String string; public long getConstraintWeight() {