diff --git a/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/contractnegotiation/ContractNegotiationApi.java b/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/contractnegotiation/ContractNegotiationApi.java index 553ac2dda4b..b9cf0627a87 100644 --- a/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/contractnegotiation/ContractNegotiationApi.java +++ b/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/contractnegotiation/ContractNegotiationApi.java @@ -39,6 +39,7 @@ import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_POLICY_TYPE_OFFER; @OpenAPIDefinition @Tag(name = "Contract Negotiation") @@ -50,7 +51,7 @@ public interface ContractNegotiationApi { @ApiResponse(responseCode = "200", description = "The contract negotiations that match the query", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ManagementApiSchema.ContractNegotiationSchema.class)))), @ApiResponse(responseCode = "400", description = "Request was malformed", - content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiCoreSchema.ApiErrorDetailSchema.class)))) } + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiCoreSchema.ApiErrorDetailSchema.class))))} ) JsonArray queryNegotiations(JsonObject querySpecJson); @@ -138,7 +139,8 @@ record ContractRequestSchema( @Deprecated(since = "0.3.2") @Schema(deprecated = true, description = "please use policy instead of offer") ContractOfferDescriptionSchema offer, - ManagementApiSchema.PolicySchema policy, + @Schema(requiredMode = REQUIRED) + OfferSchema policy, List callbackAddresses) { // policy example took from https://w3c.github.io/odrl/bp/ @@ -151,7 +153,7 @@ record ContractRequestSchema( "policy": { "@context": "http://www.w3.org/ns/odrl.jsonld", "@type": "odrl:Offer", - "@id": "policy-id", + "@id": "offer-id", "assigner": "providerId", "permission": [], "prohibition": [], @@ -169,6 +171,31 @@ record ContractRequestSchema( """; } + @Schema(name = "Offer", description = "ODRL offer", example = OfferSchema.OFFER_EXAMPLE) + record OfferSchema( + @Schema(name = TYPE, example = ODRL_POLICY_TYPE_OFFER) + String type, + @Schema(name = ID, requiredMode = REQUIRED) + String id, + @Schema(requiredMode = REQUIRED) + String assigner, + @Schema(requiredMode = REQUIRED) + String target + ) { + public static final String OFFER_EXAMPLE = """ + { + "@context": "http://www.w3.org/ns/odrl.jsonld", + "@type": "odrl:Offer", + "@id": "offer-id", + "assigner": "providerId", + "target": "assetId", + "permission": [], + "prohibition": [], + "obligation": [] + } + """; + } + @Schema(name = "NegotiationState", example = NegotiationStateSchema.NEGOTIATION_STATE_EXAMPLE) record NegotiationStateSchema( @Schema(name = TYPE, example = NegotiationState.NEGOTIATION_STATE_TYPE) diff --git a/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/contractnegotiation/validation/ContractRequestValidator.java b/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/contractnegotiation/validation/ContractRequestValidator.java index 39e5ec0caf6..c275c4f2bd7 100644 --- a/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/contractnegotiation/validation/ContractRequestValidator.java +++ b/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/contractnegotiation/validation/ContractRequestValidator.java @@ -52,6 +52,16 @@ public static Validator instance(Monitor monitor) { .build(); } + public static JsonObjectValidator.Builder offerValidator(JsonObjectValidator.Builder builder) { + return builder + .verifyId(MandatoryIdNotBlank::new) + .verify(path -> new TypeIs(path, ODRL_POLICY_TYPE_OFFER)) + .verify(ODRL_ASSIGNER_ATTRIBUTE, MandatoryObject::new) + .verifyObject(ODRL_ASSIGNER_ATTRIBUTE, b -> b.verifyId(MandatoryIdNotBlank::new)) + .verify(ODRL_TARGET_ATTRIBUTE, MandatoryObject::new) + .verifyObject(ODRL_TARGET_ATTRIBUTE, b -> b.verifyId(MandatoryIdNotBlank::new)); + } + private record MandatoryOfferOrPolicy(JsonLdPath path, Monitor monitor) implements Validator { @Override public ValidationResult validate(JsonObject input) { @@ -65,20 +75,12 @@ public ValidationResult validate(JsonObject input) { ).build().validate(input); } - var validator = JsonObjectValidator.newValidator() + return JsonObjectValidator.newValidator() .verify(POLICY, MandatoryObject::new) - .verifyObject(POLICY, builder -> builder - .verifyId(MandatoryIdNotBlank::new) - .verify(path -> new TypeIs(path, ODRL_POLICY_TYPE_OFFER)) - .verify(ODRL_ASSIGNER_ATTRIBUTE, MandatoryObject::new) - .verifyObject(ODRL_ASSIGNER_ATTRIBUTE, b -> b.verifyId(MandatoryIdNotBlank::new)) - .verify(ODRL_TARGET_ATTRIBUTE, MandatoryObject::new) - .verifyObject(ODRL_TARGET_ATTRIBUTE, b -> b.verifyId(MandatoryIdNotBlank::new)) - ) - .build(); - - return validator.validate(input); + .verifyObject(POLICY, ContractRequestValidator::offerValidator) + .build().validate(input); } + } /** diff --git a/extensions/control-plane/api/management-api/contract-negotiation-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/contractnegotiation/ContractNegotiationApiTest.java b/extensions/control-plane/api/management-api/contract-negotiation-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/contractnegotiation/ContractNegotiationApiTest.java index 926cf640559..2395bc8339c 100644 --- a/extensions/control-plane/api/management-api/contract-negotiation-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/contractnegotiation/ContractNegotiationApiTest.java +++ b/extensions/control-plane/api/management-api/contract-negotiation-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/contractnegotiation/ContractNegotiationApiTest.java @@ -30,9 +30,12 @@ import org.eclipse.edc.jsonld.JsonLdExtension; import org.eclipse.edc.jsonld.spi.JsonLd; import org.eclipse.edc.jsonld.util.JacksonJsonLd; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.spi.agent.ParticipantIdMapper; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.transform.TypeTransformerRegistryImpl; import org.eclipse.edc.transform.spi.TypeTransformerRegistry; +import org.eclipse.edc.validator.jsonobject.JsonObjectValidator; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -46,7 +49,9 @@ 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.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; class ContractNegotiationApiTest { @@ -62,7 +67,9 @@ void setUp() { transformer.register(new JsonObjectToContractOfferDescriptionTransformer()); transformer.register(new JsonObjectToCallbackAddressTransformer()); transformer.register(new JsonObjectToTerminateNegotiationCommandTransformer()); - OdrlTransformersFactory.jsonObjectToOdrlTransformers(mock()).forEach(transformer::register); + ParticipantIdMapper participantIdMapper = mock(); + when(participantIdMapper.fromIri(any())).thenAnswer(a -> a.getArgument(0)); + OdrlTransformersFactory.jsonObjectToOdrlTransformers(participantIdMapper).forEach(transformer::register); } @Test @@ -80,6 +87,21 @@ void contractRequestExample() throws JsonProcessingException { .satisfies(transformed -> assertThat(transformed.getProtocol()).isNotBlank())); } + @Test + void offerExample() throws JsonProcessingException { + var validator = ContractRequestValidator.offerValidator(JsonObjectValidator.newValidator()).build(); + + var jsonObject = objectMapper.readValue(ContractNegotiationApi.OfferSchema.OFFER_EXAMPLE, JsonObject.class); + assertThat(jsonObject).isNotNull(); + + var expanded = jsonLd.expand(jsonObject); + assertThat(expanded).isSucceeded() + .satisfies(exp -> assertThat(validator.validate(exp)).isSucceeded()) + .extracting(e -> transformer.transform(e, Policy.class)) + .satisfies(transformResult -> assertThat(transformResult).isSucceeded() + .satisfies(transformed -> assertThat(transformed.getAssigner()).isNotBlank())); + } + @Test void terminateNegotiationExample() throws JsonProcessingException { var validator = TerminateNegotiationValidator.instance();