Skip to content

Commit

Permalink
feat: replace @type with type and roundTrip testing (#114)
Browse files Browse the repository at this point in the history
  • Loading branch information
wolf4ood authored Dec 6, 2024
1 parent 91e2f7b commit 02f6f3f
Show file tree
Hide file tree
Showing 6 changed files with 275 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"@context": [
"https://w3id.org/dspace-dcp/v0.8"
],
"type": "PresentationQueryMessage",
"presentationDefinition": {
"id": "presentation1",
"input_descriptors": [
{
"id": "organization credential",
"format": {
"ldp_vc": {
"proof_type": [
"Ed25519Signature2018"
]
}
},
"constraints": {
"fields": [
{
"path": [
"$.type"
],
"filter": {
"type": "string",
"pattern": "OrganizationCredential"
}
}
]
}
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* Copyright (c) 2024 Metaform Systems, Inc.
*
* 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:
* Metaform Systems, Inc. - initial API and implementation
*
*/

package org.eclipse.dcp.context.fixtures;

import com.apicatalog.jsonld.JsonLdError;
import com.apicatalog.jsonld.JsonLdOptions;
import com.apicatalog.jsonld.document.Document;
import com.apicatalog.jsonld.document.JsonDocument;
import com.apicatalog.jsonld.loader.DocumentLoader;
import com.apicatalog.jsonld.loader.DocumentLoaderOptions;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsonp.JSONPModule;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.SchemaLocation;
import jakarta.json.Json;
import jakarta.json.JsonObject;
import jakarta.json.JsonStructure;
import org.junit.jupiter.api.BeforeEach;

import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;

import static com.apicatalog.jsonld.JsonLd.compact;
import static com.apicatalog.jsonld.JsonLd.expand;
import static com.apicatalog.jsonld.lang.Keywords.CONTEXT;
import static com.networknt.schema.SpecVersion.VersionFlag.V202012;
import static java.lang.String.format;
import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.dcp.schema.SchemaConstants.DCP_CONTEXT;
import static org.eclipse.dcp.schema.SchemaConstants.DCP_PREFIX;
import static org.eclipse.dcp.schema.fixtures.AbstractSchemaTest.PRESENTATION_EXCHANGE_PREFIX;

/**
* Base class for Json-Ld expansion and compaction tests.
*/
public abstract class AbstractJsonLdTest {
private static final String CLASSPATH_SCHEMA = "classpath:/";
private static final String CONTEXT_REFERENCE = format("{\"@context\": [\"%s\"]}", DCP_CONTEXT);
private final Map<String, String> contextMap = Map.of(
DCP_CONTEXT, "/context/dcp.jsonld",
"https://www.w3.org/ns/odrl.jsonld", "/context/odrl.jsonld",
"https://www.w3.org/2018/credentials/v1", "/context/credentials.jsonld",
"https://www.w3.org/2018/credentials/examples/v1", "/context/credentials-examples.jsonld"
);
protected ObjectMapper mapper;
protected JsonStructure compactionContext;
protected JsonLdOptions options;

@BeforeEach
void setUp() {
mapper = new ObjectMapper();
mapper.registerModule(new JSONPModule());

Map<String, Document> cache = new HashMap<>();

contextMap.forEach((key, value) -> {
try (var stream = getClass().getResourceAsStream(value)) {
var context = mapper.readValue(stream, JsonObject.class);
cache.put(key, JsonDocument.of(context));
} catch (IOException e) {
throw new RuntimeException(e);
}
});
try {
var documentLoader = new LocalDocumentLoader(cache);
compactionContext = mapper.readValue(CONTEXT_REFERENCE, JsonStructure.class);
options = new JsonLdOptions();
options.setDocumentLoader(documentLoader);
} catch (IOException e) {
throw new RuntimeException(e);
}

}

protected void verifyRoundTrip(String jsonFile, String schemaFile) {
try {
var stream = getClass().getResourceAsStream(jsonFile);
var message = mapper.readValue(stream, JsonObject.class);
verifyRoundTrip(message, schemaFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

protected void verifyRoundTrip(JsonObject message, String schemaFile) {
try {

var context = Json.createObjectBuilder().add(CONTEXT, message.get(CONTEXT)).build();
var expanded = expand(JsonDocument.of(message)).options(options).get();
var compacted = compact(JsonDocument.of(expanded), JsonDocument.of(context)).options(options).get();

var schemaFactory = JsonSchemaFactory.getInstance(V202012, builder ->
builder.schemaMappers(schemaMappers -> schemaMappers.mapPrefix(DCP_PREFIX, CLASSPATH_SCHEMA)
.mapPrefix(PRESENTATION_EXCHANGE_PREFIX, CLASSPATH_SCHEMA))
);

var schema = schemaFactory.getSchema(SchemaLocation.of(DCP_PREFIX + schemaFile));
var result = schema.validate(mapper.convertValue(compacted, JsonNode.class));
assertThat(result.isEmpty()).isTrue();
assertThat(compacted).isEqualTo(message);
} catch (JsonLdError e) {
throw new RuntimeException(e);
}
}

private static class LocalDocumentLoader implements DocumentLoader {
private final Map<String, Document> contexts = new HashMap<>();

LocalDocumentLoader(Map<String, Document> contexts) {
this.contexts.putAll(contexts);
}

@Override
public Document loadDocument(URI url, DocumentLoaderOptions options) {
return contexts.get(url.toString());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright (c) 2024 Metaform Systems, Inc.
*
* 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:
* Metaform Systems, Inc. - initial API and implementation
*
*/

package org.eclipse.dcp.context.issuance;

import jakarta.json.JsonObject;
import org.eclipse.dcp.context.fixtures.AbstractJsonLdTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.io.IOException;

public class IssuanceContextTest extends AbstractJsonLdTest {

@Test
void verifyCredentialRequestMessage() {
verifyRoundTrip("/issuance/example/credential-request-message.json", "/issuance/credential-request-message-schema.json");
}

@Test
void verifyCredentialOfferMessage() {
verifyRoundTrip("/issuance/example/credential-offer-message.json", "/issuance/credential-offer-message-schema.json");
}

@Test
void verifyCredentialObject() {
verifyRoundTrip("/issuance/example/credential-object.json", "/issuance/credential-object-schema.json");
}

@Test
void verifyIssuerMetadata() {
verifyRoundTrip("/issuance/example/issuer-metadata.json", "/issuance/issuer-metadata-schema.json");
}

@Test
void verifyCredentialStatus() {
verifyRoundTrip("/issuance/example/credential-status.json", "/issuance/credential-status-schema.json");
}

@ParameterizedTest
@ValueSource(strings = { "RECEIVED", "REJECTED", "ISSUED" })
void verifyCredentialStatus_withStatus(String status) throws IOException {
var msg = """
{
"@context": ["https://w3id.org/dspace-dcp/v0.8"],
"type": "CredentialStatus",
"requestId": "requestId",
"status": "%s"
}""".formatted(status);

var message = mapper.readValue(msg, JsonObject.class);
verifyRoundTrip(message, "/issuance/credential-status-schema.json");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2024 Metaform Systems, Inc.
*
* 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:
* Metaform Systems, Inc. - initial API and implementation
*
*/

package org.eclipse.dcp.context.presentation;

import org.eclipse.dcp.context.fixtures.AbstractJsonLdTest;
import org.junit.jupiter.api.Test;

public class PresentationContextTest extends AbstractJsonLdTest {

@Test
void verifyPresentationQueryMessage() {
verifyRoundTrip("/presentation/example/presentation-query-message.json", "/presentation/presentation-query-message-schema.json");
}

@Test
void verifyPresentationQueryMessage_withPresentationDefinition() {
verifyRoundTrip("/presentation/example/presentation-query-message-w-presentation-definition.json", "/presentation/presentation-query-message-schema.json");
}

@Test
void verifyPresentationResponseMessage() {
verifyRoundTrip("/presentation/example/presentation-response-message.json", "/presentation/presentation-response-message-schema.json");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@
* Base test class.
*/
public abstract class AbstractSchemaTest {
public static final String PRESENTATION_EXCHANGE_PREFIX = "https://identity.foundation/";
protected static final String MIN_CONTAINS = "minContains";
protected static final String REQUIRED = "required";
protected static final String ONE_OF = "oneOf";
protected static final String TYPE = "type";
protected static final String ENUM = "enum";
protected static final String PRESENTATION_EXCHANGE_PREFIX = "https://identity.foundation/";
private static final String CLASSPATH_SCHEMA = "classpath:/";
protected JsonSchema schema;

Expand Down
14 changes: 3 additions & 11 deletions specifications/verifiable.presentation.protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ exact error code is implementation-specific.
|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Schema** | [JSON Schema](./resources/v0.8/presentation/presentation-query-message-schema.json) |
| **Required** | - `@context`: Specifies a valid Json-Ld context ([[json-ld11]], sect. 3.1) |
| | - `@type`: A string specifying the `PresentationQueryMessage` type. |
| | - `type`: A string specifying the `PresentationQueryMessage` type. |
| **Optional** | - `scope`: An array of scopes corresponding to Section [[[#scopes]]]. |
| | - `presentationDefinition`: A valid `Presentation Definition` according to the [Presentation Exchange Specification](https://identity.foundation/presentation-exchange/spec/v2.0.0/#presentation-definition). |

Expand All @@ -116,15 +116,7 @@ The following are non-normative examples of the JSON body:
</aside>

<aside class="example" title="PresentationQueryMessage with presentationDefinition">
<pre class="json">
{
"@context": [
"https://w3id.org/dspace-dcp/v0.8",
"https://identity.foundation/presentation-exchange/submission/v1"
],
"@type": "PresentationQueryMessage",
"presentationDefinition": "..."
}
<pre class="json" data-include="./resources/v0.8/presentation/example/presentation-query-message-w-presentation-definition.json">
</pre>
</aside>

Expand Down Expand Up @@ -174,7 +166,7 @@ Verifiable Credential.
|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Schema** | [JSON Schema](./resources/v0.8/presentation/presentation-response-message-schema.json) |
| **Required** | - `@context`: Specifies a valid Json-Ld context ([[json-ld11]], sect. 3.1). |
| | - `@type`: A string specifying the `PresentationResponseMessage` type. |
| | - `type`: A string specifying the `PresentationResponseMessage` type. |
| | - `presentation`: An array of [=Verifiable Presentations=]. The [=Verifiable Presentations=] may be strings, JSON objects, or a combination of both depending on the format.</br> |

The following are non-normative examples of the JSON response body:
Expand Down

0 comments on commit 02f6f3f

Please sign in to comment.