Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add json array order configuration option in java #1254

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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 @@ -16,38 +16,53 @@

package org.citrusframework.validation.json;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import jakarta.annotation.Nullable;
import org.citrusframework.validation.context.DefaultValidationContext;
import org.citrusframework.validation.context.SchemaValidationContext;
import org.citrusframework.validation.context.ValidationContext;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
* Validation context holding JSON specific validation information.
*
* @author Christoph Deppisch
* @since 2.3
*/
public class JsonMessageValidationContext extends DefaultValidationContext implements SchemaValidationContext {

/** Map holding xpath expressions to identify the ignored message elements */
/**
* Map holding xpath expressions to identify the ignored message elements
*/
private final Set<String> ignoreExpressions;

/**
* Should message be validated with its schema definition
*
* <p>
* This is currently disabled by default, because old json tests would fail with a validation exception
* as soon as a json schema repository is specified and the schema validation is activated.
*/
private final boolean schemaValidation;

/** Explicit schema repository to use for this validation */
/**
* Explicit schema repository to use for this validation
*/
private final String schemaRepository;

/** Explicit schema instance to use for this validation */
/**
* Explicit schema instance to use for this validation
*/
private final String schema;

/**
* Whether the ordering of arrays should be validated or not. If this property is not explicitly set, then
* {@code true} will be assumed if the system-wide validation mode is {@code strict}, and {@code false} if otherwise.
*/
@Nullable
private final Boolean checkArrayOrder;

/**
* Default constructor.
*/
Expand All @@ -57,13 +72,15 @@ public JsonMessageValidationContext() {

/**
* Constructor using fluent builder.
*
* @param builder
*/
public JsonMessageValidationContext(Builder builder) {
this.ignoreExpressions = builder.ignoreExpressions;
this.schemaValidation = builder.schemaValidation;
this.schemaRepository = builder.schemaRepository;
this.schema = builder.schema;
this.checkArrayOrder = builder.checkArrayOrder;
}

/**
Expand All @@ -76,6 +93,7 @@ public static final class Builder implements ValidationContext.Builder<JsonMessa
private boolean schemaValidation = true;
private String schemaRepository;
private String schema;
private Boolean checkArrayOrder;

public static Builder json() {
return new Builder();
Expand Down Expand Up @@ -145,6 +163,17 @@ public Builder ignore(final List<String> paths) {
return this;
}

/**
* Sets whether array order should be validated for this message.
*
* @param checkArrayOrder whether array order is checked
* @return this builder for chaining
*/
public Builder checkArrayOrder(final boolean checkArrayOrder) {
this.checkArrayOrder = checkArrayOrder;
return this;
}

@Override
public JsonMessageValidationContext build() {
return new JsonMessageValidationContext(this);
Expand All @@ -153,6 +182,7 @@ public JsonMessageValidationContext build() {

/**
* Get ignored message elements.
*
* @return the ignoreExpressions
*/
public Set<String> getIgnoreExpressions() {
Expand All @@ -173,4 +203,14 @@ public String getSchemaRepository() {
public String getSchema() {
return schema;
}

/**
* Get whether the array order should be considered in validation.
*
* @return whether the array order is checked
*/
@Nullable
public Boolean shouldCheckArrayOrder() {
return checkArrayOrder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,26 @@ public class JsonElementValidator {
private final boolean strict;
private final TestContext context;
private final Collection<String> ignoreExpressions;
private final Boolean checkArrayOrder;

public JsonElementValidator(
boolean strict,
TestContext context,
Collection<String> ignoreExpressions
) {
this(strict, context, ignoreExpressions, null);
}

public JsonElementValidator(
boolean strict,
TestContext context,
Collection<String> ignoreExpressions,
Boolean checkArrayOrder
) {
this.strict = strict;
this.context = context;
this.ignoreExpressions = ignoreExpressions;
this.checkArrayOrder = checkArrayOrder;
}

public void validate(JsonElementValidatorItem<?> control) {
Expand Down Expand Up @@ -96,11 +107,7 @@ static boolean isIgnoredByPlaceholderOrExpressionList(Collection<String> ignoreE
return true;
}

if (ignoreExpressions.stream().anyMatch(controlEntry::isPathIgnoredBy)) {
return true;
}

return false;
return ignoreExpressions.stream().anyMatch(controlEntry::isPathIgnoredBy);
}


Expand All @@ -109,17 +116,40 @@ private void validateJSONArray(JsonElementValidator validator, JsonElementValida
if (strict) {
validateSameSize(control.getJsonPath(), arrayControl.expected, arrayControl.actual);
}

boolean doCheckArrayOrder = requireNonNullElse(checkArrayOrder, strict);
for (int i = 0; i < arrayControl.expected.size(); i++) {
if (!isAnyValidItemInActualArray(validator, arrayControl, i)) {
throw new ValidationException(buildValueToBeInCollectionErrorMessage(
"An item in '%s' is missing".formatted(arrayControl.getJsonPath()),
arrayControl.expected.get(i),
arrayControl.actual
));
if (doCheckArrayOrder) {
checkExactArrayElementPosition(validator, arrayControl, i);
} else {
if (!isAnyValidItemInActualArray(validator, arrayControl, i)) { //TODO ignores element count - intended?
throw new ValidationException(buildValueToBeInCollectionErrorMessage(
"An item in '%s' is missing".formatted(arrayControl.getJsonPath()),
arrayControl.expected.get(i),
arrayControl.actual
));
}
}
}
}

private void checkExactArrayElementPosition(JsonElementValidator validator, JsonElementValidatorItem<JSONArray> control, int index) {
try {
var itemControl = new JsonElementValidatorItem<>(
index,
control.actual.get(index),
control.expected.get(index)
).parent(control);
validator.validate(itemControl);
} catch (ValidationException e) {
throw new ValidationException(buildValueMismatchErrorMessage(
"Elements not equal for array '%s' at position %d".formatted(control.getJsonPath(), index),
control.expected.get(index),
control.actual.get(index)
));
}
}

private boolean isAnyValidItemInActualArray(JsonElementValidator validator, JsonElementValidatorItem<JSONArray> control, int index) {
Object expectedItem = control.expected.get(index);
return control.actual.stream().map(recivedItem -> {
Expand Down
Loading