Skip to content

Commit

Permalink
feat(edrs): add init edr request api validator (#703)
Browse files Browse the repository at this point in the history
  • Loading branch information
wolf4ood authored Aug 11, 2023
1 parent 8a9c698 commit 95ca87a
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 25 deletions.
2 changes: 2 additions & 0 deletions edc-extensions/edr/edr-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ dependencies {

implementation(libs.edc.api.management)
implementation(libs.edc.spi.aggregateservices)
implementation(libs.edc.core.validator)
implementation(libs.jakarta.rsApi)

testImplementation(testFixtures(libs.edc.core.jersey))
testImplementation(libs.restAssured)
testImplementation(libs.edc.junit)
testImplementation(libs.edc.ext.jersey.providers)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,14 @@
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import org.eclipse.edc.api.model.ApiCoreSchema;
import org.eclipse.edc.connector.api.management.configuration.ManagementApiSchema;
import org.eclipse.edc.web.spi.ApiErrorDetail;
import org.eclipse.tractusx.edc.api.edr.dto.NegotiateEdrRequestDto;
import org.eclipse.tractusx.edc.edr.spi.types.EndpointDataReferenceEntry;

import java.util.List;

@OpenAPIDefinition
@Tag(name = "Control Plane EDR Api")
public interface EdrApi {
Expand All @@ -49,9 +48,9 @@ public interface EdrApi {
@ApiResponse(responseCode = "200",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = EndpointDataReferenceEntry.class)))),
@ApiResponse(responseCode = "400", description = "Request was malformed",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class))))}
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)))) }
)
List<JsonObject> queryEdrs(String assetId, String agreementId, String providerId);
JsonArray queryEdrs(String assetId, String agreementId, String providerId);

@Operation(description = "Gets an EDR with the given transfer process ID",
responses = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,19 @@
import org.eclipse.edc.connector.api.management.configuration.transform.ManagementApiTypeTransformerRegistry;
import org.eclipse.edc.jsonld.spi.JsonLd;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.spi.system.ServiceExtension;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry;
import org.eclipse.edc.web.spi.WebService;
import org.eclipse.tractusx.edc.api.edr.transform.EndpointDataReferenceToDataAddressTransformer;
import org.eclipse.tractusx.edc.api.edr.transform.JsonObjectFromEndpointDataReferenceEntryTransformer;
import org.eclipse.tractusx.edc.api.edr.transform.JsonObjectToNegotiateEdrRequestDtoTransformer;
import org.eclipse.tractusx.edc.api.edr.transform.NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer;
import org.eclipse.tractusx.edc.api.edr.validation.NegotiateEdrRequestDtoValidator;
import org.eclipse.tractusx.edc.edr.spi.service.EdrService;

import static org.eclipse.tractusx.edc.api.edr.dto.NegotiateEdrRequestDto.EDR_REQUEST_DTO_TYPE;
import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.TX_NAMESPACE;
import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.TX_PREFIX;

Expand All @@ -46,13 +50,20 @@ public class EdrApiExtension implements ServiceExtension {
@Inject
private JsonLd jsonLdService;

@Inject
private JsonObjectValidatorRegistry validatorRegistry;

@Inject
private Monitor monitor;

@Override
public void initialize(ServiceExtensionContext context) {
jsonLdService.registerNamespace(TX_PREFIX, TX_NAMESPACE);
transformerRegistry.register(new NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer());
transformerRegistry.register(new JsonObjectToNegotiateEdrRequestDtoTransformer());
transformerRegistry.register(new JsonObjectFromEndpointDataReferenceEntryTransformer());
transformerRegistry.register(new EndpointDataReferenceToDataAddressTransformer());
webService.registerResource(apiConfig.getContextAlias(), new EdrController(edrService, jsonLdService, transformerRegistry));
validatorRegistry.register(EDR_REQUEST_DTO_TYPE, NegotiateEdrRequestDtoValidator.instance());
webService.registerResource(apiConfig.getContextAlias(), new EdrController(edrService, transformerRegistry, validatorRegistry, monitor));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

package org.eclipse.tractusx.edc.api.edr;

import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
Expand All @@ -26,50 +27,52 @@
import jakarta.ws.rs.core.MediaType;
import org.eclipse.edc.api.model.IdResponse;
import org.eclipse.edc.connector.api.management.configuration.transform.ManagementApiTypeTransformerRegistry;
import org.eclipse.edc.jsonld.spi.JsonLd;
import org.eclipse.edc.spi.EdcException;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.spi.query.Criterion;
import org.eclipse.edc.spi.query.QuerySpec;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.edc.spi.types.domain.DataAddress;
import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference;
import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry;
import org.eclipse.edc.web.spi.exception.InvalidRequestException;
import org.eclipse.edc.web.spi.exception.ValidationFailureException;
import org.eclipse.tractusx.edc.api.edr.dto.NegotiateEdrRequestDto;
import org.eclipse.tractusx.edc.edr.spi.service.EdrService;
import org.eclipse.tractusx.edc.edr.spi.types.EndpointDataReferenceEntry;
import org.eclipse.tractusx.edc.edr.spi.types.NegotiateEdrRequest;

import java.util.List;
import java.util.stream.Collectors;

import static jakarta.json.stream.JsonCollectors.toJsonArray;
import static org.eclipse.edc.web.spi.exception.ServiceResultHandler.exceptionMapper;
import static org.eclipse.tractusx.edc.api.edr.dto.NegotiateEdrRequestDto.EDR_REQUEST_DTO_TYPE;
import static org.eclipse.tractusx.edc.edr.spi.types.EndpointDataReferenceEntry.AGREEMENT_ID;
import static org.eclipse.tractusx.edc.edr.spi.types.EndpointDataReferenceEntry.ASSET_ID;
import static org.eclipse.tractusx.edc.edr.spi.types.EndpointDataReferenceEntry.PROVIDER_ID;

@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Path("/edrs")
public class EdrController implements EdrApi {

private final EdrService edrService;
private final ManagementApiTypeTransformerRegistry transformerRegistry;
private final JsonLd jsonLdService;

private Monitor monitor;
private final JsonObjectValidatorRegistry validatorRegistry;
private final Monitor monitor;

public EdrController(EdrService edrService, JsonLd jsonLdService, ManagementApiTypeTransformerRegistry transformerRegistry) {
public EdrController(EdrService edrService, ManagementApiTypeTransformerRegistry transformerRegistry,
JsonObjectValidatorRegistry validatorRegistry, Monitor monitor) {
this.edrService = edrService;
this.jsonLdService = jsonLdService;
this.transformerRegistry = transformerRegistry;
this.validatorRegistry = validatorRegistry;
this.monitor = monitor;
}

@POST
@Override
public JsonObject initiateEdrNegotiation(JsonObject requestObject) {
var edrNegotiationRequest = jsonLdService.expand(requestObject)
.compose(expanded -> transformerRegistry.transform(expanded, NegotiateEdrRequestDto.class))
validatorRegistry.validate(EDR_REQUEST_DTO_TYPE, requestObject).orElseThrow(ValidationFailureException::new);

var edrNegotiationRequest = transformerRegistry.transform(requestObject, NegotiateEdrRequestDto.class)
.compose(dto -> transformerRegistry.transform(dto, NegotiateEdrRequest.class))
.orElseThrow(InvalidRequestException::new);

Expand All @@ -81,25 +84,23 @@ public JsonObject initiateEdrNegotiation(JsonObject requestObject) {
.build();

return transformerRegistry.transform(idResponse, JsonObject.class)
.compose(jsonLdService::compact)
.orElseThrow(f -> new EdcException("Error creating response body: " + f.getFailureDetail()));
}

@GET
@Override
public List<JsonObject> queryEdrs(@QueryParam("assetId") String assetId, @QueryParam("agreementId") String agreementId, @QueryParam("providerId") String providerId) {
public JsonArray queryEdrs(@QueryParam("assetId") String assetId, @QueryParam("agreementId") String agreementId, @QueryParam("providerId") String providerId) {
if (assetId == null && agreementId == null) {
throw new InvalidRequestException("At least one of this query parameter is required [assetId,agreementId]");
}
return edrService.findBy(querySpec(assetId, agreementId, providerId))
.orElseThrow(exceptionMapper(EndpointDataReferenceEntry.class))
.stream()
.map(edrCached -> transformerRegistry.transform(edrCached, JsonObject.class)
.compose(jsonLdService::compact))
.map(edrCached -> transformerRegistry.transform(edrCached, JsonObject.class))
.peek(this::logIfError)
.filter(Result::succeeded)
.map(Result::getContent)
.collect(Collectors.toList());
.collect(toJsonArray());
}

@GET
Expand All @@ -109,7 +110,6 @@ public JsonObject getEdr(@PathParam("id") String transferProcessId) {
var edr = edrService.findByTransferProcessId(transferProcessId).orElseThrow(exceptionMapper(EndpointDataReference.class, transferProcessId));
return transformerRegistry.transform(edr, DataAddress.class)
.compose(dataAddress -> transformerRegistry.transform(dataAddress, JsonObject.class))
.compose(jsonLdService::compact)
.orElseThrow(f -> new EdcException("Error creating response body: " + f.getFailureDetail()));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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.tractusx.edc.api.edr.validation;

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

import static org.eclipse.edc.connector.api.management.contractnegotiation.model.ContractOfferDescription.ASSET_ID;
import static org.eclipse.edc.connector.api.management.contractnegotiation.model.ContractOfferDescription.OFFER_ID;
import static org.eclipse.edc.connector.api.management.contractnegotiation.model.ContractOfferDescription.POLICY;
import static org.eclipse.tractusx.edc.api.edr.dto.NegotiateEdrRequestDto.EDR_REQUEST_DTO_CONNECTOR_ADDRESS;
import static org.eclipse.tractusx.edc.api.edr.dto.NegotiateEdrRequestDto.EDR_REQUEST_DTO_OFFER;
import static org.eclipse.tractusx.edc.api.edr.dto.NegotiateEdrRequestDto.EDR_REQUEST_DTO_PROTOCOL;


public class NegotiateEdrRequestDtoValidator {

private NegotiateEdrRequestDtoValidator() {
}

public static Validator<JsonObject> instance() {
return JsonObjectValidator.newValidator()
.verify(EDR_REQUEST_DTO_CONNECTOR_ADDRESS, MandatoryValue::new)
.verify(EDR_REQUEST_DTO_PROTOCOL, MandatoryValue::new)
.verify(EDR_REQUEST_DTO_OFFER, MandatoryObject::new)
.verifyObject(EDR_REQUEST_DTO_OFFER, v -> v
.verify(OFFER_ID, MandatoryValue::new)
.verify(ASSET_ID, MandatoryValue::new)
.verify(POLICY, MandatoryObject::new)
)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

package org.eclipse.tractusx.edc.api.edr;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.restassured.specification.RequestSpecification;
import jakarta.json.Json;
import jakarta.json.JsonObject;
Expand All @@ -23,13 +24,18 @@
import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation;
import org.eclipse.edc.jsonld.TitaniumJsonLd;
import org.eclipse.edc.jsonld.spi.JsonLd;
import org.eclipse.edc.jsonld.util.JacksonJsonLd;
import org.eclipse.edc.junit.annotations.ApiTest;
import org.eclipse.edc.service.spi.result.ServiceResult;
import org.eclipse.edc.spi.query.Criterion;
import org.eclipse.edc.spi.query.QuerySpec;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.edc.spi.types.domain.DataAddress;
import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference;
import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry;
import org.eclipse.edc.validator.spi.ValidationResult;
import org.eclipse.edc.validator.spi.Violation;
import org.eclipse.edc.web.jersey.jsonld.JerseyJsonLdInterceptor;
import org.eclipse.edc.web.jersey.testfixtures.RestControllerTestBase;
import org.eclipse.tractusx.edc.api.edr.dto.NegotiateEdrRequestDto;
import org.eclipse.tractusx.edc.edr.spi.service.EdrService;
Expand Down Expand Up @@ -63,6 +69,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;

@ApiTest
Expand All @@ -72,6 +79,7 @@ public class EdrControllerTest extends RestControllerTestBase {
private final JsonLd jsonLdService = new TitaniumJsonLd(monitor);
EdrService edrService = mock(EdrService.class);
ManagementApiTypeTransformerRegistry transformerRegistry = mock();
JsonObjectValidatorRegistry validatorRegistry = mock();

@BeforeEach
void setup() {
Expand All @@ -81,6 +89,7 @@ void setup() {

@Test
void initEdrNegotiation_shouldWork_whenValidRequest() {
when(validatorRegistry.validate(any(), any())).thenReturn(ValidationResult.success());

var openRequest = openRequest();
var contractNegotiation = getContractNegotiation();
Expand All @@ -105,6 +114,7 @@ void initEdrNegotiation_shouldWork_whenValidRequest() {

@Test
void initEdrNegotiation_shouldReturnBadRequest_whenValidInvalidRequest() {
when(validatorRegistry.validate(any(), any())).thenReturn(ValidationResult.success());

var request = NegotiateEdrRequestDto.Builder.newInstance().build();
when(transformerRegistry.transform(any(JsonObject.class), eq(NegotiateEdrRequestDto.class))).thenReturn(Result.failure("fail"));
Expand All @@ -118,6 +128,23 @@ void initEdrNegotiation_shouldReturnBadRequest_whenValidInvalidRequest() {

}

@Test
void initEdrNegotiation_shouldReturnBadRequest_whenValidationFails() {
when(validatorRegistry.validate(any(), any())).thenReturn(ValidationResult.failure(Violation.violation("failure", "failure path")));
var request = negotiationRequest();

given()
.port(port)
.body(request)
.contentType(MediaType.APPLICATION_JSON)
.post(EDR_PATH)
.then()
.statusCode(400)
.contentType(MediaType.APPLICATION_JSON);

verifyNoInteractions(transformerRegistry);
}

@Test
void initEdrNegotiation_shouldReturnError_whenNotFound() {
var transferProcessId = "id";
Expand Down Expand Up @@ -279,7 +306,13 @@ void queryEdrs_shouldFail_whenNoQueryParameter() {

@Override
protected Object controller() {
return new EdrController(edrService, jsonLdService, transformerRegistry);
return new EdrController(edrService, transformerRegistry, validatorRegistry, monitor);
}

@Override
protected Object additionalResource() {
final ObjectMapper objectMapper = JacksonJsonLd.createObjectMapper();
return new JerseyJsonLdInterceptor(this.jsonLdService, objectMapper);
}

private RequestSpecification baseRequest() {
Expand Down
Loading

0 comments on commit 95ca87a

Please sign in to comment.