diff --git a/core/common/connector-core/src/main/java/org/eclipse/edc/connector/core/transform/TransformerContextImpl.java b/core/common/connector-core/src/main/java/org/eclipse/edc/connector/core/transform/TransformerContextImpl.java index 28958e4daf9..f98fba45fb8 100644 --- a/core/common/connector-core/src/main/java/org/eclipse/edc/connector/core/transform/TransformerContextImpl.java +++ b/core/common/connector-core/src/main/java/org/eclipse/edc/connector/core/transform/TransformerContextImpl.java @@ -14,6 +14,7 @@ package org.eclipse.edc.connector.core.transform; +import org.eclipse.edc.transform.spi.ProblemBuilder; import org.eclipse.edc.transform.spi.TransformerContext; import org.eclipse.edc.transform.spi.TypeTransformerRegistry; import org.jetbrains.annotations.Nullable; @@ -44,6 +45,11 @@ public void reportProblem(String problem) { problems.add(problem); } + @Override + public ProblemBuilder problem() { + return new ProblemBuilder(this); + } + @Override public @Nullable OUTPUT transform(INPUT object, Class outputType) { if (object == null) { diff --git a/data-protocols/dsp/dsp-catalog/dsp-catalog-transform/src/main/java/org/eclipse/edc/protocol/dsp/catalog/transform/to/JsonObjectToCatalogRequestMessageTransformer.java b/data-protocols/dsp/dsp-catalog/dsp-catalog-transform/src/main/java/org/eclipse/edc/protocol/dsp/catalog/transform/to/JsonObjectToCatalogRequestMessageTransformer.java index a495b5ab1a7..3ef39a99aee 100644 --- a/data-protocols/dsp/dsp-catalog/dsp-catalog-transform/src/main/java/org/eclipse/edc/protocol/dsp/catalog/transform/to/JsonObjectToCatalogRequestMessageTransformer.java +++ b/data-protocols/dsp/dsp-catalog/dsp-catalog-transform/src/main/java/org/eclipse/edc/protocol/dsp/catalog/transform/to/JsonObjectToCatalogRequestMessageTransformer.java @@ -17,7 +17,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.json.JsonArray; import jakarta.json.JsonObject; -import jakarta.json.JsonValue; import org.eclipse.edc.catalog.spi.CatalogRequestMessage; import org.eclipse.edc.jsonld.spi.transformer.AbstractJsonLdTransformer; import org.eclipse.edc.spi.query.QuerySpec; @@ -25,7 +24,9 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import static java.lang.String.format; +import static jakarta.json.JsonValue.ValueType.ARRAY; +import static jakarta.json.JsonValue.ValueType.OBJECT; +import static org.eclipse.edc.protocol.dsp.catalog.transform.DspCatalogPropertyAndTypeNames.DSPACE_CATALOG_REQUEST_TYPE; import static org.eclipse.edc.protocol.dsp.catalog.transform.DspCatalogPropertyAndTypeNames.DSPACE_FILTER_PROPERTY; /** @@ -44,21 +45,35 @@ public JsonObjectToCatalogRequestMessageTransformer(ObjectMapper mapper) { public @Nullable CatalogRequestMessage transform(@NotNull JsonObject object, @NotNull TransformerContext context) { var builder = CatalogRequestMessage.Builder.newInstance(); - if (object.get(DSPACE_FILTER_PROPERTY) != null) { - builder.querySpec(transformQuerySpec(object.get(DSPACE_FILTER_PROPERTY), context)); + var querySpec = transformQuerySpec(object, context); + if (querySpec != null) { + builder.querySpec(querySpec); } return builder.build(); } - private QuerySpec transformQuerySpec(JsonValue value, TransformerContext context) { + @Nullable + private QuerySpec transformQuerySpec(JsonObject object, TransformerContext context) { + var value = object.get(DSPACE_FILTER_PROPERTY); + if (value == null) { + return null; + } + if (value instanceof JsonObject) { return mapper.convertValue(value, QuerySpec.class); } else if (value instanceof JsonArray) { var array = (JsonArray) value; return transformQuerySpec(array.getJsonObject(0), context); } else { - context.reportProblem(format("Expected filter to be JsonObject or JsonArray, but was %s", value.getClass().getSimpleName())); + context.problem() + .unexpectedType() + .type(DSPACE_CATALOG_REQUEST_TYPE) + .property(DSPACE_FILTER_PROPERTY) + .actual(value.getValueType()) + .expected(OBJECT) + .expected(ARRAY) + .report(); return null; } } diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/from/JsonObjectFromContractAgreementMessageTransformer.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/from/JsonObjectFromContractAgreementMessageTransformer.java index f1115ecfccb..ac1ce0e00a1 100644 --- a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/from/JsonObjectFromContractAgreementMessageTransformer.java +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/from/JsonObjectFromContractAgreementMessageTransformer.java @@ -58,7 +58,11 @@ public JsonObjectFromContractAgreementMessageTransformer(JsonBuilderFactory json var policy = context.transform(agreement.getPolicy(), JsonObject.class); if (policy == null) { - context.reportProblem("Cannot transform from ContractAgreementMessage with null policy"); + context.problem() + .nullProperty() + .type(ContractAgreementMessage.class) + .property("policy") + .report(); return null; } diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/from/JsonObjectFromContractNegotiationTransformer.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/from/JsonObjectFromContractNegotiationTransformer.java index de72c3487c3..3c52f84cb62 100644 --- a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/from/JsonObjectFromContractNegotiationTransformer.java +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/from/JsonObjectFromContractNegotiationTransformer.java @@ -60,7 +60,16 @@ public JsonObjectFromContractNegotiationTransformer(JsonBuilderFactory jsonFacto } private String state(Integer state, TransformerContext context) { - switch (ContractNegotiationStates.from(state)) { + var negotiationState = ContractNegotiationStates.from(state); + if (negotiationState == null) { + context.problem() + .nullProperty() + .type(ContractNegotiation.class) + .property(DSPACE_NEGOTIATION_PROPERTY_STATE) + .report(); + return null; + } + switch (negotiationState) { case REQUESTING: case REQUESTED: return DSPACE_NEGOTIATION_STATE_REQUESTED; @@ -83,7 +92,13 @@ private String state(Integer state, TransformerContext context) { case TERMINATED: return DSPACE_NEGOTIATION_STATE_TERMINATED; default: - context.reportProblem(String.format("Could not map state %s in ContractNegotiation", state)); + context.problem() + .unexpectedType() + .type(ContractNegotiation.class) + .property("state") + .actual(negotiationState.toString()) + .expected(ContractNegotiationStates.class) + .report(); return null; } } diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/from/JsonObjectFromContractRequestMessageTransformer.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/from/JsonObjectFromContractRequestMessageTransformer.java index c16d7647607..53c10ee9bc1 100644 --- a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/from/JsonObjectFromContractRequestMessageTransformer.java +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/from/JsonObjectFromContractRequestMessageTransformer.java @@ -61,7 +61,11 @@ public JsonObjectFromContractRequestMessageTransformer(JsonBuilderFactory jsonFa builder.add(DSPACE_NEGOTIATION_PROPERTY_DATASET, requestMessage.getContractOffer().getAssetId()); var policy = context.transform(requestMessage.getContractOffer().getPolicy(), JsonObject.class); if (policy == null) { - context.reportProblem("Cannot transform from ContractRequestMessage policy"); + context.problem() + .nullProperty() + .type(ContractRequestMessage.class) + .property("contractOffer") + .report(); return null; } diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/to/JsonObjectToContractAgreementMessageTransformer.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/to/JsonObjectToContractAgreementMessageTransformer.java index 3902c7dabcb..84fe6620c56 100644 --- a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/to/JsonObjectToContractAgreementMessageTransformer.java +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/to/JsonObjectToContractAgreementMessageTransformer.java @@ -28,7 +28,8 @@ import java.time.format.DateTimeParseException; import java.util.Set; -import static java.lang.String.format; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.protocol.dsp.negotiation.transform.DspNegotiationPropertyAndTypeNames.DSPACE_NEGOTIATION_AGREEMENT_MESSAGE; import static org.eclipse.edc.protocol.dsp.negotiation.transform.DspNegotiationPropertyAndTypeNames.DSPACE_NEGOTIATION_PROPERTY_AGREEMENT; import static org.eclipse.edc.protocol.dsp.negotiation.transform.DspNegotiationPropertyAndTypeNames.DSPACE_NEGOTIATION_PROPERTY_CONSUMER_ID; import static org.eclipse.edc.protocol.dsp.negotiation.transform.DspNegotiationPropertyAndTypeNames.DSPACE_NEGOTIATION_PROPERTY_PROCESS_ID; @@ -50,7 +51,11 @@ public JsonObjectToContractAgreementMessageTransformer() { public @Nullable ContractAgreementMessage transform(@NotNull JsonObject object, @NotNull TransformerContext context) { var messageBuilder = ContractAgreementMessage.Builder.newInstance(); if (!transformMandatoryString(object.get(DSPACE_NEGOTIATION_PROPERTY_PROCESS_ID), messageBuilder::processId, context)) { - context.reportProblem(format("No '%s' specified on ContractAgreementMessage", DSPACE_NEGOTIATION_PROPERTY_PROCESS_ID)); + context.problem() + .missingProperty() + .type(DSPACE_NEGOTIATION_AGREEMENT_MESSAGE) + .property(DSPACE_NEGOTIATION_PROPERTY_PROCESS_ID) + .report(); return null; } @@ -63,13 +68,17 @@ public JsonObjectToContractAgreementMessageTransformer() { var policy = context.transform(filteredJsonAgreement, Policy.class); if (policy == null) { - context.reportProblem("Cannot transform to ContractAgreementMessage with invalid policy"); + context.problem() + .invalidProperty() + .type(DSPACE_NEGOTIATION_AGREEMENT_MESSAGE) + .property(DSPACE_NEGOTIATION_PROPERTY_AGREEMENT) + .report(); return null; } var agreement = contractAgreement(jsonAgreement, policy, context); if (agreement == null) { - context.reportProblem("Cannot transform to ContractAgreementMessage with null agreement"); + // problem already reported return null; } @@ -91,18 +100,30 @@ private ContractAgreement contractAgreement(JsonObject jsonAgreement, Policy pol var builder = ContractAgreement.Builder.newInstance(); var agreementId = nodeId(jsonAgreement); if (agreementId == null) { - context.reportProblem("No id specified on ContractAgreement"); + context.problem() + .missingProperty() + .type(DSPACE_NEGOTIATION_AGREEMENT_MESSAGE) + .property(ID) + .report(); return null; } builder.id(agreementId); if (!transformMandatoryString(jsonAgreement.get(DSPACE_NEGOTIATION_PROPERTY_CONSUMER_ID), builder::consumerId, context)) { - context.reportProblem(format("No '%s' specified on ContractAgreement", DSPACE_NEGOTIATION_PROPERTY_CONSUMER_ID)); + context.problem() + .missingProperty() + .type(DSPACE_NEGOTIATION_AGREEMENT_MESSAGE) + .property(DSPACE_NEGOTIATION_PROPERTY_CONSUMER_ID) + .report(); return null; } if (!transformMandatoryString(jsonAgreement.get(DSPACE_NEGOTIATION_PROPERTY_PROVIDER_ID), builder::providerId, context)) { - context.reportProblem(format("No '%s' specified on ContractAgreement", DSPACE_NEGOTIATION_PROPERTY_PROVIDER_ID)); + context.problem() + .missingProperty() + .type(DSPACE_NEGOTIATION_AGREEMENT_MESSAGE) + .property(DSPACE_NEGOTIATION_PROPERTY_PROVIDER_ID) + .report(); return null; } @@ -111,13 +132,23 @@ private ContractAgreement contractAgreement(JsonObject jsonAgreement, Policy pol var timestamp = transformString(jsonAgreement.get(DSPACE_NEGOTIATION_PROPERTY_TIMESTAMP), context); if (timestamp == null) { - context.reportProblem(format("No '%s' specified on ContractAgreement", DSPACE_NEGOTIATION_PROPERTY_TIMESTAMP)); + context.problem() + .missingProperty() + .type(DSPACE_NEGOTIATION_AGREEMENT_MESSAGE) + .property(DSPACE_NEGOTIATION_PROPERTY_TIMESTAMP) + .report(); return null; } try { builder.contractSigningDate(Instant.parse(timestamp).getEpochSecond()); } catch (DateTimeParseException e) { - context.reportProblem(format("Invalid '%s' specified on ContractAgreement: %s", DSPACE_NEGOTIATION_PROPERTY_TIMESTAMP, e.getMessage())); + context.problem() + .invalidProperty() + .type(DSPACE_NEGOTIATION_AGREEMENT_MESSAGE) + .property(DSPACE_NEGOTIATION_PROPERTY_TIMESTAMP) + .value(timestamp) + .error(e.getMessage()) + .report(); return null; } diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/to/JsonObjectToContractAgreementVerificationMessageTransformer.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/to/JsonObjectToContractAgreementVerificationMessageTransformer.java index 43816d1c3ad..a7cea69adb1 100644 --- a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/to/JsonObjectToContractAgreementVerificationMessageTransformer.java +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/to/JsonObjectToContractAgreementVerificationMessageTransformer.java @@ -21,7 +21,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import static java.lang.String.format; +import static org.eclipse.edc.protocol.dsp.negotiation.transform.DspNegotiationPropertyAndTypeNames.DSPACE_NEGOTIATION_AGREEMENT_VERIFICATION_MESSAGE; import static org.eclipse.edc.protocol.dsp.negotiation.transform.DspNegotiationPropertyAndTypeNames.DSPACE_NEGOTIATION_PROPERTY_PROCESS_ID; /** @@ -37,7 +37,11 @@ public JsonObjectToContractAgreementVerificationMessageTransformer() { public @Nullable ContractAgreementVerificationMessage transform(@NotNull JsonObject object, @NotNull TransformerContext context) { var builder = ContractAgreementVerificationMessage.Builder.newInstance(); if (!transformMandatoryString(object.get(DSPACE_NEGOTIATION_PROPERTY_PROCESS_ID), builder::processId, context)) { - context.reportProblem(format("ContractAgreementVerificationMessage is missing the '%s' property", DSPACE_NEGOTIATION_PROPERTY_PROCESS_ID)); + context.problem() + .missingProperty() + .type(DSPACE_NEGOTIATION_AGREEMENT_VERIFICATION_MESSAGE) + .property(DSPACE_NEGOTIATION_PROPERTY_PROCESS_ID) + .report(); return null; } diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/to/JsonObjectToContractNegotiationEventMessageTransformer.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/to/JsonObjectToContractNegotiationEventMessageTransformer.java index e915d5b4909..8b4b714108e 100644 --- a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/to/JsonObjectToContractNegotiationEventMessageTransformer.java +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/to/JsonObjectToContractNegotiationEventMessageTransformer.java @@ -21,9 +21,9 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import static java.lang.String.format; import static org.eclipse.edc.connector.contract.spi.types.agreement.ContractNegotiationEventMessage.Type.ACCEPTED; import static org.eclipse.edc.connector.contract.spi.types.agreement.ContractNegotiationEventMessage.Type.FINALIZED; +import static org.eclipse.edc.protocol.dsp.negotiation.transform.DspNegotiationPropertyAndTypeNames.DSPACE_NEGOTIATION_EVENT_MESSAGE; import static org.eclipse.edc.protocol.dsp.negotiation.transform.DspNegotiationPropertyAndTypeNames.DSPACE_NEGOTIATION_PROPERTY_EVENT_TYPE; import static org.eclipse.edc.protocol.dsp.negotiation.transform.DspNegotiationPropertyAndTypeNames.DSPACE_NEGOTIATION_PROPERTY_EVENT_TYPE_ACCEPTED; import static org.eclipse.edc.protocol.dsp.negotiation.transform.DspNegotiationPropertyAndTypeNames.DSPACE_NEGOTIATION_PROPERTY_EVENT_TYPE_FINALIZED; @@ -44,7 +44,11 @@ public JsonObjectToContractNegotiationEventMessageTransformer() { var builder = ContractNegotiationEventMessage.Builder.newInstance(); if (!transformMandatoryString(object.get(DSPACE_NEGOTIATION_PROPERTY_PROCESS_ID), builder::processId, context)) { - context.reportProblem(format("ContractNegotiationEventMessage is missing the %s property", DSPACE_NEGOTIATION_PROPERTY_PROCESS_ID)); + context.problem() + .missingProperty() + .type(DSPACE_NEGOTIATION_EVENT_MESSAGE) + .property(DSPACE_NEGOTIATION_PROPERTY_PROCESS_ID) + .report(); return null; } @@ -54,7 +58,13 @@ public JsonObjectToContractNegotiationEventMessageTransformer() { } else if (DSPACE_NEGOTIATION_PROPERTY_EVENT_TYPE_FINALIZED.equals(eventType)) { builder.type(FINALIZED); } else { - context.reportProblem(format("Could not map '%s' in ContractNegotiationEventMessage: %s", DSPACE_NEGOTIATION_PROPERTY_EVENT_TYPE, eventType)); + context.problem() + .unexpectedType() + .type(DSPACE_NEGOTIATION_EVENT_MESSAGE) + .property(DSPACE_NEGOTIATION_PROPERTY_EVENT_TYPE) + .expected(DSPACE_NEGOTIATION_PROPERTY_EVENT_TYPE_ACCEPTED) + .expected(DSPACE_NEGOTIATION_PROPERTY_EVENT_TYPE_FINALIZED) + .report(); return null; } diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/to/JsonObjectToContractNegotiationTerminationMessageTransformer.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/to/JsonObjectToContractNegotiationTerminationMessageTransformer.java index 89f798decdc..1d829cfaba7 100644 --- a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/to/JsonObjectToContractNegotiationTerminationMessageTransformer.java +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/to/JsonObjectToContractNegotiationTerminationMessageTransformer.java @@ -14,6 +14,7 @@ package org.eclipse.edc.protocol.dsp.negotiation.transform.to; +import jakarta.json.JsonArray; import jakarta.json.JsonObject; import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationTerminationMessage; import org.eclipse.edc.jsonld.spi.transformer.AbstractJsonLdTransformer; @@ -21,10 +22,11 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import static java.lang.String.format; +import static jakarta.json.JsonValue.ValueType.ARRAY; import static org.eclipse.edc.protocol.dsp.negotiation.transform.DspNegotiationPropertyAndTypeNames.DSPACE_NEGOTIATION_PROPERTY_CODE; import static org.eclipse.edc.protocol.dsp.negotiation.transform.DspNegotiationPropertyAndTypeNames.DSPACE_NEGOTIATION_PROPERTY_PROCESS_ID; import static org.eclipse.edc.protocol.dsp.negotiation.transform.DspNegotiationPropertyAndTypeNames.DSPACE_NEGOTIATION_PROPERTY_REASON; +import static org.eclipse.edc.protocol.dsp.negotiation.transform.DspNegotiationPropertyAndTypeNames.DSPACE_NEGOTIATION_TERMINATION_MESSAGE; /** * Creates a {@link ContractNegotiationTerminationMessage} from a {@link JsonObject}. @@ -39,7 +41,9 @@ public JsonObjectToContractNegotiationTerminationMessageTransformer() { public @Nullable ContractNegotiationTerminationMessage transform(@NotNull JsonObject object, @NotNull TransformerContext context) { var builder = ContractNegotiationTerminationMessage.Builder.newInstance(); - transformString(object.get(DSPACE_NEGOTIATION_PROPERTY_PROCESS_ID), builder::processId, context); + if (!transformMandatoryString(object.get(DSPACE_NEGOTIATION_PROPERTY_PROCESS_ID), builder::processId, context)) { + return null; + } var code = object.get(DSPACE_NEGOTIATION_PROPERTY_CODE); if (code != null) { // optional property @@ -48,13 +52,19 @@ public JsonObjectToContractNegotiationTerminationMessageTransformer() { var reasons = object.get(DSPACE_NEGOTIATION_PROPERTY_REASON); if (reasons != null) { // optional property - var result = typeValueArray(reasons, context); - if (result == null) { - context.reportProblem(format("Cannot transform property %s in ContractNegotiationTerminationMessage", DSPACE_NEGOTIATION_PROPERTY_REASON)); - } else { - if (result.size() > 0) { - builder.rejectionReason(result.toString()); + if (reasons instanceof JsonArray) { + var array = (JsonArray) reasons; + if (array.size() > 0) { + builder.rejectionReason(array.toString()); } + } else { + context.problem() + .unexpectedType() + .type(DSPACE_NEGOTIATION_TERMINATION_MESSAGE) + .property(DSPACE_NEGOTIATION_PROPERTY_REASON) + .actual(reasons.getValueType().toString()) + .expected(ARRAY) + .report(); } } diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/to/JsonObjectToContractRequestMessageTransformer.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/to/JsonObjectToContractRequestMessageTransformer.java index 4c6b894d65f..f93dc1c014f 100644 --- a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/to/JsonObjectToContractRequestMessageTransformer.java +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/main/java/org.eclipse.edc.protocol.dsp.negotiation.transform/to/JsonObjectToContractRequestMessageTransformer.java @@ -23,7 +23,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import static java.lang.String.format; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.protocol.dsp.negotiation.transform.DspNegotiationPropertyAndTypeNames.DSPACE_NEGOTIATION_CONTRACT_REQUEST_MESSAGE; import static org.eclipse.edc.protocol.dsp.negotiation.transform.DspNegotiationPropertyAndTypeNames.DSPACE_NEGOTIATION_PROPERTY_CALLBACK_ADDRESS; import static org.eclipse.edc.protocol.dsp.negotiation.transform.DspNegotiationPropertyAndTypeNames.DSPACE_NEGOTIATION_PROPERTY_DATASET; import static org.eclipse.edc.protocol.dsp.negotiation.transform.DspNegotiationPropertyAndTypeNames.DSPACE_NEGOTIATION_PROPERTY_OFFER; @@ -43,7 +44,11 @@ public JsonObjectToContractRequestMessageTransformer() { public @Nullable ContractRequestMessage transform(@NotNull JsonObject requestObject, @NotNull TransformerContext context) { var builder = ContractRequestMessage.Builder.newInstance(); if (!transformMandatoryString(requestObject.get(DSPACE_NEGOTIATION_PROPERTY_PROCESS_ID), builder::processId, context)) { - context.reportProblem(format("No '%s' specified on ContractRequestMessage", DSPACE_NEGOTIATION_PROPERTY_PROCESS_ID)); + context.problem() + .missingProperty() + .type(DSPACE_NEGOTIATION_CONTRACT_REQUEST_MESSAGE) + .property(DSPACE_NEGOTIATION_PROPERTY_PROCESS_ID) + .report(); return null; } @@ -61,12 +66,20 @@ public JsonObjectToContractRequestMessageTransformer() { if (contractOffer != null) { var policy = transformObject(contractOffer, Policy.class, context); if (policy == null) { - context.reportProblem("Cannot transform to ContractRequestMessage with null policy"); + context.problem() + .missingProperty() + .type(DSPACE_NEGOTIATION_CONTRACT_REQUEST_MESSAGE) + .property(DSPACE_NEGOTIATION_PROPERTY_OFFER) + .report(); return null; } var id = nodeId(contractOffer); if (id == null) { - context.reportProblem(format("@id must be specified when including a '%s' in a ContractRequestMessage", DSPACE_NEGOTIATION_PROPERTY_OFFER)); + context.problem() + .missingProperty() + .type(DSPACE_NEGOTIATION_PROPERTY_OFFER) + .property(ID) + .report(); return null; } var offer = ContractOffer.Builder.newInstance().id(id).assetId(policy.getTarget()).policy(policy).build(); @@ -75,7 +88,11 @@ public JsonObjectToContractRequestMessageTransformer() { return null; } else { if (!transformMandatoryString(requestObject.get(DSPACE_NEGOTIATION_PROPERTY_OFFER_ID), builder::contractOfferId, context)) { - context.reportProblem("ContractRequestMessage must specify a contract offer or contract offer id"); + context.problem() + .missingProperty() + .type(DSPACE_NEGOTIATION_CONTRACT_REQUEST_MESSAGE) + .property(DSPACE_NEGOTIATION_PROPERTY_OFFER) + .report(); return null; } } diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/from/JsonObjectFromContractAgreementMessageTransformerTest.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/from/JsonObjectFromContractAgreementMessageTransformerTest.java index a882e0b3594..bc9e6b4a019 100644 --- a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/from/JsonObjectFromContractAgreementMessageTransformerTest.java +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/from/JsonObjectFromContractAgreementMessageTransformerTest.java @@ -24,6 +24,7 @@ import org.eclipse.edc.policy.model.Permission; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.policy.model.Prohibition; +import org.eclipse.edc.transform.spi.ProblemBuilder; import org.eclipse.edc.transform.spi.TransformerContext; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -65,6 +66,7 @@ class JsonObjectFromContractAgreementMessageTransformerTest { @BeforeEach void setUp() { transformer = new JsonObjectFromContractAgreementMessageTransformer(jsonFactory); + when(context.problem()).thenReturn(new ProblemBuilder(context)); } @Test diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/from/JsonObjectFromContractRequestMessageTransformerTest.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/from/JsonObjectFromContractRequestMessageTransformerTest.java index 93020eaa7a2..1b1e2715b19 100644 --- a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/from/JsonObjectFromContractRequestMessageTransformerTest.java +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/from/JsonObjectFromContractRequestMessageTransformerTest.java @@ -20,6 +20,7 @@ import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequestMessage; import org.eclipse.edc.connector.contract.spi.types.offer.ContractOffer; import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.transform.spi.ProblemBuilder; import org.eclipse.edc.transform.spi.TransformerContext; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -60,6 +61,7 @@ class JsonObjectFromContractRequestMessageTransformerTest { @BeforeEach void setUp() { transformer = new JsonObjectFromContractRequestMessageTransformer(jsonFactory); + when(context.problem()).thenReturn(new ProblemBuilder(context)); } @Test diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/to/JsonObjectToContractAgreementMessageTransformerTest.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/to/JsonObjectToContractAgreementMessageTransformerTest.java index 7b3fdca8fd9..7247925c85a 100644 --- a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/to/JsonObjectToContractAgreementMessageTransformerTest.java +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/to/JsonObjectToContractAgreementMessageTransformerTest.java @@ -25,6 +25,7 @@ import org.eclipse.edc.policy.model.Permission; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.policy.model.Prohibition; +import org.eclipse.edc.transform.spi.ProblemBuilder; import org.eclipse.edc.transform.spi.TransformerContext; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -68,6 +69,7 @@ class JsonObjectToContractAgreementMessageTransformerTest { @BeforeEach void setUp() { transformer = new JsonObjectToContractAgreementMessageTransformer(); + when(context.problem()).thenReturn(new ProblemBuilder(context)); } @Test diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/to/JsonObjectToContractAgreementVerificationMessageTransformerTest.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/to/JsonObjectToContractAgreementVerificationMessageTransformerTest.java index 3ae8c39f94a..dd414da41ff 100644 --- a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/to/JsonObjectToContractAgreementVerificationMessageTransformerTest.java +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/to/JsonObjectToContractAgreementVerificationMessageTransformerTest.java @@ -18,6 +18,7 @@ import jakarta.json.JsonBuilderFactory; import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreementVerificationMessage; import org.eclipse.edc.jsonld.spi.JsonLdKeywords; +import org.eclipse.edc.transform.spi.ProblemBuilder; import org.eclipse.edc.transform.spi.TransformerContext; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -34,6 +35,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; class JsonObjectToContractAgreementVerificationMessageTransformerTest { private static final String PROCESS_ID = "processId"; @@ -46,6 +48,7 @@ class JsonObjectToContractAgreementVerificationMessageTransformerTest { @BeforeEach void setUp() { transformer = new JsonObjectToContractAgreementVerificationMessageTransformer(); + when(context.problem()).thenReturn(new ProblemBuilder(context)); } @Test diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/to/JsonObjectToContractNegotiationEventMessageTransformerTest.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/to/JsonObjectToContractNegotiationEventMessageTransformerTest.java index 820212f1321..df8c443374d 100644 --- a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/to/JsonObjectToContractNegotiationEventMessageTransformerTest.java +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/to/JsonObjectToContractNegotiationEventMessageTransformerTest.java @@ -18,6 +18,7 @@ import jakarta.json.JsonBuilderFactory; import org.eclipse.edc.connector.contract.spi.types.agreement.ContractNegotiationEventMessage; import org.eclipse.edc.jsonld.spi.JsonLdKeywords; +import org.eclipse.edc.transform.spi.ProblemBuilder; import org.eclipse.edc.transform.spi.TransformerContext; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -36,6 +37,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; class JsonObjectToContractNegotiationEventMessageTransformerTest { private static final String PROCESS_ID = "processId"; @@ -48,6 +50,7 @@ class JsonObjectToContractNegotiationEventMessageTransformerTest { @BeforeEach void setUp() { transformer = new JsonObjectToContractNegotiationEventMessageTransformer(); + when(context.problem()).thenReturn(new ProblemBuilder(context)); } @Test diff --git a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/to/JsonObjectToContractRequestMessageTransformerTest.java b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/to/JsonObjectToContractRequestMessageTransformerTest.java index 95f8615aec3..a331220698f 100644 --- a/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/to/JsonObjectToContractRequestMessageTransformerTest.java +++ b/data-protocols/dsp/dsp-negotiation/dsp-negotiation-transform/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/transform/to/JsonObjectToContractRequestMessageTransformerTest.java @@ -23,6 +23,7 @@ import org.eclipse.edc.policy.model.Permission; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.policy.model.Prohibition; +import org.eclipse.edc.transform.spi.ProblemBuilder; import org.eclipse.edc.transform.spi.TransformerContext; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -62,6 +63,7 @@ class JsonObjectToContractRequestMessageTransformerTest { @BeforeEach void setUp() { transformer = new JsonObjectToContractRequestMessageTransformer(); + when(context.problem()).thenReturn(new ProblemBuilder(context)); } @Test diff --git a/data-protocols/dsp/dsp-transfer-process/dsp-transfer-process-transform/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/transformer/type/to/JsonObjectToTransferCompletionMessageTransformer.java b/data-protocols/dsp/dsp-transfer-process/dsp-transfer-process-transform/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/transformer/type/to/JsonObjectToTransferCompletionMessageTransformer.java index 73e838c2a37..baf729f7913 100644 --- a/data-protocols/dsp/dsp-transfer-process/dsp-transfer-process-transform/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/transformer/type/to/JsonObjectToTransferCompletionMessageTransformer.java +++ b/data-protocols/dsp/dsp-transfer-process/dsp-transfer-process-transform/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/transformer/type/to/JsonObjectToTransferCompletionMessageTransformer.java @@ -22,6 +22,7 @@ import org.jetbrains.annotations.Nullable; import static org.eclipse.edc.protocol.dsp.transferprocess.transformer.DspTransferProcessPropertyAndTypeNames.DSPACE_PROCESS_ID; +import static org.eclipse.edc.protocol.dsp.transferprocess.transformer.DspTransferProcessPropertyAndTypeNames.DSPACE_TRANSFER_COMPLETION_TYPE; public class JsonObjectToTransferCompletionMessageTransformer extends AbstractJsonLdTransformer { @@ -33,7 +34,14 @@ public JsonObjectToTransferCompletionMessageTransformer() { public @Nullable TransferCompletionMessage transform(@NotNull JsonObject messageObject, @NotNull TransformerContext context) { var transferCompletionMessageBuilder = TransferCompletionMessage.Builder.newInstance(); - transformString(messageObject.get(DSPACE_PROCESS_ID), transferCompletionMessageBuilder::processId, context); + if (!transformMandatoryString(messageObject.get(DSPACE_PROCESS_ID), transferCompletionMessageBuilder::processId, context)) { + context.problem() + .missingProperty() + .type(DSPACE_TRANSFER_COMPLETION_TYPE) + .property(DSPACE_PROCESS_ID) + .report(); + return null; + } return transferCompletionMessageBuilder.build(); diff --git a/data-protocols/dsp/dsp-transfer-process/dsp-transfer-process-transform/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/transformer/type/to/JsonObjectToTransferRequestMessageTransformer.java b/data-protocols/dsp/dsp-transfer-process/dsp-transfer-process-transform/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/transformer/type/to/JsonObjectToTransferRequestMessageTransformer.java index b34ecf66790..3b2c00692fe 100644 --- a/data-protocols/dsp/dsp-transfer-process/dsp-transfer-process-transform/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/transformer/type/to/JsonObjectToTransferRequestMessageTransformer.java +++ b/data-protocols/dsp/dsp-transfer-process/dsp-transfer-process-transform/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/transformer/type/to/JsonObjectToTransferRequestMessageTransformer.java @@ -40,10 +40,14 @@ public JsonObjectToTransferRequestMessageTransformer() { visitProperties(messageObject, k -> { switch (k) { - case DSPACE_PROCESS_ID: return v -> transferRequestMessageBuilder.processId(transformString(v, context)); - case DSPACE_CONTRACT_AGREEMENT_ID: return v -> transferRequestMessageBuilder.contractId(transformString(v, context)); - case DSPACE_CALLBACK_ADDRESS: return v -> transferRequestMessageBuilder.callbackAddress(transformString(v, context)); - default: return doNothing(); + case DSPACE_PROCESS_ID: + return v -> transferRequestMessageBuilder.processId(transformString(v, context)); + case DSPACE_CONTRACT_AGREEMENT_ID: + return v -> transferRequestMessageBuilder.contractId(transformString(v, context)); + case DSPACE_CALLBACK_ADDRESS: + return v -> transferRequestMessageBuilder.callbackAddress(transformString(v, context)); + default: + return doNothing(); } }); diff --git a/data-protocols/dsp/dsp-transfer-process/dsp-transfer-process-transform/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/transformer/type/to/JsonObjectToTransferStartMessageTransformer.java b/data-protocols/dsp/dsp-transfer-process/dsp-transfer-process-transform/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/transformer/type/to/JsonObjectToTransferStartMessageTransformer.java index 1647b15e6ac..49a2d653065 100644 --- a/data-protocols/dsp/dsp-transfer-process/dsp-transfer-process-transform/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/transformer/type/to/JsonObjectToTransferStartMessageTransformer.java +++ b/data-protocols/dsp/dsp-transfer-process/dsp-transfer-process-transform/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/transformer/type/to/JsonObjectToTransferStartMessageTransformer.java @@ -24,6 +24,7 @@ import static org.eclipse.edc.protocol.dsp.transferprocess.transformer.DspTransferProcessPropertyAndTypeNames.DSPACE_DATA_ADDRESS; import static org.eclipse.edc.protocol.dsp.transferprocess.transformer.DspTransferProcessPropertyAndTypeNames.DSPACE_PROCESS_ID; +import static org.eclipse.edc.protocol.dsp.transferprocess.transformer.DspTransferProcessPropertyAndTypeNames.DSPACE_TRANSFER_START_TYPE; public class JsonObjectToTransferStartMessageTransformer extends AbstractJsonLdTransformer { @@ -35,7 +36,14 @@ public JsonObjectToTransferStartMessageTransformer() { public @Nullable TransferStartMessage transform(@NotNull JsonObject messageObject, @NotNull TransformerContext context) { var transferStartMessageBuilder = TransferStartMessage.Builder.newInstance(); - transformString(messageObject.get(DSPACE_PROCESS_ID), transferStartMessageBuilder::processId, context); + if (!transformMandatoryString(messageObject.get(DSPACE_PROCESS_ID), transferStartMessageBuilder::processId, context)) { + context.problem() + .missingProperty() + .type(DSPACE_TRANSFER_START_TYPE) + .property(DSPACE_PROCESS_ID) + .report(); + return null; + } var dataAddressObject = returnJsonObject(messageObject.get(DSPACE_DATA_ADDRESS), context, DSPACE_DATA_ADDRESS, false); if (dataAddressObject != null) { diff --git a/data-protocols/dsp/dsp-transfer-process/dsp-transfer-process-transform/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/transformer/type/to/JsonObjectToTransferTerminationMessageTransformer.java b/data-protocols/dsp/dsp-transfer-process/dsp-transfer-process-transform/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/transformer/type/to/JsonObjectToTransferTerminationMessageTransformer.java index 02cc0996d42..9a6466940f2 100644 --- a/data-protocols/dsp/dsp-transfer-process/dsp-transfer-process-transform/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/transformer/type/to/JsonObjectToTransferTerminationMessageTransformer.java +++ b/data-protocols/dsp/dsp-transfer-process/dsp-transfer-process-transform/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/transformer/type/to/JsonObjectToTransferTerminationMessageTransformer.java @@ -14,6 +14,7 @@ package org.eclipse.edc.protocol.dsp.transferprocess.transformer.type.to; +import jakarta.json.JsonArray; import jakarta.json.JsonObject; import org.eclipse.edc.connector.transfer.spi.types.protocol.TransferTerminationMessage; import org.eclipse.edc.jsonld.spi.transformer.AbstractJsonLdTransformer; @@ -21,10 +22,11 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import static java.lang.String.format; +import static jakarta.json.JsonValue.ValueType.ARRAY; import static org.eclipse.edc.protocol.dsp.transferprocess.transformer.DspTransferProcessPropertyAndTypeNames.DSPACE_CODE; import static org.eclipse.edc.protocol.dsp.transferprocess.transformer.DspTransferProcessPropertyAndTypeNames.DSPACE_PROCESS_ID; import static org.eclipse.edc.protocol.dsp.transferprocess.transformer.DspTransferProcessPropertyAndTypeNames.DSPACE_REASON; +import static org.eclipse.edc.protocol.dsp.transferprocess.transformer.DspTransferProcessPropertyAndTypeNames.DSPACE_TRANSFER_TERMINATION_TYPE; public class JsonObjectToTransferTerminationMessageTransformer extends AbstractJsonLdTransformer { @@ -36,7 +38,9 @@ public JsonObjectToTransferTerminationMessageTransformer() { public @Nullable TransferTerminationMessage transform(@NotNull JsonObject messageObject, @NotNull TransformerContext context) { var transferTerminationMessageBuilder = TransferTerminationMessage.Builder.newInstance(); - transformString(messageObject.get(DSPACE_PROCESS_ID), transferTerminationMessageBuilder::processId, context); + if (!transformMandatoryString(messageObject.get(DSPACE_PROCESS_ID), transferTerminationMessageBuilder::processId, context)) { + return null; + } if (messageObject.containsKey(DSPACE_CODE)) { transformString(messageObject.get(DSPACE_CODE), transferTerminationMessageBuilder::code, context); @@ -44,12 +48,18 @@ public JsonObjectToTransferTerminationMessageTransformer() { var reasons = messageObject.get(DSPACE_REASON); if (reasons != null) { // optional property - var result = typeValueArray(reasons, context); - if (result == null) { - context.reportProblem(format("Cannot transform property %s in ContractNegotiationTerminationMessage", DSPACE_REASON)); + if (!(reasons instanceof JsonArray)) { + context.problem() + .unexpectedType() + .type(DSPACE_TRANSFER_TERMINATION_TYPE) + .property(DSPACE_REASON) + .actual(reasons.getValueType()) + .expected(ARRAY) + .report(); } else { - if (result.size() > 0) { - transferTerminationMessageBuilder.reason(result.toString()); + var array = (JsonArray) reasons; + if (array.size() > 0) { + transferTerminationMessageBuilder.reason(array.toString()); } } } diff --git a/extensions/common/api/api-core/src/main/java/org/eclipse/edc/api/transformer/JsonObjectToCallbackAddressDtoTransformer.java b/extensions/common/api/api-core/src/main/java/org/eclipse/edc/api/transformer/JsonObjectToCallbackAddressDtoTransformer.java index 1d3fbf0e0a8..e7a7f2bd5f0 100644 --- a/extensions/common/api/api-core/src/main/java/org/eclipse/edc/api/transformer/JsonObjectToCallbackAddressDtoTransformer.java +++ b/extensions/common/api/api-core/src/main/java/org/eclipse/edc/api/transformer/JsonObjectToCallbackAddressDtoTransformer.java @@ -65,7 +65,16 @@ private void setProperties(String key, JsonValue value, CallbackAddressDto.Build transformString(value, builder::authCodeId, context); break; default: - context.reportProblem("Cannot convert key " + key + " as it is not known"); + context.problem() + .unexpectedType() + .type("CallbackAddress") + .property(key) + .expected(IS_TRANSACTIONAL) + .expected(URI) + .expected(EVENTS) + .expected(AUTH_KEY) + .expected(AUTH_CODE_ID) + .report(); break; } } diff --git a/extensions/common/json-ld/src/main/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToConstraintTransformer.java b/extensions/common/json-ld/src/main/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToConstraintTransformer.java index 6b71ef9545d..9ea634153f0 100644 --- a/extensions/common/json-ld/src/main/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToConstraintTransformer.java +++ b/extensions/common/json-ld/src/main/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToConstraintTransformer.java @@ -31,8 +31,8 @@ import java.util.Map; import java.util.function.Supplier; -import static java.lang.String.format; import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_AND_CONSTRAINT_ATTRIBUTE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_CONSTRAINT_TYPE; import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_LEFT_OPERAND_ATTRIBUTE; import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_OPERATOR_ATTRIBUTE; import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_OR_CONSTRAINT_ATTRIBUTE; @@ -53,7 +53,7 @@ public class JsonObjectToConstraintTransformer extends AbstractJsonLdTransformer public JsonObjectToConstraintTransformer() { super(JsonObject.class, Constraint.class); } - + @Override public @Nullable Constraint transform(@NotNull JsonObject object, @NotNull TransformerContext context) { var logicalConstraint = transformLogicalConstraint(object, context); @@ -69,12 +69,20 @@ private AtomicConstraint transformAtomicConstraint(@NotNull JsonObject object, @ var builder = AtomicConstraint.Builder.newInstance(); if (!transformMandatoryString(object.get(ODRL_LEFT_OPERAND_ATTRIBUTE), s -> builder.leftExpression(new LiteralExpression(s)), context)) { - context.reportProblem(format("No mandatory attribute %s found in Constraint", ODRL_LEFT_OPERAND_ATTRIBUTE)); + context.problem() + .missingProperty() + .type("Constraint") + .property(ODRL_LEFT_OPERAND_ATTRIBUTE) + .report(); return null; } if (!transformMandatoryString(object.get(ODRL_OPERATOR_ATTRIBUTE), s -> builder.operator(Operator.valueOf(s)), context)) { - context.reportProblem(format("No mandatory attribute %s found in Constraint", ODRL_LEFT_OPERAND_ATTRIBUTE)); + context.problem() + .missingProperty() + .type(ODRL_CONSTRAINT_TYPE) + .property(ODRL_LEFT_OPERAND_ATTRIBUTE) + .report(); return null; } diff --git a/extensions/common/json-ld/src/main/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToDataAddressTransformer.java b/extensions/common/json-ld/src/main/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToDataAddressTransformer.java index da9b28e6979..4166ad8dbe3 100644 --- a/extensions/common/json-ld/src/main/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToDataAddressTransformer.java +++ b/extensions/common/json-ld/src/main/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToDataAddressTransformer.java @@ -23,6 +23,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import static jakarta.json.JsonValue.ValueType.STRING; import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; @@ -54,7 +55,13 @@ private void transformProperties(String key, JsonValue jsonValue, DataAddress.Bu if (object instanceof String) { builder.property(key, object.toString()); } else { - context.reportProblem("DataAddress#properties can only contain Strings, but an attempt was made to put in a " + object.getClass()); + context.problem() + .unexpectedType() + .type("property") + .property(key) + .actual(object == null ? "null" : object.toString()) + .expected(STRING) + .report(); } } diff --git a/extensions/common/json-ld/src/main/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToDataServiceTransformer.java b/extensions/common/json-ld/src/main/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToDataServiceTransformer.java index 47c1d3d2021..dbab801b47b 100644 --- a/extensions/common/json-ld/src/main/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToDataServiceTransformer.java +++ b/extensions/common/json-ld/src/main/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToDataServiceTransformer.java @@ -36,7 +36,6 @@ public JsonObjectToDataServiceTransformer() { @Override public @Nullable DataService transform(@NotNull JsonObject object, @NotNull TransformerContext context) { - var type = nodeType(object, context); var builder = DataService.Builder.newInstance(); builder.id(nodeId(object)); diff --git a/extensions/common/json-ld/src/main/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToDatasetTransformer.java b/extensions/common/json-ld/src/main/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToDatasetTransformer.java index 8fa257e616a..c88a67b50c6 100644 --- a/extensions/common/json-ld/src/main/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToDatasetTransformer.java +++ b/extensions/common/json-ld/src/main/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToDatasetTransformer.java @@ -25,6 +25,9 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import static jakarta.json.JsonValue.ValueType.ARRAY; +import static jakarta.json.JsonValue.ValueType.OBJECT; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.DCAT_DATASET_TYPE; import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.DCAT_DISTRIBUTION_ATTRIBUTE; import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_POLICY_ATTRIBUTE; @@ -67,7 +70,14 @@ private void transformPolicies(JsonValue value, Dataset.Builder builder, Transfo var array = (JsonArray) value; array.forEach(entry -> transformPolicies(entry, builder, context)); } else { - context.reportProblem("Invalid hasPolicy property"); + context.problem() + .unexpectedType() + .type(DCAT_DATASET_TYPE) + .property(ODRL_POLICY_ATTRIBUTE) + .actual(value == null ? "null" : value.getValueType().toString()) + .expected(OBJECT) + .expected(ARRAY) + .report(); } } } diff --git a/extensions/common/json-ld/src/test/java/org/eclipse/edc/jsonld/transformer/from/JsonObjectFromCatalogTransformerTest.java b/extensions/common/json-ld/src/test/java/org/eclipse/edc/jsonld/transformer/from/JsonObjectFromCatalogTransformerTest.java index 20824b20c27..71f13add879 100644 --- a/extensions/common/json-ld/src/test/java/org/eclipse/edc/jsonld/transformer/from/JsonObjectFromCatalogTransformerTest.java +++ b/extensions/common/json-ld/src/test/java/org/eclipse/edc/jsonld/transformer/from/JsonObjectFromCatalogTransformerTest.java @@ -25,6 +25,7 @@ import org.eclipse.edc.catalog.spi.Dataset; import org.eclipse.edc.catalog.spi.Distribution; import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.transform.spi.ProblemBuilder; import org.eclipse.edc.transform.spi.TransformerContext; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -47,71 +48,72 @@ import static org.mockito.Mockito.when; class JsonObjectFromCatalogTransformerTest { - + private static final String CATALOG_PROPERTY = "catalog:prop:key"; - + private JsonBuilderFactory jsonFactory = Json.createBuilderFactory(Map.of()); private ObjectMapper mapper = mock(ObjectMapper.class); private TransformerContext context = mock(TransformerContext.class); - + private JsonObjectFromCatalogTransformer transformer; - + private JsonObject datasetJson; private JsonObject dataServiceJson; - + @BeforeEach void setUp() { transformer = new JsonObjectFromCatalogTransformer(jsonFactory, mapper); - + datasetJson = getJsonObject("dataset"); dataServiceJson = getJsonObject("dataService"); - + when(context.transform(isA(Dataset.class), eq(JsonObject.class))).thenReturn(datasetJson); when(context.transform(isA(DataService.class), eq(JsonObject.class))).thenReturn(dataServiceJson); + when(context.problem()).thenReturn(new ProblemBuilder(context)); } - + @Test void transform_returnJsonObject() { when(mapper.convertValue(any(), eq(JsonValue.class))).thenReturn(Json.createValue("value")); - + var catalog = getCatalog(); var result = transformer.transform(catalog, context); - + assertThat(result).isNotNull(); assertThat(result.getJsonString(ID).getString()).isEqualTo(catalog.getId()); assertThat(result.getJsonString(TYPE).getString()).isEqualTo(DCAT_CATALOG_TYPE); - + assertThat(result.get(DCAT_DATASET_ATTRIBUTE)) .isNotNull() .isInstanceOf(JsonArray.class) .matches(v -> v.asJsonArray().size() == 1) .matches(v -> v.asJsonArray().get(0).equals(datasetJson)); - + assertThat(result.get(DCAT_DATA_SERVICE_ATTRIBUTE)) .isNotNull() .isInstanceOf(JsonArray.class) .matches(v -> v.asJsonArray().size() == 1) .matches(v -> v.asJsonArray().get(0).equals(dataServiceJson)); - + assertThat(result.get(CATALOG_PROPERTY)).isNotNull(); - + verify(context, times(1)).transform(catalog.getDatasets().get(0), JsonObject.class); verify(context, times(1)).transform(catalog.getDataServices().get(0), JsonObject.class); } - + @Test void transform_mappingPropertyFails_reportProblem() { when(mapper.convertValue(any(), eq(JsonValue.class))).thenThrow(IllegalArgumentException.class); - + var catalog = getCatalog(); var result = transformer.transform(catalog, context); - + assertThat(result).isNotNull(); assertThat(result.get(CATALOG_PROPERTY)).isNull(); - + verify(context, times(1)).reportProblem(anyString()); } - + private Catalog getCatalog() { return Catalog.Builder.newInstance() .id("catalog") @@ -126,7 +128,7 @@ private Catalog getCatalog() { .property(CATALOG_PROPERTY, "value") .build(); } - + private JsonObject getJsonObject(String type) { return jsonFactory.createObjectBuilder() .add(TYPE, type) diff --git a/extensions/common/json-ld/src/test/java/org/eclipse/edc/jsonld/transformer/from/JsonObjectFromDatasetTransformerTest.java b/extensions/common/json-ld/src/test/java/org/eclipse/edc/jsonld/transformer/from/JsonObjectFromDatasetTransformerTest.java index b8d5ec01ad2..ead884dda79 100644 --- a/extensions/common/json-ld/src/test/java/org/eclipse/edc/jsonld/transformer/from/JsonObjectFromDatasetTransformerTest.java +++ b/extensions/common/json-ld/src/test/java/org/eclipse/edc/jsonld/transformer/from/JsonObjectFromDatasetTransformerTest.java @@ -24,6 +24,7 @@ import org.eclipse.edc.catalog.spi.Dataset; import org.eclipse.edc.catalog.spi.Distribution; import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.transform.spi.ProblemBuilder; import org.eclipse.edc.transform.spi.TransformerContext; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -46,47 +47,48 @@ import static org.mockito.Mockito.when; class JsonObjectFromDatasetTransformerTest { - + private static final String DATASET_PROPERTY = "catalog:prop:key"; private static final String OFFER_ID = "offerId"; - + private JsonBuilderFactory jsonFactory = Json.createBuilderFactory(Map.of()); private ObjectMapper mapper = mock(ObjectMapper.class); private TransformerContext context = mock(TransformerContext.class); - + private JsonObjectFromDatasetTransformer transformer; - + private JsonObject policyJson; private JsonObject distributionJson; - + @BeforeEach void setUp() { transformer = new JsonObjectFromDatasetTransformer(jsonFactory, mapper); - + policyJson = getJsonObject("policy"); distributionJson = getJsonObject("distribution"); - + when(context.transform(isA(Policy.class), eq(JsonObject.class))).thenReturn(policyJson); when(context.transform(isA(Distribution.class), eq(JsonObject.class))).thenReturn(distributionJson); + when(context.problem()).thenReturn(new ProblemBuilder(context)); } - + @Test void transform_returnJsonObject() { when(mapper.convertValue(any(), eq(JsonValue.class))).thenReturn(Json.createValue("value")); - + var dataset = getDataset(); var result = transformer.transform(dataset, context); - + assertThat(result).isNotNull(); assertThat(result.getJsonString(ID).getString()).isEqualTo(dataset.getId()); assertThat(result.getJsonString(TYPE).getString()).isEqualTo(DCAT_DATASET_TYPE); - + assertThat(result.get(DCAT_DISTRIBUTION_ATTRIBUTE)) .isNotNull() .isInstanceOf(JsonArray.class) .matches(v -> v.asJsonArray().size() == 1) .matches(v -> v.asJsonArray().get(0).equals(distributionJson)); - + assertThat(result.get(ODRL_POLICY_ATTRIBUTE)) .isNotNull() .isInstanceOf(JsonArray.class) @@ -96,26 +98,26 @@ void transform_returnJsonObject() { assertThat(policyResult.asJsonObject().getJsonString(ID).getString()) .isNotNull() .isEqualTo(OFFER_ID); - + assertThat(result.get(DATASET_PROPERTY)).isNotNull(); - + verify(context, times(1)).transform(dataset.getOffers().get(OFFER_ID), JsonObject.class); verify(context, times(1)).transform(dataset.getDistributions().get(0), JsonObject.class); } - + @Test void transform_mappingPropertyFails_reportProblem() { when(mapper.convertValue(any(), eq(JsonValue.class))).thenThrow(IllegalArgumentException.class); - + var dataset = getDataset(); var result = transformer.transform(dataset, context); - + assertThat(result).isNotNull(); assertThat(result.get(DATASET_PROPERTY)).isNull(); - + verify(context, times(1)).reportProblem(anyString()); } - + private Dataset getDataset() { return Dataset.Builder.newInstance() .id("dataset") @@ -127,7 +129,7 @@ private Dataset getDataset() { .property(DATASET_PROPERTY, "value") .build(); } - + private JsonObject getJsonObject(String type) { return jsonFactory.createObjectBuilder() .add(TYPE, type) diff --git a/extensions/common/json-ld/src/test/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToConstraintTransformerTest.java b/extensions/common/json-ld/src/test/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToConstraintTransformerTest.java index 4c5da05b642..228d7b2e3c2 100644 --- a/extensions/common/json-ld/src/test/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToConstraintTransformerTest.java +++ b/extensions/common/json-ld/src/test/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToConstraintTransformerTest.java @@ -25,6 +25,7 @@ import org.eclipse.edc.policy.model.Operator; import org.eclipse.edc.policy.model.OrConstraint; import org.eclipse.edc.policy.model.XoneConstraint; +import org.eclipse.edc.transform.spi.ProblemBuilder; import org.eclipse.edc.transform.spi.TransformerContext; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -71,6 +72,7 @@ class JsonObjectToConstraintTransformerTest { @BeforeEach void setUp() { transformer = new JsonObjectToConstraintTransformer(); + when(context.problem()).thenReturn(new ProblemBuilder(context)); } @Test diff --git a/extensions/common/json-ld/src/test/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToDataAddressTransformerTest.java b/extensions/common/json-ld/src/test/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToDataAddressTransformerTest.java index b286dd21d0d..c4ec2cbb1f4 100644 --- a/extensions/common/json-ld/src/test/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToDataAddressTransformerTest.java +++ b/extensions/common/json-ld/src/test/java/org/eclipse/edc/jsonld/transformer/to/JsonObjectToDataAddressTransformerTest.java @@ -22,6 +22,7 @@ import org.eclipse.edc.jsonld.transformer.Payload; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.edc.transform.spi.ProblemBuilder; import org.eclipse.edc.transform.spi.TransformerContext; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -58,6 +59,7 @@ void setUp() { transformerContext = mock(TransformerContext.class); when(transformerContext.transform(isA(JsonObject.class), eq(Object.class))) .thenAnswer(i -> objectTransformer.transform(i.getArgument(0), transformerContext)); + when(transformerContext.problem()).thenReturn(new ProblemBuilder(transformerContext)); } @Test diff --git a/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/java/org/eclipse/edc/connector/api/management/contractnegotiation/transform/JsonObjectToNegotiationInitiateRequestDtoTransformer.java b/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/java/org/eclipse/edc/connector/api/management/contractnegotiation/transform/JsonObjectToNegotiationInitiateRequestDtoTransformer.java index 9f611e00675..c54d63f02e5 100644 --- a/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/java/org/eclipse/edc/connector/api/management/contractnegotiation/transform/JsonObjectToNegotiationInitiateRequestDtoTransformer.java +++ b/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/java/org/eclipse/edc/connector/api/management/contractnegotiation/transform/JsonObjectToNegotiationInitiateRequestDtoTransformer.java @@ -76,7 +76,19 @@ private void setProperties(String key, JsonValue value, NegotiationInitiateReque transformArrayOrObject(value, ContractOfferDescription.class, builder::offer, context); break; default: - context.reportProblem("Cannot convert key " + key + " as it is not known"); + context.problem() + .unexpectedType() + .type(NegotiationInitiateRequestDto.TYPE) + .property(key) + .actual(key) + .expected(CONNECTOR_ADDRESS) + .expected(PROTOCOL) + .expected(CONNECTOR_ID) + .expected(PROVIDER_ID) + .expected(CONSUMER_ID) + .expected(CALLBACK_ADDRESSES) + .expected(OFFER) + .report(); break; } } diff --git a/extensions/control-plane/api/management-api/transfer-process-api/src/main/java/org/eclipse/edc/connector/api/management/transferprocess/transform/TransferProcessToTransferProcessDtoTransformer.java b/extensions/control-plane/api/management-api/transfer-process-api/src/main/java/org/eclipse/edc/connector/api/management/transferprocess/transform/TransferProcessToTransferProcessDtoTransformer.java index c0ee66f26f8..105c19b0881 100644 --- a/extensions/control-plane/api/management-api/transfer-process-api/src/main/java/org/eclipse/edc/connector/api/management/transferprocess/transform/TransferProcessToTransferProcessDtoTransformer.java +++ b/extensions/control-plane/api/management-api/transfer-process-api/src/main/java/org/eclipse/edc/connector/api/management/transferprocess/transform/TransferProcessToTransferProcessDtoTransformer.java @@ -69,7 +69,13 @@ public Class getOutputType() { private String getState(int value, TransformerContext context) { var result = TransferProcessStates.from(value); if (result == null) { - context.reportProblem("Invalid value for TransferProcess.state"); + context.problem() + .unexpectedType() + .type(TransferProcess.class) + .property("state") + .expected(TransferProcessStates.class) + .actual(String.valueOf(value)) + .report(); return null; } return result.name(); diff --git a/extensions/control-plane/api/management-api/transfer-process-api/src/test/java/org/eclipse/edc/connector/api/management/transferprocess/transform/TransferProcessToTransferProcessDtoTransformerTest.java b/extensions/control-plane/api/management-api/transfer-process-api/src/test/java/org/eclipse/edc/connector/api/management/transferprocess/transform/TransferProcessToTransferProcessDtoTransformerTest.java index 3f559178334..899a5d56c5c 100644 --- a/extensions/control-plane/api/management-api/transfer-process-api/src/test/java/org/eclipse/edc/connector/api/management/transferprocess/transform/TransferProcessToTransferProcessDtoTransformerTest.java +++ b/extensions/control-plane/api/management-api/transfer-process-api/src/test/java/org/eclipse/edc/connector/api/management/transferprocess/transform/TransferProcessToTransferProcessDtoTransformerTest.java @@ -22,6 +22,7 @@ import org.eclipse.edc.connector.transfer.spi.types.TransferProcess; import org.eclipse.edc.connector.transfer.spi.types.TransferProcessStates; import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.edc.transform.spi.ProblemBuilder; import org.eclipse.edc.transform.spi.TransformerContext; import org.junit.jupiter.api.Test; @@ -31,6 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.edc.connector.transfer.spi.types.TransferProcessStates.INITIAL; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.contains; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -71,13 +73,14 @@ void transform() { void transform_whenInvalidState() { when(context.transform(any(), eq(DataRequestDto.class))).thenReturn(data.dataRequestDto); when(context.transform(any(), eq(CallbackAddressDto.class))).thenReturn(data.callbackAddressDto); + when(context.problem()).thenReturn(new ProblemBuilder(context)); data.entity.state(invalidStateCode()); data.dto.state(null); var result = transformer.transform(data.entity.build(), context); - verify(context).reportProblem("Invalid value for TransferProcess.state"); + verify(context).reportProblem(contains("TransferProcess property 'state' must be")); assertThat(result) .usingRecursiveComparison() .ignoringFields("dataDestination") diff --git a/spi/common/json-ld-spi/src/main/java/org/eclipse/edc/jsonld/spi/transformer/AbstractJsonLdTransformer.java b/spi/common/json-ld-spi/src/main/java/org/eclipse/edc/jsonld/spi/transformer/AbstractJsonLdTransformer.java index 2e14305c104..2bf53af3b10 100644 --- a/spi/common/json-ld-spi/src/main/java/org/eclipse/edc/jsonld/spi/transformer/AbstractJsonLdTransformer.java +++ b/spi/common/json-ld-spi/src/main/java/org/eclipse/edc/jsonld/spi/transformer/AbstractJsonLdTransformer.java @@ -34,9 +34,14 @@ import java.util.function.Supplier; import java.util.stream.Stream; +import static jakarta.json.JsonValue.ValueType.ARRAY; +import static jakarta.json.JsonValue.ValueType.FALSE; +import static jakarta.json.JsonValue.ValueType.NUMBER; +import static jakarta.json.JsonValue.ValueType.OBJECT; +import static jakarta.json.JsonValue.ValueType.STRING; +import static jakarta.json.JsonValue.ValueType.TRUE; import static java.lang.String.format; import static java.util.Collections.emptyList; -import static java.util.Optional.ofNullable; import static java.util.stream.Collectors.toList; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.KEYWORDS; @@ -72,8 +77,8 @@ public Class getOutputType() { } /** - * Extracts the {@link JsonObject} from the value. If the value is a {@link JsonObject}, it will be returned. If it is a {@link JsonArray}, the first entry will be returned if it is a {@link JsonObject}, otherwise null. - * Note that if a JsonObject cannot be returned, a problem will be reported to the context. + * Extracts the {@link JsonObject} from the value. If the value is a {@link JsonObject}, it will be returned. If it is a {@link JsonArray}, the first entry will be + * returned if it is a {@link JsonObject}, otherwise null. Note that if a JsonObject cannot be returned, a problem will be reported to the context. * * @param value the value to extract the object from * @param context the current transformation context @@ -85,8 +90,8 @@ protected JsonObject returnMandatoryJsonObject(@Nullable JsonValue value, Transf } /** - * Extracts the {@link JsonObject} from the value. If the value is a {@link JsonObject}, it will be returned. If it is a {@link JsonArray}, the first entry will be returned if it is a {@link JsonObject}, otherwise null. - * Note that if a JsonObject cannot be returned, a problem will be reported to the context. + * Extracts the {@link JsonObject} from the value. If the value is a {@link JsonObject}, it will be returned. If it is a {@link JsonArray}, the first entry will be + * returned if it is a {@link JsonObject}, otherwise null. Note that if a JsonObject cannot be returned, a problem will be reported to the context. * * @param value the value to extract the object from * @param context the current transformation context @@ -102,7 +107,13 @@ protected JsonObject returnJsonObject(@Nullable JsonValue value, TransformerCont if (entry instanceof JsonObject) { return entry.asJsonObject(); } else { - context.reportProblem(format("Property '%s' contains an unexpected type: %s", propertyName, entry)); + context.problem() + .unexpectedType() + .type(ARRAY) + .property(propertyName) + .expected(OBJECT) + .actual(entry.getValueType()) + .report(); return null; } }) @@ -115,11 +126,20 @@ protected JsonObject returnJsonObject(@Nullable JsonValue value, TransformerCont return value.asJsonObject(); } else if (value == null) { if (mandatory) { - context.reportProblem(format("Property '%s' is null", propertyName)); + context.problem() + .nullProperty() + .property(propertyName) + .report(); } return null; } else { - context.reportProblem(format("Property '%s' contains an unexpected type: %s", propertyName, value)); + context.problem() + .unexpectedType() + .property(propertyName) + .expected(OBJECT) + .expected(ARRAY) + .actual(value.toString()) + .report(); return null; } } @@ -136,12 +156,17 @@ protected void transformProperties(Map properties, JsonObjectBuilder if (properties == null) { return; } - properties.forEach((k, v) -> { try { builder.add(k, mapper.convertValue(v, JsonValue.class)); } catch (IllegalArgumentException e) { - context.reportProblem(format("Failed to transform property: %s", e.getMessage())); + context.problem() + .invalidProperty() + .type(VALUE) + .property(k) + .value(v != null ? v.toString() : "null") + .error(e.getMessage()) + .report(); } }); } @@ -168,8 +193,11 @@ protected void visitArray(JsonValue value, Consumer resultFunction, T if (value instanceof JsonArray) { visitArray(value.asJsonArray(), resultFunction); } else { - context.reportProblem(format("Invalid JsonValue. Expected JsonArray but got: %s", - ofNullable(value).map(it -> value.toString()).orElse(null))); + context.problem() + .unexpectedType() + .actual(value != null ? value.getValueType() : null) + .expected(ARRAY) + .report(); } } @@ -199,8 +227,8 @@ protected Object transformGenericProperty(JsonValue value, TransformerContext co } /** - * Transforms a JsonValue to a string and applies the result function. If the value parameter - * is not of type JsonString, JsonObject or JsonArray, a problem is reported to the context. + * Transforms a JsonValue to a string and applies the result function. If the value parameter is not of type JsonString, JsonObject or JsonArray, + * a problem is reported to the context. * * @param value the value to transform * @param resultFunction the function to apply to the transformation result @@ -211,8 +239,8 @@ protected void transformString(JsonValue value, Consumer resultFunction, } /** - * Transforms a mandatory JsonValue to a string and applies the result function. If the value parameter - * is not of type JsonString, JsonObject or JsonArray, a problem is reported to the context. + * Transforms a mandatory JsonValue to a string and applies the result function. If the value parameter is not of type JsonString, JsonObject or JsonArray, + * a problem is reported to the context. * * @param value the value to transform * @param resultFunction the function to apply to the transformation result @@ -229,8 +257,8 @@ protected boolean transformMandatoryString(JsonValue value, Consumer res } /** - * Transforms a JsonValue to a string and applies the result function. If the value parameter - * is not of type JsonString, JsonObject or JsonArray, a problem is reported to the context. + * Transforms a JsonValue to a string and applies the result function. If the value parameter is not of type JsonString, JsonObject or JsonArray, + * a problem is reported to the context. * * @param value the value to transform * @param context the transformer context @@ -251,16 +279,21 @@ protected String transformString(JsonValue value, TransformerContext context) { }); } else if (value instanceof JsonArray) { return transformString(((JsonArray) value).get(0), context); + } else if (value == null) { + return null; } else { - context.reportProblem(format("Invalid property. Expected JsonString, JsonObject or JsonArray but got: %s", - ofNullable(value).map(it -> value.toString()).orElse(null))); + context.problem() + .unexpectedType() + .actual(value.getValueType()) + .expected(OBJECT) + .expected(ARRAY) + .report(); return null; } } /** - * Transforms a JsonValue to int. If the value parameter is not of type JsonNumber, JsonObject or JsonArray, - * a problem is reported to the context. + * Transforms a JsonValue to int. If the value parameter is not of type JsonNumber, JsonObject or JsonArray, a problem is reported to the context. * * @param value the value to transform * @param context the transformer context @@ -275,35 +308,49 @@ protected int transformInt(JsonValue value, TransformerContext context) { } else if (value instanceof JsonArray) { return transformInt(value.asJsonArray().get(0), context); } else { - context.reportProblem(format("Invalid property. Expected JsonNumber, JsonObject or JsonArray but got %s", value.getClass().getSimpleName())); + var problem = context.problem().unexpectedType().expected(OBJECT).expected(ARRAY).expected(NUMBER); + if (value != null) { + problem.actual(value.getValueType()); + } + problem.report(); return 0; } } /** - * Transforms a JsonValue to boolean. If the value parameter is not of type JsonObject or JsonArray, - * a problem is reported to the context. + * Transforms a JsonValue to boolean. If the value parameter is not of type JsonObject or JsonArray, a problem is reported to the context. * * @param value the value to transform * @param context the transformer context * @return the int value */ protected boolean transformBoolean(JsonValue value, TransformerContext context) { + if (value == null) { + context.problem().unexpectedType().expected(OBJECT).expected(ARRAY).actual("null").report(); + return false; + } if (value instanceof JsonObject) { return value.asJsonObject().getBoolean(VALUE); } else if (value instanceof JsonArray) { return transformBoolean(value.asJsonArray().get(0), context); + } else if (TRUE == value.getValueType()) { + return true; + } else if (FALSE == value.getValueType()) { + return false; } else { - context.reportProblem(format("Invalid property. Expected JsonObject or JsonArray but got %s", value.getClass().getSimpleName())); + context.problem().unexpectedType() + .expected(OBJECT) + .expected(ARRAY) + .actual(value.getValueType()) + .report(); return false; } } /** - * Transforms a JsonValue to the desired output type. The result can be a single instance or a - * list of that type, depending on whether the given value is a JsonObject or a JsonArray. The - * result function is applied to every instance. If the value parameter is neither of type - * JsonObject nor JsonArray, a problem is reported to the context. + * Transforms a JsonValue to the desired output type. The result can be a single instance or a list of that type, depending on whether the given value is a + * JsonObject or a JsonArray. The result function is applied to every instance. If the value parameter is neither of type JsonObject nor JsonArray, a problem + * is reported to the context. * * @param value the value to transform * @param type the desired result type @@ -319,7 +366,11 @@ protected void transformArrayOrObject(JsonValue value, Class type, Consum var result = context.transform(value, type); resultFunction.accept(result); } else { - context.reportProblem(format("Invalid property of type %s. Expected JsonObject or JsonArray but got %s", type.getSimpleName(), value.getClass().getSimpleName())); + var problem = context.problem().unexpectedType().expected(OBJECT).expected(ARRAY); + if (value != null) { + problem.actual(value.getValueType()); + } + problem.report(); } } @@ -337,8 +388,10 @@ protected List transformArray(JsonValue value, Class type, Transformer if (value instanceof JsonObject) { var transformed = context.transform(value.asJsonObject(), type); if (transformed == null) { - context.reportProblem(format("Invalid property of type %s. Expected JsonObject or JsonArray but got %s", - type.getSimpleName(), value.getClass().getSimpleName())); + context.problem().unexpectedType() + .type(OBJECT) + .actual(type) + .report(); return emptyList(); } return List.of(transformed); @@ -347,15 +400,19 @@ protected List transformArray(JsonValue value, Class type, Transformer .map(entry -> context.transform(entry, type)) .collect(toList()); } else { - context.reportProblem(format("Invalid property of type %s. Expected JsonObject or JsonArray but got %s", - type.getSimpleName(), value.getClass().getSimpleName())); + var problem = context.problem().unexpectedType().expected(OBJECT).expected(ARRAY); + if (value != null) { + problem.actual(value.getValueType()); + } + problem.report(); return null; } } /** - * Transforms a JsonValue to the desired output type. If the value parameter is neither of type - * JsonObject nor JsonArray, a problem is reported to the context. + * Transforms a JsonValue to the desired output type. If the value parameter is neither of type JsonObject nor JsonArray, a problem is reported to the context. + *

+ * This method reports errors it encounters. * * @param value the value to transform * @param type the desired result type @@ -370,15 +427,23 @@ protected T transformObject(JsonValue value, Class type, TransformerConte .filter(Objects::nonNull) .findFirst() .orElseGet(() -> { - context.reportProblem(format("Invalid property of type %s. Cannot map array values %s", - type.getSimpleName(), - value.getClass().getSimpleName())); + context.problem().unexpectedType() + .type(ARRAY) + .expected(type) + .report(); return null; }); } else if (value instanceof JsonObject) { return context.transform(value, type); } else { - context.reportProblem(format("Invalid property of type %s. Expected JsonObject but got %s", type.getSimpleName(), value.getClass().getSimpleName())); + var problem = context.problem().unexpectedType() + .type(type) + .expected(OBJECT) + .expected(ARRAY); + if (value != null) { + problem.actual(value.getValueType()); + } + problem.report(); return null; } } @@ -421,7 +486,11 @@ protected String nodeValue(JsonValue value, TransformerContext context) { var array = (JsonArray) value; return nodeValue(array.get(0), context); } else { - context.reportProblem("Invalid @value property: " + value); + context.problem() + .invalidProperty() + .property(VALUE) + .value(value != null ? value.toString() : null) + .report(); return null; } } @@ -432,47 +501,45 @@ protected String nodeValue(JsonValue value, TransformerContext context) { protected String nodeType(JsonObject object, TransformerContext context) { var typeNode = object.get(TYPE); if (typeNode == null) { - context.reportProblem("Property @type not found on JSON Object"); + context.problem() + .missingProperty() + .property(TYPE) + .report(); return null; } if (typeNode instanceof JsonString) { return ((JsonString) typeNode).getString(); } else if (typeNode instanceof JsonArray) { - var array = typeValueArray(typeNode, context); - if (array == null) { + var array = (JsonArray) typeNode; + if (array.isEmpty()) { return null; } var typeValue = array.get(0); // a note can have more than one type, take the first if (!(typeValue instanceof JsonString)) { - context.reportProblem("Expected @type value to be a string"); + var problem = context.problem().unexpectedType().property(TYPE).expected(STRING); + if (typeValue != null) { + problem.actual(typeValue.getValueType()); + } + problem.report(); return null; } return ((JsonString) typeValue).getString(); } - context.reportProblem("Expected @type value to be either string or array"); + context.problem() + .unexpectedType() + .property(TYPE) + .actual(typeNode.getValueType()) + .expected(STRING) + .expected(ARRAY) + .report(); return null; } - @Nullable - protected JsonArray typeValueArray(JsonValue typeNode, TransformerContext context) { - if (!(typeNode instanceof JsonArray)) { - context.reportProblem("Invalid @type node: " + typeNode.getValueType()); - return null; - } - var array = (JsonArray) typeNode; - if (array.isEmpty()) { - context.reportProblem("Expected @type node to be an array with at least one element"); - return null; - } - return array; - } - /** - * Tries to return the instance given by a supplier (a builder's build method). If this fails - * due to validation errors, e.g. a required property is missing, reports a problem to the - * context. + * Tries to return the instance given by a supplier (a builder's build method). If this fails due to validation errors, e.g. a required property is missing, + * reports a problem to the context. * * @param builder the supplier * @param context the context diff --git a/spi/common/json-ld-spi/src/test/java/org/eclipse/edc/jsonld/spi/transformer/AbstractJsonLdTransformerReturnObjectTest.java b/spi/common/json-ld-spi/src/test/java/org/eclipse/edc/jsonld/spi/transformer/AbstractJsonLdTransformerReturnObjectTest.java index 7958cae773a..fd0b8feefe9 100644 --- a/spi/common/json-ld-spi/src/test/java/org/eclipse/edc/jsonld/spi/transformer/AbstractJsonLdTransformerReturnObjectTest.java +++ b/spi/common/json-ld-spi/src/test/java/org/eclipse/edc/jsonld/spi/transformer/AbstractJsonLdTransformerReturnObjectTest.java @@ -16,6 +16,7 @@ import jakarta.json.Json; import jakarta.json.JsonBuilderFactory; +import org.eclipse.edc.transform.spi.ProblemBuilder; import org.eclipse.edc.transform.spi.TransformerContext; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -32,6 +33,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; class AbstractJsonLdTransformerReturnObjectTest { @@ -53,7 +55,7 @@ public Object transform(@NotNull Object o, @NotNull TransformerContext context) jsonFactory = Json.createBuilderFactory(Map.of()); context = mock(TransformerContext.class); - + when(context.problem()).thenReturn(new ProblemBuilder(context)); } @Test @@ -71,7 +73,7 @@ void verify_returnFromNull() { var result = transformer.returnMandatoryJsonObject(null, context, TEST_PROPERTY); assertThat(result).isNull(); - verify(context, times(1)).reportProblem(eq(format("Property '%s' is null", TEST_PROPERTY))); + verify(context, times(1)).reportProblem(eq(format("Property '%s' was null", TEST_PROPERTY))); } @Test @@ -89,7 +91,7 @@ void verify_returnFromInvalidType() { var result = transformer.returnMandatoryJsonObject(value, context, TEST_PROPERTY); assertThat(result).isNull(); - verify(context, times(1)).reportProblem(eq(format("Property '%s' contains an unexpected type: \"test\"", TEST_PROPERTY))); + verify(context, times(1)).reportProblem(eq("Property 'testProperty' must be OBJECT or ARRAY but was: \"test\"")); } @Test diff --git a/spi/common/transform-spi/src/main/java/org/eclipse/edc/transform/spi/AbstractProblemBuilder.java b/spi/common/transform-spi/src/main/java/org/eclipse/edc/transform/spi/AbstractProblemBuilder.java new file mode 100644 index 00000000000..41c927f2c6d --- /dev/null +++ b/spi/common/transform-spi/src/main/java/org/eclipse/edc/transform/spi/AbstractProblemBuilder.java @@ -0,0 +1,76 @@ +/* + * 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.transform.spi; + +import java.util.List; + +import static java.lang.String.join; +import static java.util.Objects.requireNonNull; + +/** + * Base functionality for problem builders. + */ +public abstract class AbstractProblemBuilder> { + protected static final String UNKNOWN = "unknown"; + + protected String type; + protected String property; + + @SuppressWarnings("unchecked") + public B type(String type) { + this.type = type; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B type(Class type) { + this.type = type != null ? type.getName() : null; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B type(Enum type) { + this.type = type != null ? type.toString() : null; + return (B) this; + } + + @SuppressWarnings("unchecked") + public B property(String property) { + this.property = property; + return (B) this; + } + + /** + * Concatenates the strings to a comma-separated list with the following form: + *

+     * ["one"] : "one"
+     * ["one", "two"] : "one or two"
+     * ["one", "two", "three"] : "one, two, or three"
+     * 
+ */ + protected String concatList(List elements) { + requireNonNull(elements); + if (elements.size() == 0) { + return ""; + } else if (elements.size() == 1) { + return elements.get(0); + } else if (elements.size() == 2) { + return elements.get(0) + " or " + elements.get(1); + } + return join(", ", elements.subList(0, elements.size() - 1)) + ", or " + elements.get(elements.size() - 1); + } + + public abstract void report(); +} diff --git a/spi/common/transform-spi/src/main/java/org/eclipse/edc/transform/spi/InvalidPropertyBuilder.java b/spi/common/transform-spi/src/main/java/org/eclipse/edc/transform/spi/InvalidPropertyBuilder.java new file mode 100644 index 00000000000..b71017fd94b --- /dev/null +++ b/spi/common/transform-spi/src/main/java/org/eclipse/edc/transform/spi/InvalidPropertyBuilder.java @@ -0,0 +1,57 @@ +/* + * 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.transform.spi; + +import org.jetbrains.annotations.Nullable; + +import static java.lang.String.format; + +/** + * Reports a property that contains an invalid value. + */ +public class InvalidPropertyBuilder extends AbstractProblemBuilder { + private final TransformerContext context; + + private String value = UNKNOWN; + private String error; + + public InvalidPropertyBuilder(TransformerContext context) { + this.context = context; + } + + public InvalidPropertyBuilder value(@Nullable String value) { + if (value == null) { + this.value = "null"; + return this; + } + this.value = value; + return this; + } + + public InvalidPropertyBuilder error(String error) { + this.error = error; + return this; + } + + @Override + public void report() { + context.reportProblem(format("%s '%s' was invalid%s%s", + type == null ? "Property" : type + " property", + property, + value != null ? ": " + value : "", + error != null ? ". Error was: " + error : "")); + } + +} diff --git a/spi/common/transform-spi/src/main/java/org/eclipse/edc/transform/spi/MissingPropertyBuilder.java b/spi/common/transform-spi/src/main/java/org/eclipse/edc/transform/spi/MissingPropertyBuilder.java new file mode 100644 index 00000000000..b8ff2e2bbf9 --- /dev/null +++ b/spi/common/transform-spi/src/main/java/org/eclipse/edc/transform/spi/MissingPropertyBuilder.java @@ -0,0 +1,34 @@ +/* + * 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.transform.spi; + +import static java.lang.String.format; + +/** + * Reports a missing mandatory property value. + */ +public class MissingPropertyBuilder extends AbstractProblemBuilder { + private final TransformerContext context; + + public MissingPropertyBuilder(TransformerContext context) { + this.context = context; + } + + @Override + public void report() { + context.reportProblem(format("%s '%s' was missing", type == null ? "Property" : type + " property", property)); + } + +} diff --git a/spi/common/transform-spi/src/main/java/org/eclipse/edc/transform/spi/NullPropertyBuilder.java b/spi/common/transform-spi/src/main/java/org/eclipse/edc/transform/spi/NullPropertyBuilder.java new file mode 100644 index 00000000000..802f9870c68 --- /dev/null +++ b/spi/common/transform-spi/src/main/java/org/eclipse/edc/transform/spi/NullPropertyBuilder.java @@ -0,0 +1,34 @@ +/* + * 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.transform.spi; + +import static java.lang.String.format; + +/** + * Reports a mandatory property with a null value. + */ +public class NullPropertyBuilder extends AbstractProblemBuilder { + private final TransformerContext context; + + public NullPropertyBuilder(TransformerContext context) { + this.context = context; + } + + @Override + public void report() { + context.reportProblem(format("%s '%s' was null", type == null ? "Property" : type + " property", property)); + } + +} diff --git a/spi/common/transform-spi/src/main/java/org/eclipse/edc/transform/spi/ProblemBuilder.java b/spi/common/transform-spi/src/main/java/org/eclipse/edc/transform/spi/ProblemBuilder.java new file mode 100644 index 00000000000..a11b04becd4 --- /dev/null +++ b/spi/common/transform-spi/src/main/java/org/eclipse/edc/transform/spi/ProblemBuilder.java @@ -0,0 +1,55 @@ +/* + * 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.transform.spi; + +/** + * Reports typed problems to the transformation context. + */ +public class ProblemBuilder { + private TransformerContext context; + + public ProblemBuilder(TransformerContext context) { + this.context = context; + } + + /** + * Reports a missing mandatory property. + */ + public MissingPropertyBuilder missingProperty() { + return new MissingPropertyBuilder(context); + } + + /** + * Reports a mandatory property whose value is null or empty. + */ + public NullPropertyBuilder nullProperty() { + return new NullPropertyBuilder(context); + } + + /** + * Reports an invalid property value. + */ + public InvalidPropertyBuilder invalidProperty() { + return new InvalidPropertyBuilder(context); + } + + /** + * Reports an attempt to read a property value that is not of the expected type. + */ + public UnexpectedTypeBuilder unexpectedType() { + return new UnexpectedTypeBuilder(context); + } + +} diff --git a/spi/common/transform-spi/src/main/java/org/eclipse/edc/transform/spi/TransformerContext.java b/spi/common/transform-spi/src/main/java/org/eclipse/edc/transform/spi/TransformerContext.java index 6f6fed573c7..c090dadb7b4 100644 --- a/spi/common/transform-spi/src/main/java/org/eclipse/edc/transform/spi/TransformerContext.java +++ b/spi/common/transform-spi/src/main/java/org/eclipse/edc/transform/spi/TransformerContext.java @@ -36,9 +36,18 @@ public interface TransformerContext { /** * Reports a problem. + *

+ * Note {@link #problem()} should be used in most cases. */ void reportProblem(String problem); + /** + * Returns a problem builder that can be used to report problems in a typed manner. + *

+ * Note this method should be preferred to reporting untyped problems using {@link #reportProblem(String)}. + */ + ProblemBuilder problem(); + /** * Transforms the input object and any contained types, returning its transformed representation or null if the * operation cannot be completed or input is null. @@ -64,4 +73,4 @@ default Class typeAlias(String type) { default Class typeAlias(String type, Class defaultType) { return defaultType; } -} \ No newline at end of file +} diff --git a/spi/common/transform-spi/src/main/java/org/eclipse/edc/transform/spi/UnexpectedTypeBuilder.java b/spi/common/transform-spi/src/main/java/org/eclipse/edc/transform/spi/UnexpectedTypeBuilder.java new file mode 100644 index 00000000000..a248a68159e --- /dev/null +++ b/spi/common/transform-spi/src/main/java/org/eclipse/edc/transform/spi/UnexpectedTypeBuilder.java @@ -0,0 +1,108 @@ +/* + * 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.transform.spi; + +import java.util.ArrayList; +import java.util.List; + +/** + * Reports an attempt to read a property value that is not of the expected type(s). + */ +public class UnexpectedTypeBuilder extends AbstractProblemBuilder { + private final TransformerContext context; + + private String actual = UNKNOWN; + private List expected = new ArrayList<>(); + + public UnexpectedTypeBuilder(TransformerContext context) { + this.context = context; + } + + public UnexpectedTypeBuilder actual(String actual) { + this.actual = actual; + return this; + } + + public UnexpectedTypeBuilder actual(Class actual) { + this.actual = actual == null ? null : actual.getName(); + return this; + } + + public UnexpectedTypeBuilder actual(Enum actual) { + this.actual = actual == null ? null : actual.toString(); + return this; + } + + public UnexpectedTypeBuilder expected(String expectedType) { + if (expectedType == null) { + return this; + } + expected.add(expectedType); + return this; + } + + public UnexpectedTypeBuilder expected(Enum expectedType) { + if (expectedType == null) { + return this; + } + expected.add(expectedType.toString()); + return this; + } + + public UnexpectedTypeBuilder expected(Class expectedType) { + if (expectedType == null) { + return this; + } + if (expectedType.isEnum()) { + for (var constant : expectedType.getEnumConstants()) { + expected.add(constant.toString()); + } + } else { + expected.add(expectedType.getName()); + } + return this; + } + + @Override + public void report() { + var builder = new StringBuilder(); + if (type != null) { + builder.append(type); + if (property != null) { + builder.append(" property '").append(property).append("'"); + } + } else { + if (property != null) { + builder.append("Property '").append(property).append("'"); + } + } + if (expected.isEmpty()) { + if (builder.length() == 0) { + builder.append("Value "); + } + builder.append("was not of the expected type"); + } else { + if (builder.length() == 0) { + builder.append("Value"); + } + builder.append(" must be ").append(concatList(expected)); + } + if (actual != null) { + builder.append(" but was: ").append(actual); + } + context.reportProblem(builder.toString()); + } + +} diff --git a/spi/common/transform-spi/src/test/java/org/eclipse/edc/transform/spi/AbstractProblemBuilderTestBuilder.java b/spi/common/transform-spi/src/test/java/org/eclipse/edc/transform/spi/AbstractProblemBuilderTestBuilder.java new file mode 100644 index 00000000000..18bc6fa4bb7 --- /dev/null +++ b/spi/common/transform-spi/src/test/java/org/eclipse/edc/transform/spi/AbstractProblemBuilderTestBuilder.java @@ -0,0 +1,44 @@ +/* + * 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.transform.spi; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class AbstractProblemBuilderTestBuilder { + private AbstractProblemBuilder builder; + + @BeforeEach + void setUp() { + builder = new AbstractProblemBuilder<>() { + @Override + public void report() { + + } + }; + } + + @Test + void verifyConcatList() { + assertThat(builder.concatList(List.of("one"))).isEqualTo("one"); + assertThat(builder.concatList(List.of("one", "two"))).isEqualTo("one or two"); + assertThat(builder.concatList(List.of("one", "two", "three"))).isEqualTo("one, two, or three"); + } + +} diff --git a/spi/common/transform-spi/src/test/java/org/eclipse/edc/transform/spi/InvalidPropertyBuilderTest.java b/spi/common/transform-spi/src/test/java/org/eclipse/edc/transform/spi/InvalidPropertyBuilderTest.java new file mode 100644 index 00000000000..0d76eb7694c --- /dev/null +++ b/spi/common/transform-spi/src/test/java/org/eclipse/edc/transform/spi/InvalidPropertyBuilderTest.java @@ -0,0 +1,58 @@ +/* + * 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.transform.spi; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +class InvalidPropertyBuilderTest { + private TransformerContext context; + private InvalidPropertyBuilder builder; + + @BeforeEach + void setUp() { + context = mock(TransformerContext.class); + builder = new InvalidPropertyBuilder(context); + } + + @Test + void verify_reportNoData() { + builder.report(); + verify(context).reportProblem(eq("Property 'null' was invalid: unknown")); + } + + @Test + void verify_report() { + builder.type("test").property("property").value("value").report(); + verify(context).reportProblem(eq("test property 'property' was invalid: value")); + } + + @Test + void verify_reportProperty() { + builder.property("property").value("value").report(); + verify(context).reportProblem(eq("Property 'property' was invalid: value")); + } + + @Test + void verify_reportNoProperty() { + builder.type("test").value("value").report(); + verify(context).reportProblem(eq("test property 'null' was invalid: value")); + } + +} diff --git a/spi/common/transform-spi/src/test/java/org/eclipse/edc/transform/spi/MissingPropertyBuilderTest.java b/spi/common/transform-spi/src/test/java/org/eclipse/edc/transform/spi/MissingPropertyBuilderTest.java new file mode 100644 index 00000000000..94b2d041fde --- /dev/null +++ b/spi/common/transform-spi/src/test/java/org/eclipse/edc/transform/spi/MissingPropertyBuilderTest.java @@ -0,0 +1,58 @@ +/* + * 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.transform.spi; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +class MissingPropertyBuilderTest { + private TransformerContext context; + private MissingPropertyBuilder builder; + + @BeforeEach + void setUp() { + context = mock(TransformerContext.class); + builder = new MissingPropertyBuilder(context); + } + + @Test + void verify_reportNoData() { + builder.report(); + verify(context).reportProblem(eq("Property 'null' was missing")); + } + + @Test + void verify_report() { + builder.type("test").property("property").report(); + verify(context).reportProblem(eq("test property 'property' was missing")); + } + + @Test + void verify_reportProperty() { + builder.property("property").report(); + verify(context).reportProblem(eq("Property 'property' was missing")); + } + + @Test + void verify_reportNoProperty() { + builder.type("test").report(); + verify(context).reportProblem(eq("test property 'null' was missing")); + } + +} diff --git a/spi/common/transform-spi/src/test/java/org/eclipse/edc/transform/spi/NullPropertyBuilderTest.java b/spi/common/transform-spi/src/test/java/org/eclipse/edc/transform/spi/NullPropertyBuilderTest.java new file mode 100644 index 00000000000..188337606cb --- /dev/null +++ b/spi/common/transform-spi/src/test/java/org/eclipse/edc/transform/spi/NullPropertyBuilderTest.java @@ -0,0 +1,58 @@ +/* + * 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.transform.spi; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +class NullPropertyBuilderTest { + private TransformerContext context; + private NullPropertyBuilder builder; + + @BeforeEach + void setUp() { + context = mock(TransformerContext.class); + builder = new NullPropertyBuilder(context); + } + + @Test + void verify_reportNoData() { + builder.report(); + verify(context).reportProblem(eq("Property 'null' was null")); + } + + @Test + void verify_report() { + builder.type("test").property("property").report(); + verify(context).reportProblem(eq("test property 'property' was null")); + } + + @Test + void verify_reportProperty() { + builder.property("property").report(); + verify(context).reportProblem(eq("Property 'property' was null")); + } + + @Test + void verify_reportNoProperty() { + builder.type("test").report(); + verify(context).reportProblem(eq("test property 'null' was null")); + } + +} diff --git a/spi/common/transform-spi/src/test/java/org/eclipse/edc/transform/spi/UnexpectedTypeBuilderTest.java b/spi/common/transform-spi/src/test/java/org/eclipse/edc/transform/spi/UnexpectedTypeBuilderTest.java new file mode 100644 index 00000000000..4b491e76e5d --- /dev/null +++ b/spi/common/transform-spi/src/test/java/org/eclipse/edc/transform/spi/UnexpectedTypeBuilderTest.java @@ -0,0 +1,70 @@ +/* + * 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.transform.spi; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +class UnexpectedTypeBuilderTest { + private TransformerContext context; + private UnexpectedTypeBuilder builder; + + @BeforeEach + void setUp() { + context = mock(TransformerContext.class); + builder = new UnexpectedTypeBuilder(context); + } + + @Test + void verify_reportNoData() { + builder.report(); + verify(context).reportProblem(eq("Value was not of the expected type but was: unknown")); + } + + @Test + void verify_report() { + builder.type("test").property("property").expected(String.class).actual(Integer.class).report(); + verify(context).reportProblem(eq("test property 'property' must be java.lang.String but was: java.lang.Integer")); + } + + @Test + void verify_reportMultipleExpected() { + builder.type("test").property("property").expected(String.class).expected(Long.class).actual(Integer.class).report(); + verify(context).reportProblem(eq("test property 'property' must be java.lang.String or java.lang.Long but was: java.lang.Integer")); + } + + @Test + void verify_reportNoType() { + builder.type("test").type("test").expected(String.class).actual(Integer.class).report(); + verify(context).reportProblem(eq("test must be java.lang.String but was: java.lang.Integer")); + } + + @Test + void verify_reportNoProperty() { + builder.type("test").expected(String.class).actual(Integer.class).report(); + verify(context).reportProblem(eq("test must be java.lang.String but was: java.lang.Integer")); + } + + @Test + void verify_reportNoTypeNoProperty() { + builder.expected(String.class).actual(Integer.class).report(); + verify(context).reportProblem(eq("Value must be java.lang.String but was: java.lang.Integer")); + } + +}