This is an automated email from the ASF dual-hosted git repository.

adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new 9da7e3ab3d FINERACT-2511: Validate dateFormat parameter to return HTTP 
400 instead of 500
9da7e3ab3d is described below

commit 9da7e3ab3dd6857ab4d15394343c76f0ada423e0
Author: Harshitmehra-270709 <[email protected]>
AuthorDate: Sun Mar 8 13:25:06 2026 +0530

    FINERACT-2511: Validate dateFormat parameter to return HTTP 400 instead of 
500
    
    When an invalid dateFormat (e.g., a date value like '02 February 2026' 
instead of a pattern like 'dd MMMM yyyy') is provided during client creation, 
DateTimeFormatter.ofPattern() throws an IllegalArgumentException that surfaces 
as HTTP 500.
    
    This commit:
    
    - Adds a reusable validDateTimeFormatPattern() method to 
DataValidatorBuilder that validates date/time format patterns by attempting 
DateTimeFormatter.ofPattern() and catching IllegalArgumentException
    
    - Adds dateFormat validation in ClientDataValidator.validateForCreate() and 
validateForUpdate() using the new method
    
    - Adds DataValidatorBuilderDateFormatTest with 12 parameterized unit tests 
covering valid patterns, invalid patterns, null, and blank edge cases
---
 .../core/data/DataValidatorBuilder.java            | 31 +++++++++
 .../data/DataValidatorBuilderDateFormatTest.java   | 73 ++++++++++++++++++++++
 .../portfolio/client/data/ClientDataValidator.java | 12 ++++
 3 files changed, 116 insertions(+)

diff --git 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java
index b8468ebc69..a05a2168f5 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilder.java
@@ -23,6 +23,7 @@ import com.google.gson.JsonArray;
 import java.math.BigDecimal;
 import java.text.ParseException;
 import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -1091,6 +1092,36 @@ public class DataValidatorBuilder {
         return this;
     }
 
+    /**
+     * Validates that the value is a valid {@link DateTimeFormatter} pattern.
+     *
+     * <p>
+     * If the value is a non-blank string, this method attempts to create a 
{@link DateTimeFormatter} using
+     * {@link DateTimeFormatter#ofPattern(String)}. If the pattern is invalid, 
a validation error is added.
+     * </p>
+     *
+     * @return this {@code DataValidatorBuilder} for method chaining
+     */
+    public DataValidatorBuilder validDateTimeFormatPattern() {
+        if (this.value == null && this.ignoreNullValue) {
+            return this;
+        }
+
+        if (this.value != null && this.value instanceof String pattern && 
!StringUtils.isBlank(pattern)) {
+            try {
+                DateTimeFormatter.ofPattern(pattern);
+            } catch (final IllegalArgumentException e) {
+                String validationErrorCode = "validation.msg." + this.resource 
+ "." + this.parameter + ".invalid.dateFormat.pattern";
+                String defaultEnglishMessage = "The parameter `" + 
this.parameter + "` has an invalid date/time pattern: `" + pattern
+                        + "`.";
+                final ApiParameterError error = 
ApiParameterError.parameterError(validationErrorCode, defaultEnglishMessage, 
this.parameter,
+                        pattern);
+                this.dataValidationErrors.add(error);
+            }
+        }
+        return this;
+    }
+
     /**
      * Throws Exception if validation errors.
      *
diff --git 
a/fineract-core/src/test/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilderDateFormatTest.java
 
b/fineract-core/src/test/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilderDateFormatTest.java
new file mode 100644
index 0000000000..9425ac6b88
--- /dev/null
+++ 
b/fineract-core/src/test/java/org/apache/fineract/infrastructure/core/data/DataValidatorBuilderDateFormatTest.java
@@ -0,0 +1,73 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.core.data;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class DataValidatorBuilderDateFormatTest {
+
+    private static final String RESOURCE = "test";
+    private static final String PARAMETER = "dateFormat";
+
+    @ParameterizedTest
+    @ValueSource(strings = { "dd MMMM yyyy", "yyyy-MM-dd", "dd/MM/yyyy", 
"MM/dd/yyyy", "dd-MM-yyyy HH:mm:ss" })
+    void validDateTimeFormatPatternShouldAcceptValidPatterns(final String 
pattern) {
+        final List<ApiParameterError> errors = new ArrayList<>();
+        new 
DataValidatorBuilder(errors).resource(RESOURCE).parameter(PARAMETER).value(pattern).validDateTimeFormatPattern();
+        assertThat(errors).isEmpty();
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = { "02 February 2026", "not-a-pattern", "P!@#$", 
"{{invalid}}" })
+    void validDateTimeFormatPatternShouldRejectInvalidPatterns(final String 
pattern) {
+        final List<ApiParameterError> errors = new ArrayList<>();
+        new 
DataValidatorBuilder(errors).resource(RESOURCE).parameter(PARAMETER).value(pattern).validDateTimeFormatPattern();
+        assertThat(errors).hasSize(1);
+        assertThat(errors.get(0).getParameterName()).isEqualTo(PARAMETER);
+        assertThat(errors.get(0).getDeveloperMessage()).contains("invalid 
date/time pattern");
+    }
+
+    @Test
+    void 
validDateTimeFormatPatternShouldAcceptNullValueWhenIgnoreNullEnabled() {
+        final List<ApiParameterError> errors = new ArrayList<>();
+        new 
DataValidatorBuilder(errors).resource(RESOURCE).parameter(PARAMETER).value(null).ignoreIfNull().validDateTimeFormatPattern();
+        assertThat(errors).isEmpty();
+    }
+
+    @Test
+    void validDateTimeFormatPatternShouldNotFailOnNullValue() {
+        final List<ApiParameterError> errors = new ArrayList<>();
+        // value is null but ignoreIfNull is NOT set — should still not throw 
NPE
+        new 
DataValidatorBuilder(errors).resource(RESOURCE).parameter(PARAMETER).value(null).validDateTimeFormatPattern();
+        assertThat(errors).isEmpty();
+    }
+
+    @Test
+    void validDateTimeFormatPatternShouldNotFailOnBlankValue() {
+        final List<ApiParameterError> errors = new ArrayList<>();
+        new 
DataValidatorBuilder(errors).resource(RESOURCE).parameter(PARAMETER).value("   
").validDateTimeFormatPattern();
+        assertThat(errors).isEmpty();
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientDataValidator.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientDataValidator.java
index ac9922704c..e8f0634ef7 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientDataValidator.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/data/ClientDataValidator.java
@@ -212,6 +212,12 @@ public final class ClientDataValidator {
                     .integerGreaterThanZero();
         }
 
+        if 
(this.fromApiJsonHelper.parameterExists(ClientApiConstants.dateFormatParamName, 
element)) {
+            final String dateFormat = 
this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.dateFormatParamName,
 element);
+            
baseDataValidator.reset().parameter(ClientApiConstants.dateFormatParamName).value(dateFormat).notBlank()
+                    .validDateTimeFormatPattern();
+        }
+
         final Integer legalFormId = 
this.fromApiJsonHelper.extractIntegerSansLocaleNamed(ClientApiConstants.legalFormIdParamName,
 element);
         
baseDataValidator.reset().parameter(ClientApiConstants.legalFormIdParamName).value(legalFormId).notNull().inMinMaxRange(1,
 2);
 
@@ -501,6 +507,12 @@ public final class ClientDataValidator {
                     
.validateDateBefore(DateUtils.getBusinessLocalDate()).validateDateBefore(submittedDate);
         }
 
+        if 
(this.fromApiJsonHelper.parameterExists(ClientApiConstants.dateFormatParamName, 
element)) {
+            final String dateFormat = 
this.fromApiJsonHelper.extractStringNamed(ClientApiConstants.dateFormatParamName,
 element);
+            
baseDataValidator.reset().parameter(ClientApiConstants.dateFormatParamName).value(dateFormat).notBlank()
+                    .validDateTimeFormatPattern();
+        }
+
         if 
(this.fromApiJsonHelper.parameterExists(ClientApiConstants.legalFormIdParamName,
 element)) {
             atLeastOneParameterPassedForUpdate = true;
             final Integer legalFormId = 
this.fromApiJsonHelper.extractIntegerSansLocaleNamed(ClientApiConstants.legalFormIdParamName,

Reply via email to