Skip to content

Commit

Permalink
feat: implement validators for QuerySpecDto and CatalogRequestDto (
Browse files Browse the repository at this point in the history
…#3229)

feat: implement validator for QuerySpecDto and CatalogRequestDto
  • Loading branch information
ndr-brt authored Jun 26, 2023
1 parent dfaba84 commit d762971
Show file tree
Hide file tree
Showing 37 changed files with 724 additions and 295 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import jakarta.json.JsonString;
import org.eclipse.edc.validator.spi.ValidationResult;
import org.eclipse.edc.validator.spi.Validator;
import org.eclipse.edc.validator.spi.Violation;

import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -51,6 +52,10 @@ protected JsonObjectValidator(JsonLdPath path, JsonWalker walker) {

@Override
public ValidationResult validate(JsonObject input) {
if (input == null) {
return ValidationResult.failure(Violation.violation("input json is null", path.toString()));
}

var violations = walker.extract(input, path)
.flatMap(target -> this.validators.stream().map(validator -> validator.validate(target)))
.filter(ValidationResult::failed)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@
import org.eclipse.edc.validator.jsonobject.validators.MandatoryObject;
import org.eclipse.edc.validator.jsonobject.validators.MandatoryValue;
import org.eclipse.edc.validator.jsonobject.validators.OptionalIdNotBlank;
import org.eclipse.edc.validator.spi.ValidationFailure;
import org.eclipse.edc.validator.spi.Violation;
import org.junit.jupiter.api.Test;

import static jakarta.json.Json.createArrayBuilder;
import static jakarta.json.Json.createObjectBuilder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.InstanceOfAssertFactories.list;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VALUE;
import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat;
Expand Down Expand Up @@ -164,6 +167,19 @@ void shouldValidateNestedArrayItem_failure() {
});
}

@Test
void shouldFail_whenInputIsNull() {
var result = JsonObjectValidator.newValidator().build()
.validate(null);

assertThat(result).isFailed().extracting(ValidationFailure::getViolations).asInstanceOf(list(Violation.class))
.hasSize(1)
.first().satisfies(violation -> {
assertThat(violation.message()).contains("null");
assertThat(violation.path()).isEmpty();
});
}

private JsonArrayBuilder value(String value) {
return createArrayBuilder().add(createObjectBuilder().add(VALUE, value));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,18 @@
import org.eclipse.edc.api.transformer.JsonObjectToCallbackAddressTransformer;
import org.eclipse.edc.api.transformer.JsonObjectToCriterionDtoTransformer;
import org.eclipse.edc.api.transformer.QuerySpecDtoToQuerySpecTransformer;
import org.eclipse.edc.api.validation.QuerySpecDtoValidator;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
import org.eclipse.edc.spi.system.ServiceExtension;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.edc.spi.types.TypeManager;
import org.eclipse.edc.transform.spi.TypeTransformerRegistry;
import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry;

import java.util.Map;

import static org.eclipse.edc.api.model.QuerySpecDto.EDC_QUERY_SPEC_TYPE;
import static org.eclipse.edc.spi.CoreConstants.JSON_LD;

@Extension(value = ApiCoreExtension.NAME)
Expand All @@ -48,6 +51,9 @@ public class ApiCoreExtension implements ServiceExtension {
@Inject
private TypeManager typeManager;

@Inject
private JsonObjectValidatorRegistry validatorRegistry;

@Override
public String name() {
return NAME;
Expand All @@ -71,5 +77,7 @@ public void initialize(ServiceExtensionContext context) {

transformerRegistry.register(new JsonObjectToCallbackAddressTransformer());
transformerRegistry.register(new JsonObjectToCriterionDtoTransformer());

validatorRegistry.register(EDC_QUERY_SPEC_TYPE, QuerySpecDtoValidator.instance());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,19 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import jakarta.validation.constraints.NotNull;

import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE;

@JsonDeserialize(builder = CriterionDto.Builder.class)
public class CriterionDto extends BaseDto {

// constants for JSON-LD transformation
public static final String CRITERION_TYPE = EDC_NAMESPACE + "CriterionDto";
public static final String CRITERION_OPERAND_LEFT = EDC_NAMESPACE + "operandLeft";
public static final String CRITERION_OPERAND_RIGHT = EDC_NAMESPACE + "operandRight";
public static final String CRITERION_OPERATOR = EDC_NAMESPACE + "operator";
public static final String CRITERION_TYPE = EDC_NAMESPACE + "CriterionDto";

@NotNull(message = "operandLeft cannot be null")
private Object operandLeft;
@NotNull(message = "operator cannot be null")
private String operator;
private Object operandRight;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,8 @@
package org.eclipse.edc.api.model;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.PositiveOrZero;
import jakarta.ws.rs.QueryParam;
import org.eclipse.edc.spi.query.SortOrder;

import java.util.ArrayList;
Expand All @@ -39,21 +34,11 @@ public class QuerySpecDto extends BaseDto {
public static final String EDC_QUERY_SPEC_SORT_ORDER = EDC_NAMESPACE + "sortOrder";
public static final String EDC_QUERY_SPEC_SORT_FIELD = EDC_NAMESPACE + "sortField";

@QueryParam("offset")
@PositiveOrZero(message = "offset must be greater or equal to zero")
private Integer offset = 0;

@QueryParam("limit")
@Positive(message = "limit must be greater than 0")
private Integer limit = 50;

private final List<CriterionDto> filterExpression = new ArrayList<>();

@QueryParam("sort")
private SortOrder sortOrder = SortOrder.ASC;

@QueryParam("sortField")
private String sortField;
private final List<CriterionDto> filterExpression = new ArrayList<>();

public Integer getOffset() {
return offset;
Expand All @@ -71,12 +56,6 @@ public String getSortField() {
return sortField;
}

@JsonIgnore
@AssertTrue
public boolean isValid() {
return sortField == null || !sortField.isBlank();
}

public List<CriterionDto> getFilterExpression() {
return filterExpression;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

package org.eclipse.edc.api.validation;

import jakarta.json.JsonObject;
import org.eclipse.edc.validator.jsonobject.JsonLdPath;
import org.eclipse.edc.validator.jsonobject.JsonObjectValidator;
import org.eclipse.edc.validator.jsonobject.validators.MandatoryValue;
import org.eclipse.edc.validator.spi.ValidationResult;
import org.eclipse.edc.validator.spi.Validator;
import org.eclipse.edc.validator.spi.Violation;

import java.util.Optional;

import static java.lang.String.format;
import static org.eclipse.edc.api.model.CriterionDto.CRITERION_OPERAND_LEFT;
import static org.eclipse.edc.api.model.CriterionDto.CRITERION_OPERAND_RIGHT;
import static org.eclipse.edc.api.model.CriterionDto.CRITERION_OPERATOR;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VALUE;

public class CriterionDtoValidator {

public static Validator<JsonObject> instance() {
return instance(JsonObjectValidator.newValidator()).build();
}

public static JsonObjectValidator.Builder instance(JsonObjectValidator.Builder builder) {
return builder
.verify(CRITERION_OPERAND_LEFT, MandatoryValue::new)
.verify(CRITERION_OPERATOR, MandatoryValue::new)
.verify(OperandRightValidator::new);
}

private record OperandRightValidator(JsonLdPath path) implements Validator<JsonObject> {

@Override
public ValidationResult validate(JsonObject input) {
var operator = Optional.ofNullable(input.getJsonArray(CRITERION_OPERATOR))
.map(it -> it.getJsonObject(0))
.map(it -> it.getString(VALUE))
.orElse(null);

if (operator == null || "in".equals(operator)) {
return ValidationResult.success();
}

return Optional.ofNullable(input.getJsonArray(CRITERION_OPERAND_RIGHT))
.filter(it -> it.size() == 1)
.map(it -> ValidationResult.success())
.orElse(ValidationResult.failure(Violation.violation(format("%s cannot contain multiple values as the operator is not 'in'", path.toString()), CRITERION_OPERAND_RIGHT)));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

package org.eclipse.edc.api.validation;

import jakarta.json.JsonObject;
import org.eclipse.edc.spi.query.SortOrder;
import org.eclipse.edc.validator.jsonobject.JsonLdPath;
import org.eclipse.edc.validator.jsonobject.JsonObjectValidator;
import org.eclipse.edc.validator.spi.ValidationResult;
import org.eclipse.edc.validator.spi.Validator;

import java.util.Arrays;
import java.util.Optional;

import static java.lang.String.format;
import static java.util.Collections.emptyList;
import static org.eclipse.edc.api.model.QuerySpecDto.EDC_QUERY_SPEC_FILTER_EXPRESSION;
import static org.eclipse.edc.api.model.QuerySpecDto.EDC_QUERY_SPEC_LIMIT;
import static org.eclipse.edc.api.model.QuerySpecDto.EDC_QUERY_SPEC_OFFSET;
import static org.eclipse.edc.api.model.QuerySpecDto.EDC_QUERY_SPEC_SORT_FIELD;
import static org.eclipse.edc.api.model.QuerySpecDto.EDC_QUERY_SPEC_SORT_ORDER;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VALUE;
import static org.eclipse.edc.validator.spi.Violation.violation;

public class QuerySpecDtoValidator {

public static Validator<JsonObject> instance() {
return instance(JsonObjectValidator.newValidator()).build();
}

public static JsonObjectValidator.Builder instance(JsonObjectValidator.Builder builder) {
return builder
.verify(EDC_QUERY_SPEC_OFFSET, OptionalValueGreaterEqualZero::new)
.verify(EDC_QUERY_SPEC_LIMIT, OptionalValueGreaterZero::new)
.verify(EDC_QUERY_SPEC_SORT_ORDER, OptionalValueSortField::new)
.verify(EDC_QUERY_SPEC_SORT_FIELD, OptionalValueNotBlank::new)
.verifyObject(EDC_QUERY_SPEC_FILTER_EXPRESSION, CriterionDtoValidator::instance);
}

private record OptionalValueGreaterEqualZero(JsonLdPath path) implements Validator<JsonObject> {

@Override
public ValidationResult validate(JsonObject input) {
var value = Optional.ofNullable(input.getJsonArray(path.last()))
.map(it -> it.getJsonObject(0))
.map(it -> it.getInt(VALUE))
.orElse(0);

if (value < 0) {
return ValidationResult.failure(violation(format("optional value '%s' must be greater or equal to zero", path), path.toString(), value));
}

return ValidationResult.success();
}
}

private record OptionalValueGreaterZero(JsonLdPath path) implements Validator<JsonObject> {

@Override
public ValidationResult validate(JsonObject input) {
var value = Optional.ofNullable(input.getJsonArray(path.last()))
.map(it -> it.getJsonObject(0))
.map(it -> it.getInt(VALUE))
.orElse(1);

if (value < 1) {
return ValidationResult.failure(violation(format("optional value '%s' must be greater than zero", path), path.toString(), value));
}

return ValidationResult.success();
}
}

private record OptionalValueSortField(JsonLdPath path) implements Validator<JsonObject> {

@Override
public ValidationResult validate(JsonObject input) {
var values = Optional.ofNullable(input.getJsonArray(path.last()))
.map(array -> array.stream().map(it -> it.asJsonObject().getString(VALUE)).toList())
.orElse(emptyList());

if (values.size() > 0 && !Arrays.stream(SortOrder.values()).map(Enum::name).toList().contains(values.get(0))) {
var message = format("optional value '%s' must be one of %s", path, Arrays.toString(SortOrder.values()));
return ValidationResult.failure(violation(message, path.toString(), values.get(0)));
}

return ValidationResult.success();
}
}

private record OptionalValueNotBlank(JsonLdPath path) implements Validator<JsonObject> {

@Override
public ValidationResult validate(JsonObject input) {
var optional = Optional.ofNullable(input.getJsonArray(path.last()))
.map(it -> it.getJsonObject(0))
.map(it -> it.getString(VALUE));

if (optional.isEmpty()) {
return ValidationResult.success();
}

return optional
.filter(it -> !it.isBlank())
.map(it -> ValidationResult.success())
.orElseGet(() -> ValidationResult.failure(violation(format("optional value '%s' is blank", path), path.toString())));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

package org.eclipse.edc.api;

import org.eclipse.edc.junit.extensions.DependencyInjectionExtension;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import static org.eclipse.edc.api.model.QuerySpecDto.EDC_QUERY_SPEC_TYPE;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

@ExtendWith(DependencyInjectionExtension.class)
class ApiCoreExtensionTest {

private final JsonObjectValidatorRegistry validatorRegistry = mock();

@BeforeEach
void setUp(ServiceExtensionContext context) {
context.registerService(JsonObjectValidatorRegistry.class, validatorRegistry);
}

@Test
void initialize_shouldRegisterValidators(ApiCoreExtension extension, ServiceExtensionContext context) {
extension.initialize(context);

verify(validatorRegistry).register(eq(EDC_QUERY_SPEC_TYPE), any());
}
}
Loading

0 comments on commit d762971

Please sign in to comment.