Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -913,95 +913,126 @@ public void onParameter(final ParameterMeta meta, final JsonValue value) {
return;
}

if (Boolean.parseBoolean(meta.getMetadata().get("tcomp::validation::required"))
&& value == JsonValue.NULL) {
if (isRequiredAndMissingWithoutDefault(meta, value)) {

@thboileau thboileau Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is the fix: required rule fails if there is no default value

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — this is a self-annotation explaining the original Round 0 guard. No further code change needed here.

errors.add(MESSAGES.required(meta.getPath()));
}
final Map<String, String> metadata = meta.getMetadata();
{
final String min = metadata.get("tcomp::validation::min");
if (min != null) {
final double bound = Double.parseDouble(min);
if (value.getValueType() == JsonValue.ValueType.NUMBER
&& ((JsonNumber) value).doubleValue() < bound) {
errors.add(MESSAGES.min(meta.getPath(), bound, ((JsonNumber) value).doubleValue()));
}
validateRuleMin(meta, value, metadata);

@thboileau thboileau Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following Sonar comment: reduce method complexity. Each block has been extracted as a method

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — this is a self-annotation explaining the Round 2/4 Sonar-driven refactor (cognitive complexity extraction). No further code change needed here.

validateRuleMax(meta, value, metadata);
validateRuleMinLength(meta, value, metadata);
validateRuleMaxLength(meta, value, metadata);
validateRuleMinItems(meta, value, metadata);
validateRuleMaxItems(meta, value, metadata);
validateRuleUniqueItems(meta, value, metadata);
validateRulePattern(meta, value, metadata);
}

private void validateRulePattern(final ParameterMeta meta, final JsonValue value,
final Map<String, String> metadata) {
final String pattern = metadata.get("tcomp::validation::pattern");
if (pattern != null && value.getValueType() == JsonValue.ValueType.STRING) {
final String val = ((JsonString) value).getString();
if (!new JavascriptRegex(pattern).test(val)) {
errors.add(MESSAGES.pattern(meta.getPath(), pattern));
}
}
{
final String max = metadata.get("tcomp::validation::max");
if (max != null) {
final double bound = Double.parseDouble(max);
if (value.getValueType() == JsonValue.ValueType.NUMBER
&& ((JsonNumber) value).doubleValue() > bound) {
errors.add(MESSAGES.max(meta.getPath(), bound, ((JsonNumber) value).doubleValue()));
}
}

private void validateRuleUniqueItems(final ParameterMeta meta, final JsonValue value,
final Map<String, String> metadata) {
final String unique = metadata.get("tcomp::validation::uniqueItems");
if (unique != null && value.getValueType() == JsonValue.ValueType.ARRAY) {
final JsonArray array = value.asJsonArray();
if (new HashSet<>(array).size() != array.size()) {
errors.add(MESSAGES.uniqueItems(meta.getPath()));
}
}
{
final String min = metadata.get("tcomp::validation::minLength");
if (min != null) {
final double bound = Double.parseDouble(min);
if (value.getValueType() == JsonValue.ValueType.STRING) {
final String val = ((JsonString) value).getString();
if (val.length() < bound) {
errors.add(MESSAGES.minLength(meta.getPath(), bound, val.length()));
}
}
}

private void validateRuleMaxItems(final ParameterMeta meta, final JsonValue value,
final Map<String, String> metadata) {
final String max = metadata.get("tcomp::validation::maxItems");
if (max != null) {
final double bound = Double.parseDouble(max);
if (value.getValueType() == JsonValue.ValueType.ARRAY && value.asJsonArray().size() > bound) {
errors.add(MESSAGES.maxItems(meta.getPath(), bound, value.asJsonArray().size()));
}
}
{
final String max = metadata.get("tcomp::validation::maxLength");
if (max != null) {
final double bound = Double.parseDouble(max);
if (value.getValueType() == JsonValue.ValueType.STRING) {
final String val = ((JsonString) value).getString();
if (val.length() > bound) {
errors.add(MESSAGES.maxLength(meta.getPath(), bound, val.length()));
}
}
}

private void validateRuleMinItems(final ParameterMeta meta, final JsonValue value,
final Map<String, String> metadata) {
final String min = metadata.get("tcomp::validation::minItems");
if (min != null) {
final double bound = Double.parseDouble(min);
if (value.getValueType() == JsonValue.ValueType.ARRAY && value.asJsonArray().size() < bound) {
errors.add(MESSAGES.minItems(meta.getPath(), bound, value.asJsonArray().size()));
}
}
{
final String min = metadata.get("tcomp::validation::minItems");
if (min != null) {
final double bound = Double.parseDouble(min);
if (value.getValueType() == JsonValue.ValueType.ARRAY && value.asJsonArray().size() < bound) {
errors.add(MESSAGES.minItems(meta.getPath(), bound, value.asJsonArray().size()));
}

private void validateRuleMaxLength(final ParameterMeta meta, final JsonValue value,
final Map<String, String> metadata) {
final String max = metadata.get("tcomp::validation::maxLength");
if (max != null) {
final double bound = Double.parseDouble(max);
if (value.getValueType() == JsonValue.ValueType.STRING) {
final String val = ((JsonString) value).getString();
if (val.length() > bound) {
errors.add(MESSAGES.maxLength(meta.getPath(), bound, val.length()));
}
}
}
{
final String max = metadata.get("tcomp::validation::maxItems");
if (max != null) {
final double bound = Double.parseDouble(max);
if (value.getValueType() == JsonValue.ValueType.ARRAY && value.asJsonArray().size() > bound) {
errors.add(MESSAGES.maxItems(meta.getPath(), bound, value.asJsonArray().size()));
}

private void validateRuleMinLength(final ParameterMeta meta, final JsonValue value,
final Map<String, String> metadata) {
final String min = metadata.get("tcomp::validation::minLength");
if (min != null) {
final double bound = Double.parseDouble(min);
if (value.getValueType() == JsonValue.ValueType.STRING) {
final String val = ((JsonString) value).getString();
if (val.length() < bound) {
errors.add(MESSAGES.minLength(meta.getPath(), bound, val.length()));
}
}
}
{
final String unique = metadata.get("tcomp::validation::uniqueItems");
if (unique != null) {
if (value.getValueType() == JsonValue.ValueType.ARRAY) {
final JsonArray array = value.asJsonArray();
if (new HashSet<>(array).size() != array.size()) {
errors.add(MESSAGES.uniqueItems(meta.getPath()));
}
}
}

private void validateRuleMax(final ParameterMeta meta, final JsonValue value,
final Map<String, String> metadata) {
final String max = metadata.get("tcomp::validation::max");
if (max != null) {
final double bound = Double.parseDouble(max);
if (value.getValueType() == JsonValue.ValueType.NUMBER
&& ((JsonNumber) value).doubleValue() > bound) {
errors.add(MESSAGES.max(meta.getPath(), bound, ((JsonNumber) value).doubleValue()));
}
}
{
final String pattern = metadata.get("tcomp::validation::pattern");
if (pattern != null && value.getValueType() == JsonValue.ValueType.STRING) {
final String val = ((JsonString) value).getString();
if (!new JavascriptRegex(pattern).test((CharSequence) val)) {
errors.add(MESSAGES.pattern(meta.getPath(), pattern));
}
}

private void validateRuleMin(final ParameterMeta meta, final JsonValue value,
final Map<String, String> metadata) {
final String min = metadata.get("tcomp::validation::min");
if (min != null) {
final double bound = Double.parseDouble(min);
if (value.getValueType() == JsonValue.ValueType.NUMBER
&& ((JsonNumber) value).doubleValue() < bound) {
errors.add(MESSAGES.min(meta.getPath(), bound, ((JsonNumber) value).doubleValue()));
}
}
}

private static boolean isRequiredAndMissingWithoutDefault(final ParameterMeta meta,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It lacks one use case: when the default value is an empty string. In such case, the default value is considered as irrelevant

    @Option
    @Required
    @DefaultValue("")
    private String fieldWithEmptyDefault;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 55b3803f435isRequiredAndMissingWithoutDefault now also requires the declared default value to be non-empty, so @DefaultValue("") no longer suppresses the required-field error for an absent key. Added validationRequiredWithEmptyDefaultValueMissingKeyFails to cover it.

final JsonValue value) {
// an empty-string default value is considered as irrelevant, so it does not
// suppress the required check - QTDI-2489
final String defaultValue = meta.getMetadata().get("tcomp::ui::defaultvalue::value");
return Boolean.parseBoolean(meta.getMetadata().get("tcomp::validation::required"))
&& value == JsonValue.NULL
&& (defaultValue == null || defaultValue.isEmpty());
}

private void throwIfFailed() {
if (!errors.isEmpty()) {
throw new IllegalArgumentException("- " + String.join("\n- ", errors));
Expand Down
Loading
Loading