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: Remove duplicate information in the initiate negotiation api request #3605

Merged
merged 10 commits into from
Nov 16, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.eclipse.edc.connector.api.management.configuration.ManagementApiSchema;
import org.eclipse.edc.connector.api.management.contractnegotiation.model.ContractOfferDescription;
import org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationState;
import org.eclipse.edc.policy.model.Policy;

import java.util.List;

Expand Down Expand Up @@ -131,7 +132,10 @@ record ContractRequestSchema(
String connectorAddress,
@Schema(requiredMode = REQUIRED)
String providerId,
@Deprecated(since = "0.3.2")
@Schema(deprecated = true, description = "please use policy instead of offer")
ContractOfferDescriptionSchema offer,
Policy policy,
tuncaytunc-zf marked this conversation as resolved.
Show resolved Hide resolved
List<ManagementApiSchema.CallbackAddressSchema> callbackAddresses) {

// policy example took from https://w3c.github.io/odrl/bp/
Expand All @@ -142,18 +146,14 @@ record ContractRequestSchema(
"connectorAddress": "http://provider-address",
"protocol": "dataspace-protocol-http",
"providerId": "provider-id",
"offer": {
"offerId": "offer-id",
"assetId": "asset-id",
"policy": {
"@context": "http://www.w3.org/ns/odrl.jsonld",
"@type": "Set",
"@id": "offer-id",
"permission": [{
"target": "asset-id",
"action": "display"
}]
}
"policy": {
"@context": "http://www.w3.org/ns/odrl.jsonld",
"@type": "Set",
"@id": "policy-id",
"permission": [],
"prohibition": [],
"obligation": [],
"target": "assetId"
tuncaytunc-zf marked this conversation as resolved.
Show resolved Hide resolved
},
"callbackAddresses": [{
"transactional": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,17 +66,17 @@ public String name() {
@Override
public void initialize(ServiceExtensionContext context) {
var factory = Json.createBuilderFactory(Map.of());
transformerRegistry.register(new JsonObjectToContractRequestTransformer());
var monitor = context.getMonitor();

transformerRegistry.register(new JsonObjectToContractRequestTransformer(monitor));
transformerRegistry.register(new JsonObjectToContractOfferDescriptionTransformer());
transformerRegistry.register(new JsonObjectToTerminateNegotiationCommandTransformer());
transformerRegistry.register(new JsonObjectFromContractNegotiationTransformer(factory));
transformerRegistry.register(new JsonObjectFromNegotiationStateTransformer(factory));

validatorRegistry.register(CONTRACT_REQUEST_TYPE, ContractRequestValidator.instance());
validatorRegistry.register(CONTRACT_REQUEST_TYPE, ContractRequestValidator.instance(monitor));
validatorRegistry.register(TERMINATE_NEGOTIATION_TYPE, TerminateNegotiationValidator.instance());

var monitor = context.getMonitor();

var controller = new ContractNegotiationApiController(service, transformerRegistry, monitor, validatorRegistry);
webService.registerResource(config.getContextAlias(), controller);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

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

@Deprecated(since = "0.3.2")
public class ContractOfferDescription {

public static final String CONTRACT_OFFER_DESCRIPTION_TYPE = EDC_NAMESPACE + "ContractOfferDescription";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
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;


@Deprecated(since = "0.3.2")
public class JsonObjectToContractOfferDescriptionTransformer extends AbstractJsonLdTransformer<JsonObject, ContractOfferDescription> {

public JsonObjectToContractOfferDescriptionTransformer() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import org.eclipse.edc.connector.api.management.contractnegotiation.model.ContractOfferDescription;
import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest;
import org.eclipse.edc.jsonld.spi.transformer.AbstractJsonLdTransformer;
import org.eclipse.edc.policy.model.Policy;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.spi.types.domain.callback.CallbackAddress;
import org.eclipse.edc.spi.types.domain.offer.ContractOffer;
import org.eclipse.edc.transform.spi.TransformerContext;
Expand All @@ -29,13 +31,17 @@
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest.CALLBACK_ADDRESSES;
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest.CONNECTOR_ADDRESS;
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest.OFFER;
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest.POLICY;
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest.PROTOCOL;
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest.PROVIDER_ID;

public class JsonObjectToContractRequestTransformer extends AbstractJsonLdTransformer<JsonObject, ContractRequest> {

public JsonObjectToContractRequestTransformer() {
private final Monitor monitor;

public JsonObjectToContractRequestTransformer(Monitor monitor) {
super(JsonObject.class, ContractRequest.class);
this.monitor = monitor;
}

@Override
Expand All @@ -45,13 +51,22 @@ public JsonObjectToContractRequestTransformer() {
.counterPartyAddress(transformString(jsonObject.get(CONNECTOR_ADDRESS), context))
.protocol(transformString(jsonObject.get(PROTOCOL), context));

var contractOfferDescription = transformObject(jsonObject.get(OFFER), ContractOfferDescription.class, context);
var contractOffer = ContractOffer.Builder.newInstance()
.id(contractOfferDescription.getOfferId())
.assetId(contractOfferDescription.getAssetId())
.policy(contractOfferDescription.getPolicy())
.build();
contractRequestBuilder.contractOffer(contractOffer);
var policyJson = jsonObject.get(POLICY);
if (policyJson != null) {
var policy = transformObject(jsonObject.get(POLICY), Policy.class, context);
contractRequestBuilder.policy(policy);
}

var offerJson = jsonObject.get(OFFER);
if (offerJson != null) {
var contractOfferDescription = transformObject(jsonObject.get(OFFER), ContractOfferDescription.class, context);
var contractOffer = ContractOffer.Builder.newInstance()
.id(contractOfferDescription.getOfferId())
.assetId(contractOfferDescription.getAssetId())
.policy(contractOfferDescription.getPolicy())
.build();
contractRequestBuilder.contractOffer(contractOffer);
}

var callbackAddress = jsonObject.get(CALLBACK_ADDRESSES);
if (callbackAddress != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,56 @@
package org.eclipse.edc.connector.api.management.contractnegotiation.validation;

import jakarta.json.JsonObject;
import org.eclipse.edc.connector.api.management.contractnegotiation.model.ContractOfferDescription;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.validator.jsonobject.JsonLdPath;
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.ValidationResult;
import org.eclipse.edc.validator.spi.Validator;
import org.eclipse.edc.validator.spi.Violation;

import static java.lang.String.format;
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.edc.connector.contract.spi.types.negotiation.ContractRequest.CONNECTOR_ADDRESS;
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest.CONTRACT_REQUEST_TYPE;
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest.OFFER;
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest.POLICY;
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest.PROTOCOL;

public class ContractRequestValidator {
public static Validator<JsonObject> instance() {

public static Validator<JsonObject> instance(Monitor monitor) {
return JsonObjectValidator.newValidator()
.verify(CONNECTOR_ADDRESS, MandatoryValue::new)
.verify(PROTOCOL, MandatoryValue::new)
.verify(OFFER, MandatoryObject::new)
.verifyObject(OFFER, v -> v
.verify(OFFER_ID, MandatoryValue::new)
.verify(ASSET_ID, MandatoryValue::new)
.verify(POLICY, MandatoryObject::new)
)
tuncaytunc-zf marked this conversation as resolved.
Show resolved Hide resolved
.verify(path -> new MandatoryOfferOrPolicy(path, monitor))
.build();
}

private record MandatoryOfferOrPolicy(JsonLdPath path, Monitor monitor) implements Validator<JsonObject> {
@Override
public ValidationResult validate(JsonObject input) {
var offerValidity = new MandatoryObject(path.append(OFFER)).validate(input);
if (offerValidity.succeeded()) {
monitor.warning(format("The attribute %s has been deprecated in type %s, please use %s",
OFFER, CONTRACT_REQUEST_TYPE, POLICY));
return JsonObjectValidator.newValidator()
.verifyObject(OFFER, v -> v
.verify(OFFER_ID, MandatoryValue::new)
.verify(ASSET_ID, MandatoryValue::new)
.verify(ContractOfferDescription.POLICY, MandatoryObject::new)
).build().validate(input);
}

var policyValidity = new MandatoryObject(path.append(POLICY)).validate(input);
if (policyValidity.succeeded()) {
return ValidationResult.success();
}

return ValidationResult.failure(Violation.violation(format("'%s' or '%s' must not be empty", OFFER, POLICY), path.toString()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ void getSingleContractNegotiationAgreement_whenNoneFound() {
}

@Test
void initiate() {
void initiate_with_contractOffer() {
when(validatorRegistry.validate(any(), any())).thenReturn(ValidationResult.success());
var contractNegotiation = createContractNegotiation("cn1");
var responseBody = createObjectBuilder().add(TYPE, ID_RESPONSE_TYPE).add(ID, contractNegotiation.getId()).build();
Expand Down Expand Up @@ -363,6 +363,39 @@ void initiate() {
verifyNoMoreInteractions(transformerRegistry, service);
}

@Test
void initiate_with_policy() {
when(validatorRegistry.validate(any(), any())).thenReturn(ValidationResult.success());
var contractNegotiation = createContractNegotiation("cn1");
var responseBody = createObjectBuilder().add(TYPE, ID_RESPONSE_TYPE).add(ID, contractNegotiation.getId()).build();

when(transformerRegistry.transform(any(JsonObject.class), eq(ContractRequest.class))).thenReturn(Result.success(
ContractRequest.Builder.newInstance()
.protocol("test-protocol")
.providerId("test-provider-id")
.counterPartyAddress("test-cb")
.policy(Policy.Builder.newInstance().build())
.build()));

when(transformerRegistry.transform(any(), eq(JsonObject.class))).thenReturn(Result.success(responseBody));
when(service.initiateNegotiation(any(ContractRequest.class))).thenReturn(contractNegotiation);

when(transformerRegistry.transform(any(IdResponse.class), eq(JsonObject.class))).thenReturn(Result.success(responseBody));

baseRequest()
.contentType(JSON)
.body(createObjectBuilder().build())
.post()
.then()
.statusCode(200)
.body(ID, is(contractNegotiation.getId()));

verify(service).initiateNegotiation(any());
verify(transformerRegistry).transform(any(JsonObject.class), eq(ContractRequest.class));
verify(transformerRegistry).transform(any(IdResponse.class), eq(JsonObject.class));
verifyNoMoreInteractions(transformerRegistry, service);
}

@Test
void initiate_shouldReturnBadRequest_whenValidationFails() {
when(validatorRegistry.validate(any(), any())).thenReturn(ValidationResult.failure(violation("error", "path")));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.eclipse.edc.jsonld.JsonLdExtension;
import org.eclipse.edc.jsonld.spi.JsonLd;
import org.eclipse.edc.jsonld.util.JacksonJsonLd;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.transform.spi.TypeTransformerRegistry;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -44,16 +45,18 @@
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VALUE;
import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat;
import static org.eclipse.edc.junit.extensions.TestServiceExtensionContext.testServiceExtensionContext;
import static org.mockito.Mockito.mock;

class ContractNegotiationApiTest {

private final ObjectMapper objectMapper = JacksonJsonLd.createObjectMapper();
private final JsonLd jsonLd = new JsonLdExtension().createJsonLdService(testServiceExtensionContext());
private final TypeTransformerRegistry transformer = new TypeTransformerRegistryImpl();
private final Monitor monitor = mock();

@BeforeEach
void setUp() {
transformer.register(new JsonObjectToContractRequestTransformer());
transformer.register(new JsonObjectToContractRequestTransformer(monitor));
transformer.register(new JsonObjectToContractOfferDescriptionTransformer());
transformer.register(new JsonObjectToCallbackAddressTransformer());
transformer.register(new JsonObjectToTerminateNegotiationCommandTransformer());
Expand All @@ -62,7 +65,7 @@ void setUp() {

@Test
void contractRequestExample() throws JsonProcessingException {
var validator = ContractRequestValidator.instance();
var validator = ContractRequestValidator.instance(monitor);

var jsonObject = objectMapper.readValue(CONTRACT_REQUEST_EXAMPLE, JsonObject.class);
assertThat(jsonObject).isNotNull();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,11 @@ class JsonObjectToContractRequestTransformerTest {
private final JsonLd jsonLd = new TitaniumJsonLd(mock(Monitor.class));
private final TransformerContext context = mock();
private JsonObjectToContractRequestTransformer transformer;
private final Monitor monitor = mock();

@BeforeEach
void setUp() {
transformer = new JsonObjectToContractRequestTransformer();
transformer = new JsonObjectToContractRequestTransformer(monitor);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import jakarta.json.Json;
import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonObject;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.validator.spi.ValidationFailure;
import org.eclipse.edc.validator.spi.Validator;
import org.eclipse.edc.validator.spi.Violation;
Expand All @@ -35,10 +36,12 @@
import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest.PROVIDER_ID;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VALUE;
import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat;
import static org.mockito.Mockito.mock;

class ContractRequestValidatorTest {

private final Validator<JsonObject> validator = ContractRequestValidator.instance();
private final Monitor monitor = mock();
private final Validator<JsonObject> validator = ContractRequestValidator.instance(monitor);

@Test
void shouldSuccess_whenObjectIsValid() {
Expand All @@ -59,20 +62,25 @@ void shouldSuccess_whenObjectIsValid() {
}

@Test
void shouldFail_whenMandatoryPropertiesAreMissing() {
var input = Json.createObjectBuilder().build();
void shouldFail_whenOfferMandatoryPropertiesAreMissing() {
var input = Json.createObjectBuilder()
.add(CONNECTOR_ADDRESS, value("http://connector-address"))
.add(PROTOCOL, value("protocol"))
.add(PROVIDER_ID, value("connector-id"))
.add(OFFER, createArrayBuilder().add(createObjectBuilder()))
.build();

var result = validator.validate(input);

assertThat(result).isFailed().extracting(ValidationFailure::getViolations).asInstanceOf(list(Violation.class))
.isNotEmpty()
.anySatisfy(violation -> assertThat(violation.path()).isEqualTo(CONNECTOR_ADDRESS))
.anySatisfy(violation -> assertThat(violation.path()).isEqualTo(PROTOCOL))
.anySatisfy(violation -> assertThat(violation.path()).isEqualTo(OFFER));
.anySatisfy(violation -> assertThat(violation.path()).isEqualTo(OFFER + "/" + OFFER_ID))
.anySatisfy(violation -> assertThat(violation.path()).isEqualTo(OFFER + "/" + ASSET_ID))
.anySatisfy(violation -> assertThat(violation.path()).isEqualTo(OFFER + "/" + POLICY));
}

@Test
void shouldFail_whenOfferMandatoryPropertiesAreMissing() {
void shouldFail_whenOfferAndPolicyAreMissing() {
var input = Json.createObjectBuilder()
.add(CONNECTOR_ADDRESS, value("http://connector-address"))
.add(PROTOCOL, value("protocol"))
Expand All @@ -84,9 +92,20 @@ void shouldFail_whenOfferMandatoryPropertiesAreMissing() {

assertThat(result).isFailed().extracting(ValidationFailure::getViolations).asInstanceOf(list(Violation.class))
.isNotEmpty()
.anySatisfy(violation -> assertThat(violation.path()).isEqualTo(OFFER + "/" + OFFER_ID))
.anySatisfy(violation -> assertThat(violation.path()).isEqualTo(OFFER + "/" + ASSET_ID))
.anySatisfy(violation -> assertThat(violation.path()).isEqualTo(OFFER + "/" + POLICY));
.anySatisfy(violation -> assertThat(violation.message()).contains(OFFER))
.anySatisfy(violation -> assertThat(violation.message()).contains(POLICY));
}

@Test
void shouldFail_whenMandatoryPropertiesAreMissing() {
var input = Json.createObjectBuilder().build();

var result = validator.validate(input);

assertThat(result).isFailed().extracting(ValidationFailure::getViolations).asInstanceOf(list(Violation.class))
.isNotEmpty()
.anySatisfy(violation -> assertThat(violation.path()).isEqualTo(CONNECTOR_ADDRESS))
.anySatisfy(violation -> assertThat(violation.path()).isEqualTo(PROTOCOL));
}

private JsonArrayBuilder value(String value) {
Expand Down
Loading
Loading