diff --git a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/AbstractResource.java b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/AbstractResource.java index 9683dfca42..7b6c768002 100644 --- a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/AbstractResource.java +++ b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/AbstractResource.java @@ -5,6 +5,7 @@ import io.apicurio.registry.ccompat.rest.error.ConflictException; import io.apicurio.registry.ccompat.rest.error.UnprocessableEntityException; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.model.BranchId; import io.apicurio.registry.model.GA; import io.apicurio.registry.model.GAV; @@ -30,7 +31,6 @@ import io.apicurio.registry.types.VersionState; import io.apicurio.registry.types.provider.ArtifactTypeUtilProvider; import io.apicurio.registry.types.provider.ArtifactTypeUtilProviderFactory; -import io.apicurio.registry.util.ContentTypeUtil; import jakarta.inject.Inject; import org.apache.avro.AvroTypeException; import org.apache.avro.SchemaParseException; @@ -68,19 +68,18 @@ protected ArtifactVersionMetaDataDto createOrUpdateArtifact(String subject, Stri ArtifactVersionMetaDataDto res; final List parsedReferences = parseReferences(references, groupId); final List artifactReferences = parsedReferences.stream().map(dto -> ArtifactReference.builder().name(dto.getName()).groupId(dto.getGroupId()).artifactId(dto.getArtifactId()).version(dto.getVersion()).build()).collect(Collectors.toList()); - final Map resolvedReferences = storage.resolveReferences(parsedReferences); + final Map resolvedReferences = storage.resolveReferences(parsedReferences); try { ContentHandle schemaContent; schemaContent = ContentHandle.create(schema); String contentType = ContentTypes.APPLICATION_JSON; if (artifactType.equals(ArtifactType.PROTOBUF)) { contentType = ContentTypes.APPLICATION_PROTOBUF; - } else if (ContentTypeUtil.isParsableYaml(schemaContent)) { - contentType = ContentTypes.APPLICATION_YAML; } if (!doesArtifactExist(subject, groupId)) { - rulesService.applyRules(groupId, subject, artifactType, schemaContent, RuleApplicationType.CREATE, artifactReferences, resolvedReferences); + TypedContent typedSchemaContent = TypedContent.create(schemaContent, contentType); + rulesService.applyRules(groupId, subject, artifactType, typedSchemaContent, RuleApplicationType.CREATE, artifactReferences, resolvedReferences); EditableArtifactMetaDataDto artifactMetaData = EditableArtifactMetaDataDto.builder().build(); EditableVersionMetaDataDto firstVersionMetaData = EditableVersionMetaDataDto.builder().build(); @@ -93,7 +92,8 @@ protected ArtifactVersionMetaDataDto createOrUpdateArtifact(String subject, Stri res = storage.createArtifact(groupId, subject, artifactType, artifactMetaData, null, firstVersionContent, firstVersionMetaData, null).getValue(); } else { - rulesService.applyRules(groupId, subject, artifactType, schemaContent, RuleApplicationType.UPDATE, artifactReferences, resolvedReferences); + TypedContent typedSchemaContent = TypedContent.create(schemaContent, contentType); + rulesService.applyRules(groupId, subject, artifactType, typedSchemaContent, RuleApplicationType.UPDATE, artifactReferences, resolvedReferences); ContentWrapperDto versionContent = ContentWrapperDto.builder() .content(schemaContent) .contentType(contentType) @@ -116,13 +116,15 @@ protected ArtifactVersionMetaDataDto lookupSchema(String groupId, String subject //FIXME simplify logic try { final String type = schemaType == null ? ArtifactType.AVRO : schemaType; + final String contentType = type.equals(ArtifactType.PROTOBUF) ? ContentTypes.APPLICATION_PROTOBUF : ContentTypes.APPLICATION_JSON; + TypedContent typedSchemaContent = TypedContent.create(ContentHandle.create(schema), contentType); final List artifactReferences = parseReferences(schemaReferences, groupId); ArtifactTypeUtilProvider artifactTypeProvider = factory.getArtifactTypeProvider(type); ArtifactVersionMetaDataDto amd; if (cconfig.canonicalHashModeEnabled.get() || normalize) { try { - amd = storage.getArtifactVersionMetaDataByContent(groupId, subject, true, ContentHandle.create(schema), artifactReferences); + amd = storage.getArtifactVersionMetaDataByContent(groupId, subject, true, typedSchemaContent, artifactReferences); } catch (ArtifactNotFoundException ex) { if (type.equals(ArtifactType.AVRO)) { //When comparing using content, sometimes the references might be inlined into the content, try to dereference the existing content and compare as a fallback. See https://github.com/Apicurio/apicurio-registry/issues/3588 for more information. @@ -131,8 +133,13 @@ protected ArtifactVersionMetaDataDto lookupSchema(String groupId, String subject amd = storage.getArtifactVersions(groupId, subject) .stream().filter(version -> { StoredArtifactVersionDto artifactVersion = storage.getArtifactVersionContent(groupId, subject, version); - Map artifactVersionReferences = storage.resolveReferences(artifactVersion.getReferences()); - String dereferencedExistingContentSha = DigestUtils.sha256Hex(artifactTypeProvider.getContentDereferencer().dereference(artifactVersion.getContent(), artifactVersionReferences).content()); + TypedContent typedArtifactVersion = TypedContent.create(artifactVersion.getContent(), artifactVersion.getContentType()); + Map artifactVersionReferences = storage.resolveReferences(artifactVersion.getReferences()); + String dereferencedExistingContentSha = DigestUtils.sha256Hex( + artifactTypeProvider.getContentDereferencer().dereference( + typedArtifactVersion, artifactVersionReferences + ).getContent().content() + ); return dereferencedExistingContentSha.equals(DigestUtils.sha256Hex(schema)); }) .findAny() @@ -144,7 +151,7 @@ protected ArtifactVersionMetaDataDto lookupSchema(String groupId, String subject } } else { - amd = storage.getArtifactVersionMetaDataByContent(groupId, subject, false, ContentHandle.create(schema), artifactReferences); + amd = storage.getArtifactVersionMetaDataByContent(groupId, subject, false, typedSchemaContent, artifactReferences); } return amd; @@ -153,8 +160,8 @@ protected ArtifactVersionMetaDataDto lookupSchema(String groupId, String subject } } - protected Map resolveReferences(List references) { - Map resolvedReferences = Collections.emptyMap(); + protected Map resolveReferences(List references) { + Map resolvedReferences = Collections.emptyMap(); if (references != null && !references.isEmpty()) { //Transform the given references into dtos and set the contentId, this will also detect if any of the passed references does not exist. final List referencesAsDtos = references.stream().map(schemaReference -> { diff --git a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/CompatibilityResourceImpl.java b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/CompatibilityResourceImpl.java index b440c32d80..570da62249 100644 --- a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/CompatibilityResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/CompatibilityResourceImpl.java @@ -9,11 +9,14 @@ import io.apicurio.registry.ccompat.rest.error.UnprocessableEntityException; import io.apicurio.registry.ccompat.rest.v7.CompatibilityResource; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.rules.RuleViolationException; import io.apicurio.registry.rules.UnprocessableSchemaException; import io.apicurio.registry.storage.dto.ArtifactVersionMetaDataDto; +import io.apicurio.registry.types.ArtifactType; +import io.apicurio.registry.types.ContentTypes; import jakarta.interceptor.Interceptors; import java.util.Collections; @@ -31,7 +34,14 @@ public CompatibilityCheckResponse testCompatibilityBySubjectName(String subject, final List versions = storage.getArtifactVersions(groupId, subject); for (String version : versions) { final ArtifactVersionMetaDataDto artifactVersionMetaData = storage.getArtifactVersionMetaData(groupId, subject, version); - rulesService.applyRules(groupId, subject, version, artifactVersionMetaData.getArtifactType(), ContentHandle.create(request.getSchema()), Collections.emptyList(), Collections.emptyMap()); + // Assume the content type of the SchemaContent is the same as the previous version. + String contentType = ContentTypes.APPLICATION_JSON; + if (artifactVersionMetaData.getArtifactType().equals(ArtifactType.PROTOBUF)) { + contentType = ContentTypes.APPLICATION_PROTOBUF; + } + TypedContent typedContent = TypedContent.create(ContentHandle.create(request.getSchema()), contentType); + rulesService.applyRules(groupId, subject, version, artifactVersionMetaData.getArtifactType(), + typedContent, Collections.emptyList(), Collections.emptyMap()); } return CompatibilityCheckResponse.IS_COMPATIBLE; } catch (RuleViolationException ex) { @@ -53,7 +63,14 @@ public CompatibilityCheckResponse testCompatibilityByVersion(String subject, Str return parseVersionString(subject, versionString, groupId, v -> { try { final ArtifactVersionMetaDataDto artifact = storage.getArtifactVersionMetaData(groupId, subject, v); - rulesService.applyRules(groupId, subject, v, artifact.getArtifactType(), ContentHandle.create(request.getSchema()), Collections.emptyList(), Collections.emptyMap()); + // Assume the content type of the SchemaContent is correct based on the artifact type. + String contentType = ContentTypes.APPLICATION_JSON; + if (artifact.getArtifactType().equals(ArtifactType.PROTOBUF)) { + contentType = ContentTypes.APPLICATION_PROTOBUF; + } + TypedContent typedContent = TypedContent.create(ContentHandle.create(request.getSchema()), contentType); + rulesService.applyRules(groupId, subject, v, artifact.getArtifactType(), + typedContent, Collections.emptyList(), Collections.emptyMap()); return CompatibilityCheckResponse.IS_COMPATIBLE; } catch (RuleViolationException ex) { if (fverbose) { diff --git a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SchemasResourceImpl.java b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SchemasResourceImpl.java index 597d6258a6..e0f18fd49c 100644 --- a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SchemasResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SchemasResourceImpl.java @@ -8,6 +8,7 @@ import io.apicurio.registry.ccompat.dto.SubjectVersion; import io.apicurio.registry.ccompat.rest.v7.SchemasResource; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.storage.dto.ArtifactReferenceDto; @@ -45,8 +46,9 @@ public SchemaInfo getSchema(int id, String subject, String groupId) { contentType = contentWrapper.getContentType(); references = contentWrapper.getReferences(); } - return converter.convert(contentHandle, ArtifactTypeUtil.determineArtifactType(contentHandle, null, contentType, - storage.resolveReferences(references), factory.getAllArtifactTypes()), references); + TypedContent typedContent = TypedContent.create(contentHandle, contentType); + return converter.convert(contentHandle, ArtifactTypeUtil.determineArtifactType(typedContent, null, + storage.resolveReferences(references), factory), references); } @Override diff --git a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectVersionsResourceImpl.java b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectVersionsResourceImpl.java index bc984f7185..babe985058 100644 --- a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectVersionsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectVersionsResourceImpl.java @@ -14,6 +14,7 @@ import io.apicurio.registry.ccompat.rest.error.UnprocessableEntityException; import io.apicurio.registry.ccompat.rest.v7.SubjectVersionsResource; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.storage.dto.ArtifactVersionMetaDataDto; @@ -24,8 +25,8 @@ import io.apicurio.registry.storage.error.VersionNotFoundException; import io.apicurio.registry.types.VersionState; import io.apicurio.registry.util.ArtifactTypeUtil; -import io.apicurio.registry.util.ContentTypeUtil; -import io.apicurio.registry.util.VersionUtil; +import io.apicurio.registry.content.util.ContentTypeUtil; +import io.apicurio.registry.utils.VersionUtil; import jakarta.inject.Inject; import jakarta.interceptor.Interceptors; import jakarta.ws.rs.BadRequestException; @@ -71,7 +72,7 @@ public SchemaId register(String subject, SchemaInfo request, Boolean normalize, throw new UnprocessableEntityException("The schema provided is null."); } - final Map resolvedReferences = resolveReferences(request.getReferences()); + final Map resolvedReferences = resolveReferences(request.getReferences()); try { ArtifactVersionMetaDataDto dto = lookupSchema(groupId, subject, request.getSchema(), request.getReferences(), request.getSchemaType(), fnormalize); @@ -88,10 +89,11 @@ public SchemaId register(String subject, SchemaInfo request, Boolean normalize, try { ContentHandle schemaContent = ContentHandle.create(request.getSchema()); String contentType = ContentTypeUtil.determineContentType(schemaContent); + TypedContent typedSchemaContent = TypedContent.create(schemaContent, contentType); // We validate the schema at creation time by inferring the type from the content - final String artifactType = ArtifactTypeUtil.determineArtifactType(ContentHandle.create(request.getSchema()), - null, contentType, resolvedReferences, factory.getAllArtifactTypes()); + final String artifactType = ArtifactTypeUtil.determineArtifactType(typedSchemaContent, + null, resolvedReferences, factory); if (request.getSchemaType() != null && !artifactType.equals(request.getSchemaType())) { throw new UnprocessableEntityException(String.format("Given schema is not from type: %s", request.getSchemaType())); } diff --git a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectsResourceImpl.java b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectsResourceImpl.java index 6b759b54a7..6d3cf3885f 100644 --- a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectsResourceImpl.java @@ -32,7 +32,7 @@ import io.apicurio.registry.storage.error.InvalidArtifactStateException; import io.apicurio.registry.storage.error.InvalidVersionStateException; import io.apicurio.registry.types.VersionState; -import io.apicurio.registry.util.VersionUtil; +import io.apicurio.registry.utils.VersionUtil; import jakarta.interceptor.Interceptors; @Interceptors({ResponseErrorLivenessCheck.class, ResponseTimeoutReadinessCheck.class}) diff --git a/app/src/main/java/io/apicurio/registry/rest/v2/AbstractResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v2/AbstractResourceImpl.java index 7d90aec2eb..1c13a0f6e3 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v2/AbstractResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v2/AbstractResourceImpl.java @@ -1,22 +1,7 @@ package io.apicurio.registry.rest.v2; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import jakarta.inject.Inject; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.ws.rs.core.Context; - -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.slf4j.Logger; - import io.apicurio.common.apps.config.Info; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.content.dereference.ContentDereferencer; import io.apicurio.registry.content.refs.JsonPointerExternalReference; import io.apicurio.registry.storage.RegistryStorage; @@ -25,6 +10,19 @@ import io.apicurio.registry.types.provider.ArtifactTypeUtilProvider; import io.apicurio.registry.types.provider.ArtifactTypeUtilProviderFactory; import io.apicurio.registry.utils.StringUtil; +import jakarta.inject.Inject; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.core.Context; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.slf4j.Logger; + +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public abstract class AbstractResourceImpl { @@ -51,13 +49,13 @@ public abstract class AbstractResourceImpl { * @param dereference * @param content */ - protected ContentHandle handleContentReferences(boolean dereference, String artifactType, - ContentHandle content, List references) { + protected TypedContent handleContentReferences(boolean dereference, String artifactType, + TypedContent content, List references) { // Dereference or rewrite references if (!references.isEmpty() && dereference) { ArtifactTypeUtilProvider artifactTypeProvider = factory.getArtifactTypeProvider(artifactType); ContentDereferencer contentDereferencer = artifactTypeProvider.getContentDereferencer(); - Map resolvedReferences = storage.resolveReferences(references); + Map resolvedReferences = storage.resolveReferences(references); content = contentDereferencer.dereference(content, resolvedReferences); } return content; @@ -119,7 +117,6 @@ protected String resolveReferenceUrl(ArtifactReferenceDto reference) { /** * Resolves a host name from the information found in X-Forwarded-Host and X-Forwarded-Proto. - * @param path */ private static URI getApiBaseHrefFromXForwarded(HttpServletRequest request) throws URISyntaxException { String fproto = request.getHeader("X-Forwarded-Proto"); @@ -133,7 +130,6 @@ private static URI getApiBaseHrefFromXForwarded(HttpServletRequest request) thro /** * Resolves a host name from the request information. - * @param path */ private static URI getApiBaseHrefFromRequest(HttpServletRequest request) throws URISyntaxException { String requestUrl = request.getRequestURL().toString(); diff --git a/app/src/main/java/io/apicurio/registry/rest/v2/AdminResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v2/AdminResourceImpl.java index c6407467db..32dc990320 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v2/AdminResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v2/AdminResourceImpl.java @@ -52,8 +52,8 @@ import java.util.zip.ZipInputStream; import static io.apicurio.common.apps.logging.audit.AuditingConstants.*; -import static io.apicurio.registry.util.DtoUtil.appAuthPropertyToRegistry; -import static io.apicurio.registry.util.DtoUtil.registryAuthPropertyToApp; +import static io.apicurio.registry.utils.DtoUtil.appAuthPropertyToRegistry; +import static io.apicurio.registry.utils.DtoUtil.registryAuthPropertyToApp; @ApplicationScoped @Interceptors({ResponseErrorLivenessCheck.class, ResponseTimeoutReadinessCheck.class}) diff --git a/app/src/main/java/io/apicurio/registry/rest/v2/GroupsResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v2/GroupsResourceImpl.java index ea0ec0d76d..390c6e7134 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v2/GroupsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v2/GroupsResourceImpl.java @@ -7,8 +7,10 @@ import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.content.extract.ContentExtractor; import io.apicurio.registry.content.extract.ExtractedMetaData; +import io.apicurio.registry.content.util.ContentTypeUtil; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.model.BranchId; @@ -69,7 +71,6 @@ import io.apicurio.registry.types.provider.ArtifactTypeUtilProvider; import io.apicurio.registry.util.ArtifactIdGenerator; import io.apicurio.registry.util.ArtifactTypeUtil; -import io.apicurio.registry.util.ContentTypeUtil; import io.apicurio.registry.utils.ArtifactIdValidator; import io.apicurio.registry.utils.IoUtil; import io.apicurio.registry.utils.JAXRSClientUtil; @@ -81,7 +82,6 @@ import jakarta.ws.rs.HttpMethod; import jakarta.ws.rs.NotAllowedException; import jakarta.ws.rs.client.Client; -import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.apache.commons.lang3.tuple.Pair; import org.jose4j.base64url.Base64; @@ -173,12 +173,10 @@ public Response getLatestArtifact(String groupId, String artifactId, Boolean der ArtifactVersionMetaDataDto metaData = storage.getArtifactVersionMetaData(latestGAV.getRawGroupIdWithNull(), latestGAV.getRawArtifactId(), latestGAV.getRawVersionId()); StoredArtifactVersionDto artifact = storage.getArtifactVersionContent(defaultGroupIdToNull(groupId), artifactId, latestGAV.getRawVersionId()); - MediaType contentType = factory.getArtifactMediaType(metaData.getArtifactType()); - - ContentHandle contentToReturn = artifact.getContent(); + TypedContent contentToReturn = TypedContent.create(artifact.getContent(), artifact.getContentType()); contentToReturn = handleContentReferences(dereference, metaData.getArtifactType(), contentToReturn, artifact.getReferences()); - Response.ResponseBuilder builder = Response.ok(contentToReturn, contentType); + Response.ResponseBuilder builder = Response.ok(contentToReturn.getContent(), contentToReturn.getContentType()); checkIfDeprecated(metaData::getState, groupId, artifactId, metaData.getVersion(), builder); return builder.build(); } catch (VersionNotFoundException e) { @@ -404,17 +402,22 @@ private VersionMetaData getArtifactVersionMetaDataByContent(String groupId, Stri if (canonical == null) { canonical = Boolean.FALSE; } + + String contentType = getContentType(); ContentHandle content = ContentHandle.create(data); if (content.bytes().length == 0) { throw new BadRequestException(EMPTY_CONTENT_ERROR_MESSAGE); } if (ContentTypeUtil.isApplicationYaml(getContentType())) { content = ContentTypeUtil.yamlToJson(content); + contentType = ContentTypes.APPLICATION_JSON; } final List artifactReferenceDtos = toReferenceDtos(artifactReferences); - ArtifactVersionMetaDataDto dto = storage.getArtifactVersionMetaDataByContent(defaultGroupIdToNull(groupId), artifactId, canonical, content, artifactReferenceDtos); + TypedContent typedContent = TypedContent.create(content, contentType); + ArtifactVersionMetaDataDto dto = storage.getArtifactVersionMetaDataByContent(defaultGroupIdToNull(groupId), + artifactId, canonical, typedContent, artifactReferenceDtos); return V2ApiUtil.dtoToVersionMetaData(defaultGroupIdToNull(groupId), artifactId, dto.getArtifactType(), dto); } @@ -553,10 +556,13 @@ public void testUpdateArtifact(String groupId, String artifactId, InputStream da String ct = getContentType(); if (ContentTypeUtil.isApplicationYaml(ct)) { content = ContentTypeUtil.yamlToJson(content); + ct = ContentTypes.APPLICATION_JSON; } String artifactType = lookupArtifactType(groupId, artifactId); - rulesService.applyRules(defaultGroupIdToNull(groupId), artifactId, artifactType, content, RuleApplicationType.UPDATE, Collections.emptyList(), Collections.emptyMap()); //TODO:references not supported for testing update + TypedContent typedContent = TypedContent.create(content, ct); + rulesService.applyRules(defaultGroupIdToNull(groupId), artifactId, artifactType, typedContent, + RuleApplicationType.UPDATE, Collections.emptyList(), Collections.emptyMap()); //TODO:references not supported for testing update } /** @@ -579,12 +585,10 @@ public Response getArtifactVersion(String groupId, String artifactId, String ver } StoredArtifactVersionDto artifact = storage.getArtifactVersionContent(defaultGroupIdToNull(groupId), artifactId, version); - MediaType contentType = factory.getArtifactMediaType(metaData.getArtifactType()); - - ContentHandle contentToReturn = artifact.getContent(); + TypedContent contentToReturn = TypedContent.create(artifact.getContent(), artifact.getContentType()); contentToReturn = handleContentReferences(dereference, metaData.getArtifactType(), contentToReturn, artifact.getReferences()); - Response.ResponseBuilder builder = Response.ok(contentToReturn, contentType); + Response.ResponseBuilder builder = Response.ok(contentToReturn.getContent(), contentToReturn.getContentType()); checkIfDeprecated(metaData::getState, groupId, artifactId, version, builder); return builder.build(); } @@ -955,14 +959,15 @@ private ArtifactMetaData createArtifactWithRefs(String groupId, String xRegistry ct = ContentTypes.APPLICATION_JSON; } - String artifactType = ArtifactTypeUtil.determineArtifactType(content, xRegistryArtifactType, ct, factory.getAllArtifactTypes()); + TypedContent typedContent = TypedContent.create(content, ct); + String artifactType = ArtifactTypeUtil.determineArtifactType(typedContent, xRegistryArtifactType, factory); final List referencesAsDtos = toReferenceDtos(references); //Try to resolve the new artifact references and the nested ones (if any) - final Map resolvedReferences = storage.resolveReferences(referencesAsDtos); + final Map resolvedReferences = storage.resolveReferences(referencesAsDtos); - rulesService.applyRules(defaultGroupIdToNull(groupId), artifactId, artifactType, content, + rulesService.applyRules(defaultGroupIdToNull(groupId), artifactId, artifactType, typedContent, RuleApplicationType.CREATE, toV3Refs(references), resolvedReferences); @@ -1080,16 +1085,18 @@ private VersionMetaData createArtifactVersionWithRefs(String groupId, String art String ct = getContentType(); if (ContentTypeUtil.isApplicationYaml(ct)) { content = ContentTypeUtil.yamlToJson(content); + ct = ContentTypes.APPLICATION_JSON; } // Transform the given references into dtos and set the contentId, this will also detect if any of the passed references does not exist. final List referencesAsDtos = toReferenceDtos(references); // Try to resolve the new artifact references and the nested ones (if any) - final Map resolvedReferences = storage.resolveReferences(referencesAsDtos); + final Map resolvedReferences = storage.resolveReferences(referencesAsDtos); String artifactType = lookupArtifactType(groupId, artifactId); - rulesService.applyRules(defaultGroupIdToNull(groupId), artifactId, artifactType, content, + TypedContent typedContent = TypedContent.create(content, ct); + rulesService.applyRules(defaultGroupIdToNull(groupId), artifactId, artifactType, typedContent, RuleApplicationType.UPDATE, toV3Refs(references), resolvedReferences); EditableVersionMetaDataDto metaData = getEditableVersionMetaData(artifactName, artifactDescription); ContentWrapperDto contentDto = ContentWrapperDto.builder() @@ -1180,7 +1187,9 @@ private ArtifactMetaData handleIfExistsReturnOrUpdate(String groupId, String art String artifactName, String artifactDescription, ContentHandle content, String contentType, boolean canonical, List references) { try { - ArtifactVersionMetaDataDto mdDto = this.storage.getArtifactVersionMetaDataByContent(defaultGroupIdToNull(groupId), artifactId, canonical, content, toReferenceDtos(references)); + TypedContent typedContent = TypedContent.create(content, contentType); + ArtifactVersionMetaDataDto mdDto = this.storage.getArtifactVersionMetaDataByContent(defaultGroupIdToNull(groupId), + artifactId, canonical, typedContent, toReferenceDtos(references)); ArtifactMetaData md = V2ApiUtil.dtoToMetaData(defaultGroupIdToNull(groupId), artifactId, null, mdDto); return md; } catch (ArtifactNotFoundException nfe) { @@ -1202,9 +1211,10 @@ private ArtifactMetaData updateArtifactInternal(String groupId, String artifactI //Transform the given references into dtos and set the contentId, this will also detect if any of the passed references does not exist. final List referencesAsDtos = toReferenceDtos(references); - final Map resolvedReferences = storage.resolveReferences(referencesAsDtos); + final Map resolvedReferences = storage.resolveReferences(referencesAsDtos); - rulesService.applyRules(defaultGroupIdToNull(groupId), artifactId, artifactType, content, + TypedContent typedContent = TypedContent.create(content, contentType); + rulesService.applyRules(defaultGroupIdToNull(groupId), artifactId, artifactType, typedContent, RuleApplicationType.UPDATE, toV3Refs(references), resolvedReferences); // Extract metadata from content, then override extracted values with provided values. diff --git a/app/src/main/java/io/apicurio/registry/rest/v2/IdsResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v2/IdsResourceImpl.java index 65c69e4175..202d3fabed 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v2/IdsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v2/IdsResourceImpl.java @@ -5,6 +5,7 @@ import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.rest.HeadersHack; @@ -21,7 +22,6 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.interceptor.Interceptors; -import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import java.util.List; @@ -68,12 +68,10 @@ public Response getContentByGlobalId(long globalId, Boolean dereference) { StoredArtifactVersionDto artifact = storage.getArtifactVersionContent(globalId); - MediaType contentType = factory.getArtifactMediaType(metaData.getArtifactType()); - - ContentHandle contentToReturn = artifact.getContent(); + TypedContent contentToReturn = TypedContent.create(artifact.getContent(), artifact.getContentType()); handleContentReferences(dereference, metaData.getArtifactType(), contentToReturn, artifact.getReferences()); - Response.ResponseBuilder builder = Response.ok(contentToReturn, contentType); + Response.ResponseBuilder builder = Response.ok(contentToReturn.getContent(), contentToReturn.getContentType()); checkIfDeprecated(metaData::getState, metaData.getArtifactId(), metaData.getVersion(), builder); return builder.build(); } diff --git a/app/src/main/java/io/apicurio/registry/rest/v2/SearchResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v2/SearchResourceImpl.java index 993f8c7b64..6844839ad2 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v2/SearchResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v2/SearchResourceImpl.java @@ -5,6 +5,7 @@ import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.content.canon.ContentCanonicalizer; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; @@ -16,10 +17,11 @@ import io.apicurio.registry.storage.dto.OrderBy; import io.apicurio.registry.storage.dto.OrderDirection; import io.apicurio.registry.storage.dto.SearchFilter; +import io.apicurio.registry.types.ContentTypes; import io.apicurio.registry.types.Current; import io.apicurio.registry.types.provider.ArtifactTypeUtilProvider; import io.apicurio.registry.types.provider.ArtifactTypeUtilProviderFactory; -import io.apicurio.registry.util.ContentTypeUtil; +import io.apicurio.registry.content.util.ContentTypeUtil; import io.apicurio.registry.utils.StringUtil; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @@ -148,16 +150,19 @@ public ArtifactSearchResults searchArtifactsByContent(Boolean canonical, String canonical = Boolean.FALSE; } ContentHandle content = ContentHandle.create(data); + String contentType = getContentType(); if (content.bytes().length == 0) { throw new BadRequestException(EMPTY_CONTENT_ERROR_MESSAGE); } - if (ContentTypeUtil.isApplicationYaml(getContentType())) { + if (ContentTypeUtil.isApplicationYaml(contentType)) { content = ContentTypeUtil.yamlToJson(content); + contentType = ContentTypes.APPLICATION_JSON; } + TypedContent typedContent = TypedContent.create(content, contentType); Set filters = new HashSet(); if (canonical && artifactType != null) { - String canonicalHash = sha256Hash(canonicalizeContent(artifactType, content)); + String canonicalHash = sha256Hash(canonicalizeContent(artifactType, typedContent).getContent()); filters.add(SearchFilter.ofCanonicalHash(canonicalHash)); } else if (!canonical) { String contentHash = sha256Hash(content); @@ -188,11 +193,11 @@ private String gidOrNull(String groupId) { return groupId; } - protected ContentHandle canonicalizeContent(String artifactType, ContentHandle content) { + protected TypedContent canonicalizeContent(String artifactType, TypedContent content) { try { ArtifactTypeUtilProvider provider = factory.getArtifactTypeProvider(artifactType); ContentCanonicalizer canonicalizer = provider.getContentCanonicalizer(); - ContentHandle canonicalContent = canonicalizer.canonicalize(content, Collections.emptyMap()); + TypedContent canonicalContent = canonicalizer.canonicalize(content, Collections.emptyMap()); return canonicalContent; } catch (Exception e) { log.debug("Failed to canonicalize content of type: {}", artifactType); diff --git a/app/src/main/java/io/apicurio/registry/rest/v3/AbstractResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v3/AbstractResourceImpl.java index e73ab8793b..86d0973904 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v3/AbstractResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v3/AbstractResourceImpl.java @@ -1,22 +1,7 @@ package io.apicurio.registry.rest.v3; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import jakarta.inject.Inject; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.ws.rs.core.Context; - -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.slf4j.Logger; - import io.apicurio.common.apps.config.Info; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.content.dereference.ContentDereferencer; import io.apicurio.registry.content.refs.JsonPointerExternalReference; import io.apicurio.registry.rest.v3.beans.HandleReferencesType; @@ -26,6 +11,19 @@ import io.apicurio.registry.types.provider.ArtifactTypeUtilProvider; import io.apicurio.registry.types.provider.ArtifactTypeUtilProviderFactory; import io.apicurio.registry.utils.StringUtil; +import jakarta.inject.Inject; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.core.Context; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.slf4j.Logger; + +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public abstract class AbstractResourceImpl { @@ -49,19 +47,15 @@ public abstract class AbstractResourceImpl { /** * Handle the content references based on the value of "HandleReferencesType" - this can either mean * we need to fully dereference the content, or we need to rewrite the references, or we do nothing. - * @param referencesType - * @param metaData - * @param artifact - * @param content */ - protected ContentHandle handleContentReferences(HandleReferencesType referencesType, String artifactType, - ContentHandle content, List references) { + protected TypedContent handleContentReferences(HandleReferencesType referencesType, String artifactType, + TypedContent content, List references) { // Dereference or rewrite references if (!references.isEmpty()) { if (referencesType == HandleReferencesType.DEREFERENCE) { ArtifactTypeUtilProvider artifactTypeProvider = factory.getArtifactTypeProvider(artifactType); ContentDereferencer contentDereferencer = artifactTypeProvider.getContentDereferencer(); - Map resolvedReferences = storage.resolveReferences(references); + Map resolvedReferences = storage.resolveReferences(references); content = contentDereferencer.dereference(content, resolvedReferences); } else if (referencesType == HandleReferencesType.REWRITE) { ArtifactTypeUtilProvider artifactTypeProvider = factory.getArtifactTypeProvider(artifactType); @@ -129,7 +123,6 @@ protected String resolveReferenceUrl(ArtifactReferenceDto reference) { /** * Resolves a host name from the information found in X-Forwarded-Host and X-Forwarded-Proto. - * @param path */ private static URI getApiBaseHrefFromXForwarded(HttpServletRequest request) throws URISyntaxException { String fproto = request.getHeader("X-Forwarded-Proto"); @@ -143,7 +136,6 @@ private static URI getApiBaseHrefFromXForwarded(HttpServletRequest request) thro /** * Resolves a host name from the request information. - * @param path */ private static URI getApiBaseHrefFromRequest(HttpServletRequest request) throws URISyntaxException { String requestUrl = request.getRequestURL().toString(); diff --git a/app/src/main/java/io/apicurio/registry/rest/v3/AdminResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v3/AdminResourceImpl.java index e8131110fe..cee581f721 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v3/AdminResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v3/AdminResourceImpl.java @@ -71,8 +71,8 @@ import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_RULE; import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_RULE_TYPE; import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_UPDATE_ROLE; -import static io.apicurio.registry.util.DtoUtil.appAuthPropertyToRegistry; -import static io.apicurio.registry.util.DtoUtil.registryAuthPropertyToApp; +import static io.apicurio.registry.utils.DtoUtil.appAuthPropertyToRegistry; +import static io.apicurio.registry.utils.DtoUtil.registryAuthPropertyToApp; @ApplicationScoped @Interceptors({ResponseErrorLivenessCheck.class, ResponseTimeoutReadinessCheck.class}) diff --git a/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java index 56d10b59bd..9bac74b905 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java @@ -6,6 +6,7 @@ import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.model.BranchId; @@ -71,6 +72,7 @@ import io.apicurio.registry.types.ReferenceType; import io.apicurio.registry.types.RuleType; import io.apicurio.registry.types.VersionState; +import io.apicurio.registry.types.provider.ArtifactTypeUtilProviderFactory; import io.apicurio.registry.util.ArtifactIdGenerator; import io.apicurio.registry.util.ArtifactTypeUtil; import io.apicurio.registry.utils.ArtifactIdValidator; @@ -124,6 +126,9 @@ public class GroupsResourceImpl extends AbstractResourceImpl implements GroupsRe @Inject RulesService rulesService; + @Inject + ArtifactTypeUtilProviderFactory factory; + @Inject ArtifactIdGenerator idGenerator; @@ -407,10 +412,10 @@ public Response getArtifactVersionContent(String groupId, String artifactId, Str } StoredArtifactVersionDto artifact = storage.getArtifactVersionContent(gav.getRawGroupIdWithNull(), gav.getRawArtifactId(), gav.getRawVersionId()); - ContentHandle contentToReturn = artifact.getContent(); + TypedContent contentToReturn = TypedContent.create(artifact.getContent(), artifact.getContentType()); contentToReturn = handleContentReferences(references, metaData.getArtifactType(), contentToReturn, artifact.getReferences()); - Response.ResponseBuilder builder = Response.ok(contentToReturn, artifact.getContentType()); + Response.ResponseBuilder builder = Response.ok(contentToReturn.getContent(), artifact.getContentType()); checkIfDeprecated(metaData::getState, groupId, artifactId, versionExpression, builder); return builder.build(); } @@ -644,17 +649,19 @@ public CreateArtifactResponse createArtifact(String groupId, IfArtifactExists if } else if (!ArtifactIdValidator.isArtifactIdAllowed(artifactId)) { throw new InvalidArtifactIdException(ArtifactIdValidator.ARTIFACT_ID_ERROR_MESSAGE); } + TypedContent typedContent = TypedContent.create(content, contentType); - String artifactType = ArtifactTypeUtil.determineArtifactType(content, data.getArtifactType(), contentType, factory.getAllArtifactTypes()); + String artifactType = ArtifactTypeUtil.determineArtifactType(typedContent, data.getArtifactType(), factory); // Convert references to DTOs final List referencesAsDtos = toReferenceDtos(references); // Try to resolve the references - final Map resolvedReferences = storage.resolveReferences(referencesAsDtos); + final Map resolvedReferences = storage.resolveReferences(referencesAsDtos); // Apply any configured rules - rulesService.applyRules(new GroupId(groupId).getRawGroupIdWithNull(), artifactId, artifactType, content, RuleApplicationType.CREATE, references, resolvedReferences); + rulesService.applyRules(new GroupId(groupId).getRawGroupIdWithNull(), artifactId, artifactType, typedContent, + RuleApplicationType.CREATE, references, resolvedReferences); // Create the artifact (with optional first version) EditableArtifactMetaDataDto artifactMetaData = EditableArtifactMetaDataDto.builder() @@ -704,11 +711,6 @@ public CreateArtifactResponse createArtifact(String groupId, IfArtifactExists if artifactId, artifactType, artifactMetaData, firstVersion, firstVersionContent, firstVersionMetaData, firstVersionBranches); - // TODO the branches should be created in the storage -// for (String rawBranchId : normalizeMultiValuedHeader(artifactBranches)) { -// storage.createOrUpdateArtifactBranch(new GAV(groupId, artifactId, vmd.getVersion()), new BranchId(rawBranchId)); -// } - // Now return both the artifact metadata and (if available) the version metadata CreateArtifactResponse rval = CreateArtifactResponse.builder() .artifact(V3ApiUtil.dtoToArtifactMetaData(storageResult.getLeft())) @@ -775,10 +777,11 @@ public VersionMetaData createArtifactVersion(String groupId, String artifactId, final List referencesAsDtos = toReferenceDtos(data.getContent().getReferences()); // Try to resolve the new artifact references and the nested ones (if any) - final Map resolvedReferences = storage.resolveReferences(referencesAsDtos); + final Map resolvedReferences = storage.resolveReferences(referencesAsDtos); String artifactType = lookupArtifactType(groupId, artifactId); - rulesService.applyRules(new GroupId(groupId).getRawGroupIdWithNull(), artifactId, artifactType, content, + TypedContent typedContent = TypedContent.create(content, ct); + rulesService.applyRules(new GroupId(groupId).getRawGroupIdWithNull(), artifactId, artifactType, typedContent, RuleApplicationType.UPDATE, data.getContent().getReferences(), resolvedReferences); EditableVersionMetaDataDto metaDataDto = EditableVersionMetaDataDto.builder() .description(data.getDescription()) @@ -812,11 +815,6 @@ public VersionMetaData createArtifactVersion(String groupId, String artifactId, ArtifactVersionMetaDataDto vmd = storage.createArtifactVersion(new GroupId(groupId).getRawGroupIdWithNull(), artifactId, data.getVersion(), artifactType, contentDto, metaDataDto, data.getBranches()); - // TODO branches should be created in the storage (in the Tx) -// for (String rawBranchId : normalizeMultiValuedHeader(artifactBranches)) { -// storage.createOrUpdateArtifactBranch(new GAV(groupId, artifactId, vmd.getVersion()), new BranchId(rawBranchId)); -// } - return V3ApiUtil.dtoToVersionMetaData(vmd); } @@ -1046,7 +1044,7 @@ private CreateArtifactResponse handleIfExists(String groupId, String artifactId, private CreateArtifactResponse handleIfExistsReturnOrUpdate(String groupId, String artifactId, CreateVersion theVersion, boolean canonical) { try { // Find the version - ContentHandle content = ContentHandle.create(theVersion.getContent().getContent()); + TypedContent content = TypedContent.create(ContentHandle.create(theVersion.getContent().getContent()), theVersion.getContent().getContentType()); List referenceDtos = toReferenceDtos(theVersion.getContent().getReferences()); ArtifactVersionMetaDataDto vmdDto = this.storage.getArtifactVersionMetaDataByContent( new GroupId(groupId).getRawGroupIdWithNull(), artifactId, canonical, content, referenceDtos); @@ -1082,9 +1080,9 @@ private CreateArtifactResponse updateArtifactInternal(String groupId, String art //Transform the given references into dtos and set the contentId, this will also detect if any of the passed references does not exist. final List referencesAsDtos = toReferenceDtos(references); - final Map resolvedReferences = storage.resolveReferences(referencesAsDtos); - - rulesService.applyRules(new GroupId(groupId).getRawGroupIdWithNull(), artifactId, artifactType, content, + final Map resolvedReferences = storage.resolveReferences(referencesAsDtos); + final TypedContent typedContent = TypedContent.create(content, contentType); + rulesService.applyRules(new GroupId(groupId).getRawGroupIdWithNull(), artifactId, artifactType, typedContent, RuleApplicationType.UPDATE, references, resolvedReferences); EditableVersionMetaDataDto metaData = EditableVersionMetaDataDto.builder() .name(name) @@ -1104,11 +1102,6 @@ private CreateArtifactResponse updateArtifactInternal(String groupId, String art ArtifactMetaDataDto amdDto = this.storage.getArtifactMetaData(groupId, artifactId); ArtifactMetaData amd = V3ApiUtil.dtoToArtifactMetaData(amdDto); - // TODO branches should be created in the storage in a Tx -// for (String rawBranchId : normalizeMultiValuedHeader(branches)) { -// storage.createOrUpdateArtifactBranch(new GAV(groupId, artifactId, dto.getVersion()), new BranchId(rawBranchId)); -// } - return CreateArtifactResponse.builder() .artifact(amd) .version(vmd) diff --git a/app/src/main/java/io/apicurio/registry/rest/v3/IdsResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v3/IdsResourceImpl.java index 0c8cd9eb44..3e7a15ede2 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v3/IdsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v3/IdsResourceImpl.java @@ -5,6 +5,7 @@ import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.rest.HeadersHack; @@ -21,7 +22,6 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.interceptor.Interceptors; -import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import java.util.List; @@ -68,12 +68,10 @@ public Response getContentByGlobalId(long globalId, HandleReferencesType referen StoredArtifactVersionDto artifact = storage.getArtifactVersionContent(globalId); - MediaType contentType = factory.getArtifactMediaType(metaData.getArtifactType()); - - ContentHandle contentToReturn = artifact.getContent(); + TypedContent contentToReturn = TypedContent.create(artifact.getContent(), artifact.getContentType()); contentToReturn = handleContentReferences(references, metaData.getArtifactType(), contentToReturn, artifact.getReferences()); - Response.ResponseBuilder builder = Response.ok(contentToReturn, contentType); + Response.ResponseBuilder builder = Response.ok(contentToReturn.getContent(), contentToReturn.getContentType()); checkIfDeprecated(metaData::getState, metaData.getArtifactId(), metaData.getVersion(), builder); return builder.build(); } diff --git a/app/src/main/java/io/apicurio/registry/rest/v3/SearchResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v3/SearchResourceImpl.java index 93404f43cc..d1cb444858 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v3/SearchResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v3/SearchResourceImpl.java @@ -5,6 +5,7 @@ import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.model.GroupId; @@ -24,7 +25,6 @@ import io.apicurio.registry.storage.dto.VersionSearchResultsDto; import io.apicurio.registry.storage.impl.sql.RegistryStorageContentUtils; import io.apicurio.registry.types.Current; -import io.apicurio.registry.util.ContentTypeUtil; import io.apicurio.registry.utils.StringUtil; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @@ -145,13 +145,12 @@ public ArtifactSearchResults searchArtifactsByContent(Boolean canonical, String if (content.bytes().length == 0) { throw new BadRequestException(EMPTY_CONTENT_ERROR_MESSAGE); } - if (ContentTypeUtil.isApplicationYaml(getContentType())) { - content = ContentTypeUtil.yamlToJson(content); - } + String ct = getContentType(); + TypedContent typedContent = TypedContent.create(content, ct); Set filters = new HashSet(); if (canonical && artifactType != null) { - String canonicalHash = contentUtils.getCanonicalContentHash(content, artifactType, null, null); + String canonicalHash = contentUtils.getCanonicalContentHash(typedContent, artifactType, null, null); filters.add(SearchFilter.ofCanonicalHash(canonicalHash)); } else if (!canonical) { String contentHash = content.getSha256Hash(); @@ -319,8 +318,11 @@ public VersionSearchResults searchVersionsByContent(Boolean canonical, String ar if (content.bytes().length == 0) { throw new BadRequestException(EMPTY_CONTENT_ERROR_MESSAGE); } + String ct = getContentType(); + TypedContent typedContent = TypedContent.create(content, ct); + if (canonical && artifactType != null) { - String canonicalHash = contentUtils.getCanonicalContentHash(content, artifactType, null, null); + String canonicalHash = contentUtils.getCanonicalContentHash(typedContent, artifactType, null, null); filters.add(SearchFilter.ofCanonicalHash(canonicalHash)); } else if (!canonical) { String contentHash = content.getSha256Hash(); diff --git a/app/src/main/java/io/apicurio/registry/rules/RuleContext.java b/app/src/main/java/io/apicurio/registry/rules/RuleContext.java index 7361f5f762..06f0734c10 100644 --- a/app/src/main/java/io/apicurio/registry/rules/RuleContext.java +++ b/app/src/main/java/io/apicurio/registry/rules/RuleContext.java @@ -1,103 +1,31 @@ package io.apicurio.registry.rules; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rest.v3.beans.ArtifactReference; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; import java.util.List; import java.util.Map; -import java.util.Objects; /** * Contains all of the information needed by a rule executor, including the rule-specific * configuration, current and updated content, and any other meta-data needed. * */ +@Getter +@Setter +@Builder +@AllArgsConstructor public class RuleContext { private final String groupId; private final String artifactId; private final String artifactType; private final String configuration; - private final List currentContent; - private final ContentHandle updatedContent; + private final List currentContent; + private final TypedContent updatedContent; private final List references; - private final Map resolvedReferences; - - /** - * Constructor. - * - * @param groupId - * @param artifactId - * @param artifactType - * @param configuration - * @param currentContent - * @param updatedContent - */ - public RuleContext(String groupId, String artifactId, String artifactType, String configuration, - List currentContent, ContentHandle updatedContent, - List references, Map resolvedReferences) { - this.groupId = groupId; - this.artifactId = Objects.requireNonNull(artifactId); - this.artifactType = Objects.requireNonNull(artifactType); - this.configuration = Objects.requireNonNull(configuration); - this.currentContent = currentContent; // Current Content will be null when creating an artifact. - this.updatedContent = Objects.requireNonNull(updatedContent); - this.references = Objects.requireNonNull(references); - this.resolvedReferences = Objects.requireNonNull(resolvedReferences); - } - - /** - * @return the groupId - */ - public String getGroupId() { - return groupId; - } - - /** - * @return the artifactId - */ - public String getArtifactId() { - return artifactId; - } - - /** - * @return the artifactType - */ - public String getArtifactType() { - return artifactType; - } - - /** - * @return the configuration - */ - public String getConfiguration() { - return configuration; - } - - /** - * @return the currentContent - */ - public List getCurrentContent() { - return currentContent; - } - - /** - * @return the updatedContent - */ - public ContentHandle getUpdatedContent() { - return updatedContent; - } - - /** - * @return the references - */ - public Map getResolvedReferences() { - return resolvedReferences; - } - - /** - * @return the references - */ - public List getReferences() { - return references; - } + private final Map resolvedReferences; } diff --git a/app/src/main/java/io/apicurio/registry/rules/RulesService.java b/app/src/main/java/io/apicurio/registry/rules/RulesService.java index 2e442524f5..46fa7cd679 100644 --- a/app/src/main/java/io/apicurio/registry/rules/RulesService.java +++ b/app/src/main/java/io/apicurio/registry/rules/RulesService.java @@ -1,17 +1,16 @@ package io.apicurio.registry.rules; -import java.util.List; -import java.util.Map; - -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rest.v3.beans.ArtifactReference; import io.apicurio.registry.types.RuleType; +import java.util.List; +import java.util.Map; + /** * A service used to apply configured rules to a given content update. In other words, * when artifact content is being created or updated, this service is used to apply * any rules configured for the artifact. - * */ public interface RulesService { @@ -20,22 +19,22 @@ public interface RulesService { * @param groupId * @param artifactId * @param artifactType - * @param artifactContent + * @param content * @param ruleApplicationType * @param references * @param resolvedReferences * @throws RuleViolationException */ - public void applyRules(String groupId, String artifactId, String artifactType, ContentHandle artifactContent, + public void applyRules(String groupId, String artifactId, String artifactType, TypedContent content, RuleApplicationType ruleApplicationType, List references, - Map resolvedReferences) throws RuleViolationException; + Map resolvedReferences) throws RuleViolationException; /** * Applies a single, specific rule to the content update for the given artifact. * @param groupId * @param artifactId * @param artifactType - * @param artifactContent + * @param content * @param ruleType * @param ruleConfiguration * @param ruleApplicationType @@ -43,9 +42,9 @@ public void applyRules(String groupId, String artifactId, String artifactType, C * @param resolvedReferences * @throws RuleViolationException */ - public void applyRule(String groupId, String artifactId, String artifactType, ContentHandle artifactContent, + public void applyRule(String groupId, String artifactId, String artifactType, TypedContent content, RuleType ruleType, String ruleConfiguration, RuleApplicationType ruleApplicationType, - List references, Map resolvedReferences) + List references, Map resolvedReferences) throws RuleViolationException; /** @@ -59,12 +58,8 @@ public void applyRule(String groupId, String artifactId, String artifactType, Co * @param resolvedReferences * @throws RuleViolationException */ - public void applyRules(String groupId, String artifactId, String artifactVersion, String artifactType, - ContentHandle updatedContent, List references, Map resolvedReferences) + public void applyRules(String groupId, String artifactId, String artifactVersion, String artifactType, + TypedContent updatedContent, List references, + Map resolvedReferences) throws RuleViolationException; - - - public void applyRulesCompat(String groupId, String artifactId, String artifactVersion, String artifactType, - ContentHandle updatedContent, List references, - Map resolvedReferences) throws RuleViolationException; } diff --git a/app/src/main/java/io/apicurio/registry/rules/RulesServiceImpl.java b/app/src/main/java/io/apicurio/registry/rules/RulesServiceImpl.java index 793526c867..5804c70423 100644 --- a/app/src/main/java/io/apicurio/registry/rules/RulesServiceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rules/RulesServiceImpl.java @@ -1,17 +1,14 @@ package io.apicurio.registry.rules; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rest.v3.beans.ArtifactReference; -import io.apicurio.registry.content.canon.ContentCanonicalizer; import io.apicurio.registry.storage.RegistryStorage; import io.apicurio.registry.storage.dto.LazyContentList; import io.apicurio.registry.storage.dto.RuleConfigurationDto; import io.apicurio.registry.storage.dto.StoredArtifactVersionDto; import io.apicurio.registry.types.Current; import io.apicurio.registry.types.RuleType; -import io.apicurio.registry.types.provider.ArtifactTypeUtilProvider; import io.apicurio.registry.types.provider.ArtifactTypeUtilProviderFactory; - import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @@ -41,12 +38,12 @@ public class RulesServiceImpl implements RulesService { ArtifactTypeUtilProviderFactory providerFactory; /** - * @see io.apicurio.registry.rules.RulesService#applyRules(java.lang.String, java.lang.String, java.lang.String, io.apicurio.registry.content.ContentHandle, io.apicurio.registry.rules.RuleApplicationType, java.util.List, java.util.Map) + * @see io.apicurio.registry.rules.RulesService#applyRules(String, String, String, TypedContent, RuleApplicationType, List, Map) */ @Override - public void applyRules(String groupId, String artifactId, String artifactType, ContentHandle artifactContent, + public void applyRules(String groupId, String artifactId, String artifactType, TypedContent content, RuleApplicationType ruleApplicationType, List references, - Map resolvedReferences) throws RuleViolationException { + Map resolvedReferences) throws RuleViolationException { @SuppressWarnings("unchecked") List rules = Collections.EMPTY_LIST; if (ruleApplicationType == RuleApplicationType.UPDATE) { @@ -59,12 +56,13 @@ public void applyRules(String groupId, String artifactId, String artifactType, C currentContent = new LazyContentList(storage, Collections.emptyList()); } - applyGlobalAndArtifactRules(groupId, artifactId, artifactType, currentContent, artifactContent, rules, references, resolvedReferences); + applyGlobalAndArtifactRules(groupId, artifactId, artifactType, currentContent, content, rules, references, resolvedReferences); } private void applyGlobalAndArtifactRules(String groupId, String artifactId, String artifactType, - List currentArtifactContent, ContentHandle updatedArtifactContent, - List artifactRules, List references, Map resolvedReferences) { + List currentContent, TypedContent updatedContent, + List artifactRules, List references, + Map resolvedReferences) { Map globalOrArtifactRulesMap = artifactRules.stream() .collect(Collectors.toMap(ruleType -> ruleType, ruleType -> storage.getArtifactRule(groupId, artifactId, ruleType))); @@ -85,60 +83,57 @@ private void applyGlobalAndArtifactRules(String groupId, String artifactId, Stri } for (RuleType ruleType : globalOrArtifactRulesMap.keySet()) { - applyRule(groupId, artifactId, artifactType, currentArtifactContent, updatedArtifactContent, ruleType, + applyRule(groupId, artifactId, artifactType, currentContent, updatedContent, ruleType, globalOrArtifactRulesMap.get(ruleType).getConfiguration(), references, resolvedReferences); } } /** - * @see io.apicurio.registry.rules.RulesService#applyRule(java.lang.String, java.lang.String, java.lang.String, io.apicurio.registry.content.ContentHandle, io.apicurio.registry.types.RuleType, java.lang.String, io.apicurio.registry.rules.RuleApplicationType, java.util.List, java.util.Map) + * @see io.apicurio.registry.rules.RulesService#applyRule(String, String, String, TypedContent, RuleType, String, RuleApplicationType, List, Map) */ @Override - public void applyRule(String groupId, String artifactId, String artifactType, ContentHandle artifactContent, + public void applyRule(String groupId, String artifactId, String artifactType, TypedContent content, RuleType ruleType, String ruleConfiguration, RuleApplicationType ruleApplicationType, - List references, Map resolvedReferences) + List references, Map resolvedReferences) throws RuleViolationException { LazyContentList currentContent = null; if (ruleApplicationType == RuleApplicationType.UPDATE) { currentContent = new LazyContentList(storage, storage.getEnabledArtifactContentIds(groupId, artifactId)); } - applyRule(groupId, artifactId, artifactType, currentContent, artifactContent, ruleType, ruleConfiguration, + applyRule(groupId, artifactId, artifactType, currentContent, content, ruleType, ruleConfiguration, references, resolvedReferences); } /** * Applies a single rule. Throws an exception if the rule is violated. */ - private void applyRule(String groupId, String artifactId, String artifactType, List currentContent, - ContentHandle updatedContent, RuleType ruleType, String ruleConfiguration, - List references, Map resolvedReferences) { + private void applyRule(String groupId, String artifactId, String artifactType, List currentContent, + TypedContent updatedContent, RuleType ruleType, String ruleConfiguration, + List references, Map resolvedReferences) { RuleExecutor executor = factory.createExecutor(ruleType); - RuleContext context = new RuleContext(groupId, artifactId, artifactType, ruleConfiguration, currentContent, - updatedContent, references, resolvedReferences); + RuleContext context = RuleContext.builder() + .groupId(groupId) + .artifactId(artifactId) + .artifactType(artifactType) + .currentContent(currentContent) + .updatedContent(updatedContent) + .configuration(ruleConfiguration) + .references(references) + .resolvedReferences(resolvedReferences) + .build(); executor.execute(context); } /** - * @see io.apicurio.registry.rules.RulesService#applyRules(java.lang.String, java.lang.String, java.lang.String, java.lang.String, io.apicurio.registry.content.ContentHandle, java.util.List, java.util.Map) + * @see io.apicurio.registry.rules.RulesService#applyRules(String, String, String, String, TypedContent, List, Map) */ @Override public void applyRules(String groupId, String artifactId, String artifactVersion, String artifactType, - ContentHandle updatedContent, List references, - Map resolvedReferences) throws RuleViolationException { - StoredArtifactVersionDto versionContent = storage.getArtifactVersionContent(groupId, artifactId, artifactVersion); - applyGlobalAndArtifactRules(groupId, artifactId, artifactType, Collections.singletonList(versionContent.getContent()), - updatedContent, storage.getArtifactRules(groupId, artifactId), references, resolvedReferences); - } - - @Override - public void applyRulesCompat(String groupId, String artifactId, String artifactVersion, String artifactType, - ContentHandle updatedContent, List references, - Map resolvedReferences) throws RuleViolationException { - ArtifactTypeUtilProvider artifactTypeProvider = providerFactory.getArtifactTypeProvider(artifactType); - ContentCanonicalizer contentCanonicalizer = artifactTypeProvider.getContentCanonicalizer(); + TypedContent updatedContent, List references, + Map resolvedReferences) throws RuleViolationException { StoredArtifactVersionDto versionContent = storage.getArtifactVersionContent(groupId, artifactId, artifactVersion); - applyGlobalAndArtifactRules(groupId, artifactId, artifactType, - Collections.singletonList(contentCanonicalizer.canonicalize(versionContent.getContent(), Map.of())), + TypedContent typedVersionContent = TypedContent.create(versionContent.getContent(), versionContent.getContentType()); + applyGlobalAndArtifactRules(groupId, artifactId, artifactType, Collections.singletonList(typedVersionContent), updatedContent, storage.getArtifactRules(groupId, artifactId), references, resolvedReferences); } } diff --git a/app/src/main/java/io/apicurio/registry/rules/compatibility/CompatibilityRuleExecutor.java b/app/src/main/java/io/apicurio/registry/rules/compatibility/CompatibilityRuleExecutor.java index c1ac5bdab9..6028f41e76 100644 --- a/app/src/main/java/io/apicurio/registry/rules/compatibility/CompatibilityRuleExecutor.java +++ b/app/src/main/java/io/apicurio/registry/rules/compatibility/CompatibilityRuleExecutor.java @@ -1,7 +1,7 @@ package io.apicurio.registry.rules.compatibility; import io.apicurio.common.apps.logging.Logged; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rules.RuleContext; import io.apicurio.registry.rules.RuleExecutor; import io.apicurio.registry.rules.RuleViolation; @@ -9,9 +9,9 @@ import io.apicurio.registry.types.RuleType; import io.apicurio.registry.types.provider.ArtifactTypeUtilProvider; import io.apicurio.registry.types.provider.ArtifactTypeUtilProviderFactory; - import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; + import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -41,7 +41,7 @@ public void execute(RuleContext context) throws RuleViolationException { CompatibilityLevel level = CompatibilityLevel.valueOf(context.getConfiguration()); ArtifactTypeUtilProvider provider = factory.getArtifactTypeProvider(context.getArtifactType()); CompatibilityChecker checker = provider.getCompatibilityChecker(); - List existingArtifacts = context.getCurrentContent() != null + List existingArtifacts = context.getCurrentContent() != null ? context.getCurrentContent() : emptyList(); CompatibilityExecutionResult compatibilityExecutionResult = checker.testCompatibility( level, diff --git a/app/src/main/java/io/apicurio/registry/rules/integrity/IntegrityRuleExecutor.java b/app/src/main/java/io/apicurio/registry/rules/integrity/IntegrityRuleExecutor.java index 591f3b35b6..ba0a364881 100644 --- a/app/src/main/java/io/apicurio/registry/rules/integrity/IntegrityRuleExecutor.java +++ b/app/src/main/java/io/apicurio/registry/rules/integrity/IntegrityRuleExecutor.java @@ -1,15 +1,7 @@ package io.apicurio.registry.rules.integrity; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; - import io.apicurio.common.apps.logging.Logged; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rest.v3.beans.ArtifactReference; import io.apicurio.registry.rules.RuleContext; import io.apicurio.registry.rules.RuleExecutor; @@ -19,6 +11,13 @@ import io.apicurio.registry.types.RuleType; import io.apicurio.registry.types.provider.ArtifactTypeUtilProvider; import io.apicurio.registry.types.provider.ArtifactTypeUtilProviderFactory; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; @ApplicationScoped @Logged @@ -60,7 +59,7 @@ private void verifyAllReferencesHaveMappings(RuleContext context) throws RuleVio private void validateReferencesExist(RuleContext context) throws RuleViolationException { List references = context.getReferences(); - Map resolvedReferences = context.getResolvedReferences(); + Map resolvedReferences = context.getResolvedReferences(); Set causes = new HashSet<>(); references.forEach(ref -> { diff --git a/app/src/main/java/io/apicurio/registry/storage/RegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/RegistryStorage.java index e3a07f3e62..01991bf7ca 100644 --- a/app/src/main/java/io/apicurio/registry/storage/RegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/RegistryStorage.java @@ -2,13 +2,41 @@ import io.apicurio.common.apps.config.DynamicConfigPropertyDto; import io.apicurio.common.apps.config.DynamicConfigStorage; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.model.BranchId; import io.apicurio.registry.model.GA; import io.apicurio.registry.model.GAV; import io.apicurio.registry.model.VersionId; -import io.apicurio.registry.storage.dto.*; -import io.apicurio.registry.storage.error.*; +import io.apicurio.registry.storage.dto.ArtifactMetaDataDto; +import io.apicurio.registry.storage.dto.ArtifactReferenceDto; +import io.apicurio.registry.storage.dto.ArtifactSearchResultsDto; +import io.apicurio.registry.storage.dto.ArtifactVersionMetaDataDto; +import io.apicurio.registry.storage.dto.CommentDto; +import io.apicurio.registry.storage.dto.ContentWrapperDto; +import io.apicurio.registry.storage.dto.DownloadContextDto; +import io.apicurio.registry.storage.dto.EditableArtifactMetaDataDto; +import io.apicurio.registry.storage.dto.EditableGroupMetaDataDto; +import io.apicurio.registry.storage.dto.EditableVersionMetaDataDto; +import io.apicurio.registry.storage.dto.GroupMetaDataDto; +import io.apicurio.registry.storage.dto.GroupSearchResultsDto; +import io.apicurio.registry.storage.dto.OrderBy; +import io.apicurio.registry.storage.dto.OrderDirection; +import io.apicurio.registry.storage.dto.RoleMappingDto; +import io.apicurio.registry.storage.dto.RoleMappingSearchResultsDto; +import io.apicurio.registry.storage.dto.RuleConfigurationDto; +import io.apicurio.registry.storage.dto.SearchFilter; +import io.apicurio.registry.storage.dto.StoredArtifactVersionDto; +import io.apicurio.registry.storage.dto.VersionSearchResultsDto; +import io.apicurio.registry.storage.error.ArtifactAlreadyExistsException; +import io.apicurio.registry.storage.error.ArtifactNotFoundException; +import io.apicurio.registry.storage.error.ContentNotFoundException; +import io.apicurio.registry.storage.error.GroupAlreadyExistsException; +import io.apicurio.registry.storage.error.GroupNotFoundException; +import io.apicurio.registry.storage.error.RegistryStorageException; +import io.apicurio.registry.storage.error.RuleAlreadyExistsException; +import io.apicurio.registry.storage.error.RuleNotFoundException; +import io.apicurio.registry.storage.error.VersionAlreadyExistsException; +import io.apicurio.registry.storage.error.VersionNotFoundException; import io.apicurio.registry.storage.impexp.EntityInputStream; import io.apicurio.registry.types.RuleType; import io.apicurio.registry.utils.impexp.ArtifactBranchEntity; @@ -198,7 +226,7 @@ ArtifactMetaDataDto getArtifactMetaData(String groupId, String artifactId) * @throws RegistryStorageException */ ArtifactVersionMetaDataDto getArtifactVersionMetaDataByContent(String groupId, String artifactId, boolean canonical, - ContentHandle content, List artifactReferences) throws ArtifactNotFoundException, RegistryStorageException; + TypedContent content, List artifactReferences) throws ArtifactNotFoundException, RegistryStorageException; /** * Updates the stored meta-data for an artifact by group and ID. Only the client-editable meta-data can be updated. Client @@ -633,7 +661,7 @@ VersionSearchResultsDto searchVersions(Set filters, OrderBy orderB /** * @return The artifact references resolved as a map containing the reference name as key and the referenced artifact content. */ - Map resolveReferences(List references); + Map resolveReferences(List references); /** * Quickly checks for the existence of a given artifact. diff --git a/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorReadOnlyBase.java b/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorReadOnlyBase.java index e569d8a6f1..0da4d6b8c3 100644 --- a/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorReadOnlyBase.java +++ b/app/src/main/java/io/apicurio/registry/storage/decorator/RegistryStorageDecoratorReadOnlyBase.java @@ -1,12 +1,27 @@ package io.apicurio.registry.storage.decorator; import io.apicurio.common.apps.config.DynamicConfigPropertyDto; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.model.BranchId; import io.apicurio.registry.model.GA; import io.apicurio.registry.model.GAV; import io.apicurio.registry.storage.RegistryStorage; -import io.apicurio.registry.storage.dto.*; +import io.apicurio.registry.storage.dto.ArtifactMetaDataDto; +import io.apicurio.registry.storage.dto.ArtifactReferenceDto; +import io.apicurio.registry.storage.dto.ArtifactSearchResultsDto; +import io.apicurio.registry.storage.dto.ArtifactVersionMetaDataDto; +import io.apicurio.registry.storage.dto.CommentDto; +import io.apicurio.registry.storage.dto.ContentWrapperDto; +import io.apicurio.registry.storage.dto.GroupMetaDataDto; +import io.apicurio.registry.storage.dto.GroupSearchResultsDto; +import io.apicurio.registry.storage.dto.OrderBy; +import io.apicurio.registry.storage.dto.OrderDirection; +import io.apicurio.registry.storage.dto.RoleMappingDto; +import io.apicurio.registry.storage.dto.RoleMappingSearchResultsDto; +import io.apicurio.registry.storage.dto.RuleConfigurationDto; +import io.apicurio.registry.storage.dto.SearchFilter; +import io.apicurio.registry.storage.dto.StoredArtifactVersionDto; +import io.apicurio.registry.storage.dto.VersionSearchResultsDto; import io.apicurio.registry.storage.error.ArtifactNotFoundException; import io.apicurio.registry.storage.error.ContentNotFoundException; import io.apicurio.registry.storage.error.GroupNotFoundException; @@ -100,7 +115,7 @@ public ArtifactMetaDataDto getArtifactMetaData(String groupId, String artifactId @Override public ArtifactVersionMetaDataDto getArtifactVersionMetaDataByContent(String groupId, String artifactId, - boolean canonical, ContentHandle content, List artifactReferences) + boolean canonical, TypedContent content, List artifactReferences) throws ArtifactNotFoundException, RegistryStorageException { return delegate.getArtifactVersionMetaDataByContent(groupId, artifactId, canonical, content, artifactReferences); } @@ -249,7 +264,7 @@ public DynamicConfigPropertyDto getRawConfigProperty(String propertyName) { } @Override - public Map resolveReferences(List references) { + public Map resolveReferences(List references) { return delegate.resolveReferences(references); } diff --git a/app/src/main/java/io/apicurio/registry/storage/dto/LazyContentList.java b/app/src/main/java/io/apicurio/registry/storage/dto/LazyContentList.java index e8ad08ed07..555f03701c 100644 --- a/app/src/main/java/io/apicurio/registry/storage/dto/LazyContentList.java +++ b/app/src/main/java/io/apicurio/registry/storage/dto/LazyContentList.java @@ -1,6 +1,6 @@ package io.apicurio.registry.storage.dto; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.storage.RegistryStorage; import org.jetbrains.annotations.NotNull; @@ -12,7 +12,7 @@ import java.util.Spliterator; import java.util.function.Consumer; -public class LazyContentList implements List { +public class LazyContentList implements List { private final RegistryStorage storage; private final List contentIds; @@ -38,23 +38,23 @@ public boolean contains(Object o) { } @Override - public ContentHandle get(int index) { + public TypedContent get(int index) { //Not the best solution, works for now... - return storage.getContentById(contentIds.get(index)).getContent(); + return toTypedContent(storage.getContentById(contentIds.get(index))); } @Override - public ContentHandle set(int index, ContentHandle element) { + public TypedContent set(int index, TypedContent element) { throw new UnsupportedOperationException(); } @Override - public void add(int index, ContentHandle element) { + public void add(int index, TypedContent element) { throw new UnsupportedOperationException(); } @Override - public ContentHandle remove(int index) { + public TypedContent remove(int index) { throw new UnsupportedOperationException(); } @@ -70,25 +70,25 @@ public int lastIndexOf(Object o) { @NotNull @Override - public ListIterator listIterator() { + public ListIterator listIterator() { throw new UnsupportedOperationException(); } @NotNull @Override - public ListIterator listIterator(int index) { + public ListIterator listIterator(int index) { throw new UnsupportedOperationException(); } @NotNull @Override - public List subList(int fromIndex, int toIndex) { + public List subList(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); } @NotNull @Override - public Iterator iterator() { + public Iterator iterator() { return new LazyContentListIterator(this, contentIds.iterator()); } @@ -105,7 +105,7 @@ public T[] toArray(@NotNull T[] a) { } @Override - public boolean add(ContentHandle contentHandle) { + public boolean add(TypedContent contentHandle) { throw new UnsupportedOperationException(); } @@ -120,12 +120,12 @@ public boolean containsAll(@NotNull Collection c) { } @Override - public boolean addAll(@NotNull Collection c) { + public boolean addAll(@NotNull Collection c) { throw new UnsupportedOperationException(); } @Override - public boolean addAll(int index, @NotNull Collection c) { + public boolean addAll(int index, @NotNull Collection c) { throw new UnsupportedOperationException(); } @@ -145,15 +145,15 @@ public void clear() { } @Override - public Spliterator spliterator() { + public Spliterator spliterator() { //prevent streaming on this list throw new UnsupportedOperationException(); } @Override - public void forEach(Consumer action) { + public void forEach(Consumer action) { for (Long contentId : contentIds) { - ContentHandle retrievedContent = storage.getContentById(contentId).getContent(); + TypedContent retrievedContent = toTypedContent(storage.getContentById(contentId)); action.accept(retrievedContent); } } @@ -162,15 +162,15 @@ public List getContentIds() { return contentIds; } - public ContentHandle getContentById(long contentId) { + public TypedContent getContentById(long contentId) { if (contentIds.contains(contentId)) { - return storage.getContentById(contentId).getContent(); + return toTypedContent(storage.getContentById(contentId)); } else { throw new NoSuchElementException(String.format("No content found with id %d", contentId)); } } - private static class LazyContentListIterator implements Iterator { + private static class LazyContentListIterator implements Iterator { private final LazyContentList lazyContentList; private final Iterator contentIdsIterator; @@ -186,7 +186,7 @@ public boolean hasNext() { } @Override - public ContentHandle next() { + public TypedContent next() { Long nextContentId = contentIdsIterator.next(); return lazyContentList.getContentById(nextContentId); } @@ -196,4 +196,8 @@ public void remove() { contentIdsIterator.remove(); } } + + private static TypedContent toTypedContent(ContentWrapperDto dto) { + return TypedContent.create(dto.getContent(), dto.getContentType()); + } } \ No newline at end of file diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/gitops/GitManager.java b/app/src/main/java/io/apicurio/registry/storage/impl/gitops/GitManager.java index 63ba76288c..5f01bd18ae 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/gitops/GitManager.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/gitops/GitManager.java @@ -2,6 +2,7 @@ import io.apicurio.common.apps.config.DynamicConfigPropertyDto; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.storage.impl.gitops.model.GitFile; import io.apicurio.registry.storage.impl.gitops.model.Type; import io.apicurio.registry.storage.impl.gitops.model.v0.Artifact; @@ -15,7 +16,7 @@ import io.apicurio.registry.types.ContentTypes; import io.apicurio.registry.types.RuleType; import io.apicurio.registry.types.VersionState; -import io.apicurio.registry.util.ContentTypeUtil; +import io.apicurio.registry.content.util.ContentTypeUtil; import io.apicurio.registry.utils.impexp.ArtifactRuleEntity; import io.apicurio.registry.utils.impexp.ArtifactVersionEntity; import io.apicurio.registry.utils.impexp.ContentEntity; @@ -374,15 +375,27 @@ private Content processContent(ProcessingState state, GitFile base, String conte data = ContentTypeUtil.yamlToJson(data); } try { + // FIXME need to better determine the content type? + String contentType = ContentTypes.APPLICATION_JSON; + if (dataFile.getPath().toLowerCase().endsWith(".yaml") || dataFile.getPath().toLowerCase().endsWith(".yml")) { + contentType = ContentTypes.APPLICATION_YAML; + } else if (dataFile.getPath().toLowerCase().endsWith(".xml") || dataFile.getPath().toLowerCase().endsWith(".wsdl") || dataFile.getPath().toLowerCase().endsWith(".xsd")) { + contentType = ContentTypes.APPLICATION_XML; + } else if (dataFile.getPath().toLowerCase().endsWith(".proto")) { + contentType = ContentTypes.APPLICATION_PROTOBUF; + } else if (dataFile.getPath().toLowerCase().endsWith(".graphql")) { + contentType = ContentTypes.APPLICATION_GRAPHQL; + } + var typedContent = TypedContent.create(data, contentType); + var e = new ContentEntity(); e.contentId = content.getId(); e.contentHash = content.getContentHash(); e.contentBytes = data.bytes(); - content.setArtifactType(utils.determineArtifactType(data, content.getArtifactType())); - e.canonicalHash = utils.getCanonicalContentHash(data, content.getArtifactType(), null, null); + content.setArtifactType(utils.determineArtifactType(typedContent, content.getArtifactType())); + e.canonicalHash = utils.getCanonicalContentHash(typedContent, content.getArtifactType(), null, null); e.artifactType = content.getArtifactType(); - // FIXME need to better determine the content type? - e.contentType = ContentTypes.APPLICATION_JSON; + e.contentType = contentType; if (contentFile.getPath().toLowerCase().endsWith(".yaml") || contentFile.getPath().toLowerCase().endsWith(".yml")) { e.contentType = ContentTypes.APPLICATION_YAML; } else if (contentFile.getPath().toLowerCase().endsWith(".xml") || contentFile.getPath().toLowerCase().endsWith(".wsdl") || contentFile.getPath().toLowerCase().endsWith(".xsd")) { diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/gitops/GitOpsRegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/gitops/GitOpsRegistryStorage.java index b7d0b47042..9ee97e527d 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/gitops/GitOpsRegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/gitops/GitOpsRegistryStorage.java @@ -4,7 +4,7 @@ import io.apicurio.common.apps.config.DynamicConfigStorage; import io.apicurio.common.apps.config.Info; import io.apicurio.common.apps.logging.Logged; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.metrics.StorageMetricsApply; import io.apicurio.registry.model.BranchId; import io.apicurio.registry.model.GA; @@ -259,7 +259,7 @@ public ArtifactMetaDataDto getArtifactMetaData(String groupId, String artifactId @Override - public ArtifactVersionMetaDataDto getArtifactVersionMetaDataByContent(String groupId, String artifactId, boolean canonical, ContentHandle content, List artifactReferences) { + public ArtifactVersionMetaDataDto getArtifactVersionMetaDataByContent(String groupId, String artifactId, boolean canonical, TypedContent content, List artifactReferences) { return proxy(storage -> storage.getArtifactVersionMetaDataByContent(groupId, artifactId, canonical, content, artifactReferences)); } @@ -424,7 +424,7 @@ public boolean isRoleMappingExists(String principalId) { @Override - public Map resolveReferences(List references) { + public Map resolveReferences(List references) { return proxy(storage -> storage.resolveReferences(references)); } diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java index ff3c520d50..5e6e0577a4 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java @@ -5,7 +5,7 @@ import io.apicurio.common.apps.config.DynamicConfigPropertyDto; import io.apicurio.common.apps.config.Info; import io.apicurio.common.apps.core.System; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.exception.UnreachableCodeException; import io.apicurio.registry.model.BranchId; import io.apicurio.registry.model.GA; @@ -87,7 +87,7 @@ import io.apicurio.registry.storage.importing.SqlDataImporter; import io.apicurio.registry.types.ArtifactState; import io.apicurio.registry.types.RuleType; -import io.apicurio.registry.util.DtoUtil; +import io.apicurio.registry.utils.DtoUtil; import io.apicurio.registry.utils.IoUtil; import io.apicurio.registry.utils.StringUtil; import io.apicurio.registry.utils.impexp.ArtifactBranchEntity; @@ -654,16 +654,15 @@ private ArtifactVersionMetaDataDto createArtifactVersionRaw(Handle handle, boole */ private Long getOrCreateContent(Handle handle, String artifactType, ContentWrapperDto contentDto) { List references = contentDto.getReferences(); - ContentHandle content = contentDto.getContent(); - String contentType = contentDto.getContentType(); + TypedContent content = TypedContent.create(contentDto.getContent(), contentDto.getContentType()); if (notEmpty(references)) { - return getOrCreateContentRaw(handle, content, contentType, + return getOrCreateContentRaw(handle, content, utils.getContentHash(content, references), utils.getCanonicalContentHash(content, artifactType, references, this::resolveReferences), references, SqlUtil.serializeReferences(references)); } else { - return getOrCreateContentRaw(handle, content, contentType, + return getOrCreateContentRaw(handle, content, utils.getContentHash(content, null), utils.getCanonicalContentHash(content, artifactType, null, null), null, null); @@ -676,9 +675,9 @@ private Long getOrCreateContent(Handle handle, String artifactType, ContentWrapp *

* IMPORTANT: Private methods can't be @Transactional. Callers MUST have started a transaction. */ - private Long getOrCreateContentRaw(Handle handle, ContentHandle content, String contentType, String contentHash, + private Long getOrCreateContentRaw(Handle handle, TypedContent content, String contentHash, String canonicalContentHash, List references, String referencesSerialized) { - byte[] contentBytes = content.bytes(); + byte[] contentBytes = content.getContent().bytes(); // Upsert a row in the "content" table. This will insert a row for the content // if a row doesn't already exist. We use the content hash to determine whether @@ -691,7 +690,7 @@ private Long getOrCreateContentRaw(Handle handle, ContentHandle content, String .bind(0, nextContentId()) .bind(1, canonicalContentHash) .bind(2, contentHash) - .bind(3, contentType) + .bind(3, content.getContentType()) .bind(4, contentBytes) .bind(5, referencesSerialized) .execute(); @@ -710,7 +709,7 @@ private Long getOrCreateContentRaw(Handle handle, ContentHandle content, String .bind(0, nextContentId()) .bind(1, canonicalContentHash) .bind(2, contentHash) - .bind(3, contentType) + .bind(3, content.getContentType()) .bind(4, contentBytes) .bind(5, referencesSerialized) .execute(); @@ -1091,7 +1090,7 @@ public ArtifactMetaDataDto getArtifactMetaData(String groupId, String artifactId * @param references may be null */ private String getContentHash(String groupId, String artifactId, boolean canonical, - ContentHandle content, List references) { + TypedContent content, List references) { if (canonical) { var artifactMetaData = getArtifactMetaData(groupId, artifactId); return utils.getCanonicalContentHash(content, artifactMetaData.getArtifactType(), @@ -1108,7 +1107,7 @@ private String getContentHash(String groupId, String artifactId, boolean canonic @Override @Transactional public ArtifactVersionMetaDataDto getArtifactVersionMetaDataByContent(String groupId, String artifactId, boolean canonical, - ContentHandle content, List references) + TypedContent content, List references) throws ArtifactNotFoundException, RegistryStorageException { String hash = getContentHash(groupId, artifactId, canonical, content, references); @@ -2585,11 +2584,11 @@ public void deleteAllUserData() { @Override @Transactional - public Map resolveReferences(List references) { + public Map resolveReferences(List references) { if (references == null || references.isEmpty()) { return Collections.emptyMap(); } else { - Map result = new LinkedHashMap<>(); + Map result = new LinkedHashMap<>(); resolveReferences(result, references); return result; } @@ -2797,7 +2796,7 @@ public GroupSearchResultsDto searchGroups(Set filters, OrderBy ord /** * IMPORTANT: Private methods can't be @Transactional. Callers MUST have started a transaction. */ - private void resolveReferences(Map resolvedReferences, List references) { + private void resolveReferences(Map resolvedReferences, List references) { if (references != null && !references.isEmpty()) { for (ArtifactReferenceDto reference : references) { if (reference.getArtifactId() == null || reference.getName() == null || reference.getVersion() == null) { @@ -2809,7 +2808,8 @@ private void resolveReferences(Map resolvedReferences, Li final ArtifactVersionMetaDataDto referencedArtifactMetaData = getArtifactVersionMetaData(reference.getGroupId(), reference.getArtifactId(), reference.getVersion()); final ContentWrapperDto referencedContent = getContentById(referencedArtifactMetaData.getContentId()); resolveReferences(resolvedReferences, referencedContent.getReferences()); - resolvedReferences.put(reference.getName(), referencedContent.getContent()); + TypedContent typedContent = TypedContent.create(referencedContent.getContent(), referencedContent.getContentType()); + resolvedReferences.put(reference.getName(), typedContent); } catch (VersionNotFoundException ex) { // Ignored } diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/RegistryStorageContentUtils.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/RegistryStorageContentUtils.java index 226fc541d1..3b4750abe4 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/RegistryStorageContentUtils.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/RegistryStorageContentUtils.java @@ -1,23 +1,22 @@ package io.apicurio.registry.storage.impl.sql; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.function.Function; - -import org.apache.commons.codec.digest.DigestUtils; -import org.slf4j.Logger; - import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.storage.dto.ArtifactReferenceDto; -import io.apicurio.registry.storage.dto.EditableVersionMetaDataDto; import io.apicurio.registry.types.RegistryException; import io.apicurio.registry.types.provider.ArtifactTypeUtilProviderFactory; import io.apicurio.registry.util.ArtifactTypeUtil; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.apache.commons.codec.digest.DigestUtils; +import org.slf4j.Logger; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Function; /** * TODO Refactor @@ -38,7 +37,7 @@ public class RegistryStorageContentUtils { * * @throws RegistryException in the case of an error. */ - public ContentHandle canonicalizeContent(String artifactType, ContentHandle content, Map resolvedReferences) { + public TypedContent canonicalizeContent(String artifactType, TypedContent content, Map resolvedReferences) { try { return factory.getArtifactTypeProvider(artifactType) .getContentCanonicalizer() @@ -46,7 +45,7 @@ public ContentHandle canonicalizeContent(String artifactType, ContentHandle cont } catch (Exception ex) { // TODO: We should consider explicitly failing when a content could not be canonicalized. // throw new RegistryException("Failed to canonicalize content.", ex); - log.debug("Failed to canonicalize content: {}", content.content()); + log.debug("Failed to canonicalize content: {}", artifactType); return content; } } @@ -57,8 +56,8 @@ public ContentHandle canonicalizeContent(String artifactType, ContentHandle cont * * @throws RegistryException in the case of an error. */ - public ContentHandle canonicalizeContent(String artifactType, ContentHandle content, List references, - Function, Map> referenceResolver) { + public TypedContent canonicalizeContent(String artifactType, TypedContent content, List references, + Function, Map> referenceResolver) { try { return canonicalizeContent(artifactType, content, referenceResolver.apply(references)); } catch (Exception ex) { @@ -71,16 +70,16 @@ public ContentHandle canonicalizeContent(String artifactType, ContentHandle cont * @param references may be null * @param referenceResolver may be null if references is null */ - public String getCanonicalContentHash(ContentHandle content, String artifactType, List references, - Function, Map> referenceResolver) { + public String getCanonicalContentHash(TypedContent content, String artifactType, List references, + Function, Map> referenceResolver) { try { if (notEmpty(references)) { String referencesSerialized = SqlUtil.serializeReferences(references); - ContentHandle canonicalContent = canonicalizeContent(artifactType, content, referenceResolver.apply(references)); - return DigestUtils.sha256Hex(concatContentAndReferences(canonicalContent.bytes(), referencesSerialized)); + TypedContent canonicalContent = canonicalizeContent(artifactType, content, referenceResolver.apply(references)); + return DigestUtils.sha256Hex(concatContentAndReferences(canonicalContent.getContent().bytes(), referencesSerialized)); } else { - ContentHandle canonicalContent = canonicalizeContent(artifactType, content, Map.of()); - return DigestUtils.sha256Hex(canonicalContent.bytes()); + TypedContent canonicalContent = canonicalizeContent(artifactType, content, Map.of()); + return DigestUtils.sha256Hex(canonicalContent.getContent().bytes()); } } catch (IOException ex) { throw new RegistryException("Failed to compute canonical content hash.", ex); @@ -91,13 +90,13 @@ public String getCanonicalContentHash(ContentHandle content, String artifactType /** * @param references may be null */ - public String getContentHash(ContentHandle content, List references) { + public String getContentHash(TypedContent content, List references) { try { if (notEmpty(references)) { String referencesSerialized = SqlUtil.serializeReferences(references); - return DigestUtils.sha256Hex(concatContentAndReferences(content.bytes(), referencesSerialized)); + return DigestUtils.sha256Hex(concatContentAndReferences(content.getContent().bytes(), referencesSerialized)); } else { - return DigestUtils.sha256Hex(content.bytes()); + return DigestUtils.sha256Hex(content.getContent().bytes()); } } catch (IOException ex) { throw new RegistryException("Failed to compute content hash.", ex); @@ -118,24 +117,8 @@ private byte[] concatContentAndReferences(byte[] contentBytes, String references } - public String determineArtifactType(ContentHandle content, String artifactTypeHint) { - return ArtifactTypeUtil.determineArtifactType(content, artifactTypeHint, null, factory.getAllArtifactTypes()); - } - - - public EditableVersionMetaDataDto extractEditableArtifactMetadata(String artifactType, ContentHandle content) { - var provider = factory.getArtifactTypeProvider(artifactType); - var extractor = provider.getContentExtractor(); - var extractedMetadata = extractor.extract(content); - if (extractedMetadata != null) { - return EditableVersionMetaDataDto.builder() - .name(extractedMetadata.getName()) - .description(extractedMetadata.getDescription()) - .labels(extractedMetadata.getLabels()) - .build(); - } else { - return EditableVersionMetaDataDto.builder().build(); - } + public String determineArtifactType(TypedContent content, String artifactTypeHint) { + return ArtifactTypeUtil.determineArtifactType(content, artifactTypeHint, null, factory); } diff --git a/app/src/main/java/io/apicurio/registry/storage/importing/SqlDataImporter.java b/app/src/main/java/io/apicurio/registry/storage/importing/SqlDataImporter.java index 4065bdc86a..b1abcf691c 100644 --- a/app/src/main/java/io/apicurio/registry/storage/importing/SqlDataImporter.java +++ b/app/src/main/java/io/apicurio/registry/storage/importing/SqlDataImporter.java @@ -1,6 +1,7 @@ package io.apicurio.registry.storage.importing; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.model.GAV; import io.apicurio.registry.storage.RegistryStorage; import io.apicurio.registry.storage.dto.ArtifactReferenceDto; @@ -122,12 +123,14 @@ public void importContent(ContentEntity entity) { throw new RuntimeException("ContentEntity is missing required field: contentType"); } + TypedContent typedContent = TypedContent.create(ContentHandle.create(entity.contentBytes), entity.contentType); + // We do not need canonicalHash if we have artifactType if (entity.canonicalHash == null && entity.artifactType != null) { - ContentHandle canonicalContent = utils.canonicalizeContent( - entity.artifactType, ContentHandle.create(entity.contentBytes), + TypedContent canonicalContent = utils.canonicalizeContent( + entity.artifactType, typedContent, storage.resolveReferences(references)); - entity.canonicalHash = DigestUtils.sha256Hex(canonicalContent.bytes()); + entity.canonicalHash = DigestUtils.sha256Hex(canonicalContent.getContent().bytes()); } diff --git a/app/src/main/java/io/apicurio/registry/util/ArtifactTypeUtil.java b/app/src/main/java/io/apicurio/registry/util/ArtifactTypeUtil.java index 2ab19d0a7b..2e56f7f42c 100644 --- a/app/src/main/java/io/apicurio/registry/util/ArtifactTypeUtil.java +++ b/app/src/main/java/io/apicurio/registry/util/ArtifactTypeUtil.java @@ -1,29 +1,15 @@ package io.apicurio.registry.util; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.protobuf.DescriptorProtos; -import graphql.schema.idl.SchemaParser; -import graphql.schema.idl.TypeDefinitionRegistry; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.storage.error.InvalidArtifactTypeException; -import io.apicurio.registry.types.ArtifactType; -import io.apicurio.registry.utils.protobuf.schema.FileDescriptorUtils; -import io.apicurio.registry.utils.protobuf.schema.ProtobufFile; -import org.apache.avro.Schema; -import org.w3c.dom.Document; -import org.w3c.dom.Element; +import io.apicurio.registry.types.provider.ArtifactTypeUtilProvider; +import io.apicurio.registry.types.provider.ArtifactTypeUtilProviderFactory; -import java.io.InputStream; -import java.util.*; -import java.util.regex.Pattern; +import java.util.Collections; +import java.util.Map; public final class ArtifactTypeUtil { - private static final Pattern QUOTED_BRACKETS = Pattern.compile(": *\"\\{}\""); - - private static final ObjectMapper mapper = new ObjectMapper(); - /** * Constructor. */ @@ -33,28 +19,26 @@ private ArtifactTypeUtil() { /** * Figures out the artifact type in the following order of precedent: *

- * 1) The provided X-Registry-String header - * 2) A hint provided in the Content-Type header - * 3) Determined from the content itself + * 1) The type provided in the request + * 2) Determined from the content itself * * @param content the content * @param artifactType the artifact type - * @param contentType content type from request API */ - //FIXME:references artifact must be dereferenced here otherwise this will fail to discover the type - public static String determineArtifactType(ContentHandle content, String artifactType, String contentType, List availableTypes) { - return determineArtifactType(content, artifactType, contentType, Collections.emptyMap(), availableTypes); + public static String determineArtifactType(TypedContent content, String artifactType, + ArtifactTypeUtilProviderFactory artifactTypeProviderFactory) { + return determineArtifactType(content, artifactType, Collections.emptyMap(), artifactTypeProviderFactory); } - public static String determineArtifactType(ContentHandle content, String artifactType, String contentType, - Map resolvedReferences, List availableTypes) { + public static String determineArtifactType(TypedContent content, String artifactType, + Map resolvedReferences, ArtifactTypeUtilProviderFactory artifactTypeProviderFactory) { if ("".equals(artifactType)) { artifactType = null; } if (artifactType == null && content != null) { - artifactType = ArtifactTypeUtil.discoverType(content, contentType, resolvedReferences); + artifactType = ArtifactTypeUtil.discoverType(content, resolvedReferences, artifactTypeProviderFactory); } - if (!availableTypes.contains(artifactType)) { + if (!artifactTypeProviderFactory.getAllArtifactTypes().contains(artifactType)) { throw new InvalidArtifactTypeException("Invalid or unknown artifact type: " + artifactType); } return artifactType; @@ -68,153 +52,20 @@ public static String determineArtifactType(ContentHandle content, String artifac * is. Examples include Avro, Protobuf, OpenAPI, etc. Most of the supported artifact types are JSON * formatted. So in these cases we will need to look for some sort of type-specific marker in the content * of the artifact. The method does its best to figure out the type, but will default to Avro if all else - * fails. TODO This default behavior does not sound right - * @param content - * @param contentType + * fails. + * @param content * @param resolvedReferences */ @SuppressWarnings("deprecation") - private static String discoverType(ContentHandle content, String contentType, Map resolvedReferences) throws InvalidArtifactTypeException { - boolean triedProto = false; - - // If the content-type suggests it's protobuf, try that first. - if (contentType == null || contentType.toLowerCase().contains("proto")) { - triedProto = true; - String type = tryProto(content); - if (type != null) { - return type; - } - } - - // Try the various JSON formatted types - // TODO handle both JSON and YAML - try { - JsonNode tree = mapper.readTree(content.content()); - - // OpenAPI - if (tree.has("openapi") || tree.has("swagger")) { - return ArtifactType.OPENAPI; - } - // AsyncAPI - if (tree.has("asyncapi")) { - return ArtifactType.ASYNCAPI; - } - // JSON Schema - if (tree.has("$schema") && tree.get("$schema").asText().contains("json-schema.org") || tree.has("properties")) { - return ArtifactType.JSON; - } - // Kafka Connect?? - // TODO detect Kafka Connect schemas - - throw new InvalidArtifactTypeException("Failed to discover artifact type from JSON content."); - } catch (Exception e) { - // Apparently it's not JSON. - } - - try { - // Avro without quote - final Schema.Parser parser = new Schema.Parser(); - final List schemaRefs = new ArrayList<>(); - for (Map.Entry referencedContent : resolvedReferences.entrySet()) { - if (!parser.getTypes().containsKey(referencedContent.getKey())) { - Schema schemaRef = parser.parse(referencedContent.getValue().content()); - schemaRefs.add(schemaRef); - } - } - final Schema schema = parser.parse(removeQuotedBrackets(content.content())); - schema.toString(schemaRefs, false); - return ArtifactType.AVRO; - } catch (Exception e) { - //ignored - } - - try { - // Avro with original input - final Schema.Parser parser = new Schema.Parser(); - final List schemaRefs = new ArrayList<>(); - for (Map.Entry referencedContent : resolvedReferences.entrySet()) { - if (!parser.getTypes().containsKey(referencedContent.getKey())) { - Schema schemaRef = parser.parse(referencedContent.getValue().content()); - schemaRefs.add(schemaRef); - } - } - final Schema schema = parser.parse(content.content()); - schema.toString(schemaRefs, false); - return ArtifactType.AVRO; - } catch (Exception e) { - //ignored - } - - // Try protobuf (only if we haven't already) - if (!triedProto) { - String type = tryProto(content); - if (type != null) { - return type; + private static String discoverType(TypedContent content, Map resolvedReferences, + ArtifactTypeUtilProviderFactory artifactTypeProviderFactory) throws InvalidArtifactTypeException { + for (ArtifactTypeUtilProvider provider : artifactTypeProviderFactory.getAllArtifactTypeProviders()) { + if (provider.acceptsContent(content, resolvedReferences)) { + return provider.getArtifactType(); } } - // Try GraphQL (SDL) - if (tryGraphQL(content)) { - return ArtifactType.GRAPHQL; - } - - // Try the various XML formatted types - try (InputStream stream = content.stream()) { - Document xmlDocument = DocumentBuilderAccessor.getDocumentBuilder().parse(stream); - Element root = xmlDocument.getDocumentElement(); - String ns = root.getNamespaceURI(); - - // XSD - if (ns != null && ns.equals("http://www.w3.org/2001/XMLSchema")) { - return ArtifactType.XSD; - } // WSDL - else if (ns != null && (ns.equals("http://schemas.xmlsoap.org/wsdl/") - || ns.equals("http://www.w3.org/ns/wsdl/"))) { - return ArtifactType.WSDL; - } else { - // default to XML since its been parsed - return ArtifactType.XML; - } - } catch (Exception e) { - // It's not XML. - } - throw new InvalidArtifactTypeException("Failed to discover artifact type from content."); } - - private static String tryProto(ContentHandle content) { - try { - ProtobufFile.toProtoFileElement(content.content()); - return ArtifactType.PROTOBUF; - } catch (Exception e) { - try { - // Attempt to parse binary FileDescriptorProto - byte[] bytes = Base64.getDecoder().decode(content.content()); - FileDescriptorUtils.fileDescriptorToProtoFile(DescriptorProtos.FileDescriptorProto.parseFrom(bytes)); - return ArtifactType.PROTOBUF; - } catch (Exception pe) { - // Doesn't seem to be protobuf - } - } - return null; - } - - /** - * Given a content removes any quoted brackets. This is useful for some validation corner cases in avro where some libraries detects quoted brackets as valid and others as invalid - */ - private static String removeQuotedBrackets(String content) { - return QUOTED_BRACKETS.matcher(content).replaceAll(":{}"); - } - - private static boolean tryGraphQL(ContentHandle content) { - try { - TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(content.content()); - if (typeRegistry != null) { - return true; - } - } catch (Exception e) { - // Must not be a GraphQL file - } - return false; - } } + diff --git a/app/src/main/java/io/apicurio/registry/util/DtoUtil.java b/app/src/main/java/io/apicurio/registry/util/DtoUtil.java deleted file mode 100644 index 5212069f0f..0000000000 --- a/app/src/main/java/io/apicurio/registry/util/DtoUtil.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.apicurio.registry.util; - -import io.apicurio.registry.storage.dto.ArtifactMetaDataDto; -import io.apicurio.registry.storage.dto.EditableArtifactMetaDataDto; - -public final class DtoUtil { - - /** - * Sets values from the EditableArtifactMetaDataDto into the ArtifactMetaDataDto. - * - * @param amdd - * @param editableArtifactMetaData - * @return the updated ArtifactMetaDataDto object - */ - public static final ArtifactMetaDataDto setEditableMetaDataInArtifact(ArtifactMetaDataDto amdd, EditableArtifactMetaDataDto editableArtifactMetaData) { - if (editableArtifactMetaData.getName() != null) { - amdd.setName(editableArtifactMetaData.getName()); - } - if (editableArtifactMetaData.getDescription() != null) { - amdd.setDescription(editableArtifactMetaData.getDescription()); - } - if (editableArtifactMetaData.getLabels() != null && !editableArtifactMetaData.getLabels().isEmpty()) { - amdd.setLabels(editableArtifactMetaData.getLabels()); - } - return amdd; - } - - public static String registryAuthPropertyToApp(String propertyName) { - return propertyName.replace("apicurio.auth.", "app.authn."); - } - - public static String appAuthPropertyToRegistry(String propertyName) { - return propertyName.replace("app.authn.", "apicurio.auth."); - } - -} diff --git a/app/src/test/java/io/apicurio/registry/AbstractRegistryTestBase.java b/app/src/test/java/io/apicurio/registry/AbstractRegistryTestBase.java index 644513dd31..6f204c7fb5 100644 --- a/app/src/test/java/io/apicurio/registry/AbstractRegistryTestBase.java +++ b/app/src/test/java/io/apicurio/registry/AbstractRegistryTestBase.java @@ -1,6 +1,8 @@ package io.apicurio.registry; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.types.ContentTypes; import io.apicurio.registry.utils.tests.ParallelizableTest; import io.apicurio.registry.utils.tests.TestUtils; @@ -58,6 +60,23 @@ protected final ContentHandle resourceToContentHandle(String resourceName) { return ContentHandle.create(resourceToString(resourceName)); } + protected final TypedContent resourceToTypedContentHandle(String resourceName) { + String ct = ContentTypes.APPLICATION_JSON; + if (resourceName.toLowerCase().endsWith("yaml") || resourceName.toLowerCase().endsWith("yml")) { + ct = ContentTypes.APPLICATION_YAML; + } + if (resourceName.toLowerCase().endsWith("xml") || resourceName.toLowerCase().endsWith("wsdl") || resourceName.toLowerCase().endsWith("xsd") ) { + ct = ContentTypes.APPLICATION_XML; + } + if (resourceName.toLowerCase().endsWith("proto")) { + ct = ContentTypes.APPLICATION_PROTOBUF; + } + if (resourceName.toLowerCase().endsWith("graphql")) { + ct = ContentTypes.APPLICATION_GRAPHQL; + } + return TypedContent.create(resourceToContentHandle(resourceName), ct); + } + public static void assertMultilineTextEquals(String expected, String actual) throws Exception { Assertions.assertEquals(TestUtils.normalizeMultiLineString(expected), TestUtils.normalizeMultiLineString(actual)); } @@ -66,4 +85,12 @@ public static InputStream asInputStream(String value) { return new ByteArrayInputStream(value.getBytes(StandardCharsets.UTF_8)); } + public static TypedContent asTypedContent(String schema) { + return asTypedContent(schema, ContentTypes.APPLICATION_JSON); + } + + public static TypedContent asTypedContent(String schema, String contentType) { + return TypedContent.create(ContentHandle.create(schema), contentType); + } + } diff --git a/app/src/test/java/io/apicurio/registry/AbstractResourceTestBase.java b/app/src/test/java/io/apicurio/registry/AbstractResourceTestBase.java index 54080c8004..1603b168d1 100644 --- a/app/src/test/java/io/apicurio/registry/AbstractResourceTestBase.java +++ b/app/src/test/java/io/apicurio/registry/AbstractResourceTestBase.java @@ -72,8 +72,6 @@ protected void beforeAll() throws Exception { @AfterAll protected void afterAll() { - //delete data to - //storage.deleteAllUserData(); } protected RestService buildConfluentClient() { diff --git a/app/src/test/java/io/apicurio/registry/noprofile/ArtifactTypeTest.java b/app/src/test/java/io/apicurio/registry/noprofile/ArtifactTypeTest.java index 56609e3dfa..be50fbd700 100644 --- a/app/src/test/java/io/apicurio/registry/noprofile/ArtifactTypeTest.java +++ b/app/src/test/java/io/apicurio/registry/noprofile/ArtifactTypeTest.java @@ -1,27 +1,26 @@ package io.apicurio.registry.noprofile; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import jakarta.inject.Inject; - import io.apicurio.registry.AbstractRegistryTestBase; import io.apicurio.registry.JsonSchemas; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - import io.apicurio.registry.rules.compatibility.CompatibilityChecker; import io.apicurio.registry.rules.compatibility.CompatibilityDifference; import io.apicurio.registry.rules.compatibility.CompatibilityExecutionResult; import io.apicurio.registry.rules.compatibility.CompatibilityLevel; +import io.apicurio.registry.rules.compatibility.JsonSchemaCompatibilityDifference; import io.apicurio.registry.rules.compatibility.jsonschema.diff.DiffType; import io.apicurio.registry.rules.compatibility.jsonschema.diff.Difference; -import io.apicurio.registry.rules.compatibility.JsonSchemaCompatibilityDifference; import io.apicurio.registry.types.ArtifactType; +import io.apicurio.registry.types.ContentTypes; import io.apicurio.registry.types.provider.ArtifactTypeUtilProvider; import io.apicurio.registry.types.provider.ArtifactTypeUtilProviderFactory; import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.List; +import java.util.Set; @QuarkusTest public class ArtifactTypeTest extends AbstractRegistryTestBase { @@ -51,12 +50,14 @@ public void testAvro() { ArtifactTypeUtilProvider provider = factory.getArtifactTypeProvider(avro); CompatibilityChecker checker = provider.getCompatibilityChecker(); - CompatibilityExecutionResult compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.BACKWARD, Collections.emptyList(), avroString, Collections.emptyMap()); + CompatibilityExecutionResult compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.BACKWARD, + Collections.emptyList(), asTypedContent(avroString), Collections.emptyMap()); Assertions.assertTrue(compatibilityExecutionResult.isCompatible()); Assertions.assertTrue(compatibilityExecutionResult.getIncompatibleDifferences().isEmpty()); String avroString2 = "{\"type\":\"record\",\"name\":\"myrecord1\",\"fields\":[{\"name\":\"f1\",\"type\":\"string\", \"qq\":\"ff\"}]}"; - compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.BACKWARD, Collections.singletonList(avroString), avroString2, Collections.emptyMap()); + compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.BACKWARD, Collections.singletonList(asTypedContent(avroString)), + asTypedContent(avroString2), Collections.emptyMap()); Assertions.assertTrue(compatibilityExecutionResult.isCompatible()); Assertions.assertTrue(compatibilityExecutionResult.getIncompatibleDifferences().isEmpty()); } @@ -69,10 +70,13 @@ public void testJson() { ArtifactTypeUtilProvider provider = factory.getArtifactTypeProvider(json); CompatibilityChecker checker = provider.getCompatibilityChecker(); - Assertions.assertTrue(checker.testCompatibility(CompatibilityLevel.BACKWARD, Collections.emptyList(), jsonString, Collections.emptyMap()).isCompatible()); - Assertions.assertTrue(checker.testCompatibility(CompatibilityLevel.BACKWARD, Collections.singletonList(jsonString), jsonString, Collections.emptyMap()).isCompatible()); + Assertions.assertTrue(checker.testCompatibility(CompatibilityLevel.BACKWARD, Collections.emptyList(), + asTypedContent(jsonString), Collections.emptyMap()).isCompatible()); + Assertions.assertTrue(checker.testCompatibility(CompatibilityLevel.BACKWARD, Collections.singletonList(asTypedContent(jsonString)), + asTypedContent(jsonString), Collections.emptyMap()).isCompatible()); - CompatibilityExecutionResult compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.BACKWARD, Collections.singletonList(jsonString), incompatibleJsonString, Collections.emptyMap()); + CompatibilityExecutionResult compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.BACKWARD, + Collections.singletonList(asTypedContent(jsonString)), asTypedContent(incompatibleJsonString), Collections.emptyMap()); Assertions.assertFalse(compatibilityExecutionResult.isCompatible()); Set incompatibleDifferences = compatibilityExecutionResult.getIncompatibleDifferences(); Difference ageDiff = findDiffByPathUpdated(incompatibleDifferences, "/properties/age"); @@ -117,7 +121,8 @@ public void testProtobuf() { ArtifactTypeUtilProvider provider = factory.getArtifactTypeProvider(protobuf); CompatibilityChecker checker = provider.getCompatibilityChecker(); - CompatibilityExecutionResult compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.BACKWARD, Collections.emptyList(), data, Collections.emptyMap()); + CompatibilityExecutionResult compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.BACKWARD, + Collections.emptyList(), asTypedContent(data, ContentTypes.APPLICATION_PROTOBUF), Collections.emptyMap()); Assertions.assertTrue(compatibilityExecutionResult.isCompatible()); Assertions.assertTrue(compatibilityExecutionResult.getIncompatibleDifferences().isEmpty()); @@ -141,7 +146,9 @@ public void testProtobuf() { "\trpc Previous(PreviousRequest) returns (stream Channel);\n" + "}\n"; - compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.BACKWARD, Collections.singletonList(data), data2, Collections.emptyMap()); + compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.BACKWARD, + Collections.singletonList(asTypedContent(data, ContentTypes.APPLICATION_PROTOBUF)), + asTypedContent(data2, ContentTypes.APPLICATION_PROTOBUF), Collections.emptyMap()); Assertions.assertTrue(compatibilityExecutionResult.isCompatible()); Assertions.assertTrue(compatibilityExecutionResult.getIncompatibleDifferences().isEmpty()); @@ -162,7 +169,8 @@ public void testProtobuf() { "\trpc Previous(PreviousRequest) returns (stream Channel);\n" + "}\n"; - compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.BACKWARD, Collections.singletonList(data), data3, Collections.emptyMap()); + compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.BACKWARD, + Collections.singletonList(asTypedContent(data, ContentTypes.APPLICATION_PROTOBUF)), asTypedContent(data3, ContentTypes.APPLICATION_PROTOBUF), Collections.emptyMap()); Assertions.assertFalse(compatibilityExecutionResult.isCompatible()); Assertions.assertFalse(compatibilityExecutionResult.getIncompatibleDifferences().isEmpty()); Assertions.assertEquals("The new version of the protobuf artifact is not backward compatible.", compatibilityExecutionResult.getIncompatibleDifferences().iterator().next().asRuleViolation().getDescription()); @@ -182,7 +190,8 @@ public void testProtobufV2() { ArtifactTypeUtilProvider provider = factory.getArtifactTypeProvider(protobuf); CompatibilityChecker checker = provider.getCompatibilityChecker(); - CompatibilityExecutionResult compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.BACKWARD, Collections.emptyList(), data, Collections.emptyMap()); + CompatibilityExecutionResult compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.BACKWARD, + Collections.emptyList(), asTypedContent(data, ContentTypes.APPLICATION_PROTOBUF), Collections.emptyMap()); Assertions.assertTrue(compatibilityExecutionResult.isCompatible()); Assertions.assertTrue(compatibilityExecutionResult.getIncompatibleDifferences().isEmpty()); @@ -194,7 +203,8 @@ public void testProtobufV2() { " required string code = 3;\n" + "}"; - compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.BACKWARD, Collections.singletonList(data), data2, Collections.emptyMap()); + compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.BACKWARD, + Collections.singletonList(asTypedContent(data, ContentTypes.APPLICATION_PROTOBUF)), asTypedContent(data2, ContentTypes.APPLICATION_PROTOBUF), Collections.emptyMap()); Assertions.assertFalse(compatibilityExecutionResult.isCompatible()); Assertions.assertFalse(compatibilityExecutionResult.getIncompatibleDifferences().isEmpty()); Assertions.assertEquals("The new version of the protobuf artifact is not backward compatible.", compatibilityExecutionResult.getIncompatibleDifferences().iterator().next().asRuleViolation().getDescription()); @@ -208,7 +218,9 @@ public void testProtobufBackwardTransitive() { CompatibilityChecker checker = provider.getCompatibilityChecker(); //adding a required field is not allowed since the first schema does not have it, should fail - CompatibilityExecutionResult compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.BACKWARD_TRANSITIVE, List.of(PROTO_DATA, PROTO_DATA_2), PROTO_DATA_2, Collections.emptyMap()); + CompatibilityExecutionResult compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.BACKWARD_TRANSITIVE, + List.of(asTypedContent(PROTO_DATA, ContentTypes.APPLICATION_PROTOBUF), asTypedContent(PROTO_DATA_2, ContentTypes.APPLICATION_PROTOBUF)), + asTypedContent(PROTO_DATA_2, ContentTypes.APPLICATION_PROTOBUF), Collections.emptyMap()); Assertions.assertFalse(compatibilityExecutionResult.isCompatible()); Assertions.assertFalse(compatibilityExecutionResult.getIncompatibleDifferences().isEmpty()); Assertions.assertEquals("The new version of the protobuf artifact is not backward compatible.", compatibilityExecutionResult.getIncompatibleDifferences().iterator().next().asRuleViolation().getDescription()); @@ -223,14 +235,18 @@ public void testProtobufForward() { CompatibilityChecker checker = provider.getCompatibilityChecker(); //adding a required field is not allowed, should fail - CompatibilityExecutionResult compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.FORWARD, Collections.singletonList(PROTO_DATA_2), PROTO_DATA, Collections.emptyMap()); + CompatibilityExecutionResult compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.FORWARD, + Collections.singletonList(asTypedContent(PROTO_DATA_2, ContentTypes.APPLICATION_PROTOBUF)), + asTypedContent(PROTO_DATA, ContentTypes.APPLICATION_PROTOBUF), Collections.emptyMap()); Assertions.assertFalse(compatibilityExecutionResult.isCompatible()); Assertions.assertFalse(compatibilityExecutionResult.getIncompatibleDifferences().isEmpty()); Assertions.assertEquals("The new version of the protobuf artifact is not forward compatible.", compatibilityExecutionResult.getIncompatibleDifferences().iterator().next().asRuleViolation().getDescription()); Assertions.assertEquals("/", compatibilityExecutionResult.getIncompatibleDifferences().iterator().next().asRuleViolation().getContext()); //adding a required field is allowed since we're only checking forward, not forward transitive - compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.FORWARD, List.of(PROTO_DATA, PROTO_DATA_2), PROTO_DATA_2, Collections.emptyMap()); + compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.FORWARD, + List.of(asTypedContent(PROTO_DATA, ContentTypes.APPLICATION_PROTOBUF), asTypedContent(PROTO_DATA_2, ContentTypes.APPLICATION_PROTOBUF)), + asTypedContent(PROTO_DATA_2, ContentTypes.APPLICATION_PROTOBUF), Collections.emptyMap()); Assertions.assertTrue(compatibilityExecutionResult.isCompatible()); Assertions.assertTrue(compatibilityExecutionResult.getIncompatibleDifferences().isEmpty()); } @@ -242,12 +258,16 @@ public void testProtobufForwardTransitive() { CompatibilityChecker checker = provider.getCompatibilityChecker(); //must pass, all the existing schemas are the same - CompatibilityExecutionResult compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.FORWARD_TRANSITIVE, List.of(PROTO_DATA_2, PROTO_DATA_2), PROTO_DATA_2, Collections.emptyMap()); + CompatibilityExecutionResult compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.FORWARD_TRANSITIVE, + List.of(asTypedContent(PROTO_DATA_2, ContentTypes.APPLICATION_PROTOBUF), asTypedContent(PROTO_DATA_2, ContentTypes.APPLICATION_PROTOBUF)), + asTypedContent(PROTO_DATA_2, ContentTypes.APPLICATION_PROTOBUF), Collections.emptyMap()); Assertions.assertTrue(compatibilityExecutionResult.isCompatible()); Assertions.assertTrue(compatibilityExecutionResult.getIncompatibleDifferences().isEmpty()); //adding a required field is not allowed since we're now checking forward transitive and the field is not present, not forward transitive - compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.FORWARD_TRANSITIVE, List.of(PROTO_DATA, PROTO_DATA_2), PROTO_DATA_2, Collections.emptyMap()); + compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.FORWARD_TRANSITIVE, + List.of(asTypedContent(PROTO_DATA, ContentTypes.APPLICATION_PROTOBUF), asTypedContent(PROTO_DATA_2, ContentTypes.APPLICATION_PROTOBUF)), + asTypedContent(PROTO_DATA_2, ContentTypes.APPLICATION_PROTOBUF), Collections.emptyMap()); Assertions.assertFalse(compatibilityExecutionResult.isCompatible()); Assertions.assertFalse(compatibilityExecutionResult.getIncompatibleDifferences().isEmpty()); Assertions.assertEquals("The new version of the protobuf artifact is not forward compatible.", compatibilityExecutionResult.getIncompatibleDifferences().iterator().next().asRuleViolation().getDescription()); @@ -261,19 +281,24 @@ public void testProtobufFull() { CompatibilityChecker checker = provider.getCompatibilityChecker(); //adding a required field is not allowed since we're now checking forward transitive and the field is not present, not forward transitive - CompatibilityExecutionResult compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.FULL, List.of(PROTO_DATA), PROTO_DATA_2, Collections.emptyMap()); + CompatibilityExecutionResult compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.FULL, + List.of(asTypedContent(PROTO_DATA, ContentTypes.APPLICATION_PROTOBUF)), asTypedContent(PROTO_DATA_2, ContentTypes.APPLICATION_PROTOBUF), Collections.emptyMap()); Assertions.assertFalse(compatibilityExecutionResult.isCompatible()); Assertions.assertFalse(compatibilityExecutionResult.getIncompatibleDifferences().isEmpty()); Assertions.assertEquals("The new version of the protobuf artifact is not fully compatible.", compatibilityExecutionResult.getIncompatibleDifferences().iterator().next().asRuleViolation().getDescription()); Assertions.assertEquals("/", compatibilityExecutionResult.getIncompatibleDifferences().iterator().next().asRuleViolation().getContext()); //must pass, since the schema is both backwards and forwards compatible with the latest existing schema - compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.FULL, List.of(PROTO_DATA, PROTO_DATA_2), PROTO_DATA_2, Collections.emptyMap()); + compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.FULL, + List.of(asTypedContent(PROTO_DATA, ContentTypes.APPLICATION_PROTOBUF), asTypedContent(PROTO_DATA_2, ContentTypes.APPLICATION_PROTOBUF)), + asTypedContent(PROTO_DATA_2, ContentTypes.APPLICATION_PROTOBUF), Collections.emptyMap()); Assertions.assertTrue(compatibilityExecutionResult.isCompatible()); Assertions.assertTrue(compatibilityExecutionResult.getIncompatibleDifferences().isEmpty()); //must fail, the schema is not compatible with the first existing schema - compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.FULL_TRANSITIVE, List.of(PROTO_DATA, PROTO_DATA_2), PROTO_DATA_2, Collections.emptyMap()); + compatibilityExecutionResult = checker.testCompatibility(CompatibilityLevel.FULL_TRANSITIVE, + List.of(asTypedContent(PROTO_DATA, ContentTypes.APPLICATION_PROTOBUF), asTypedContent(PROTO_DATA_2, ContentTypes.APPLICATION_PROTOBUF)), + asTypedContent(PROTO_DATA_2, ContentTypes.APPLICATION_PROTOBUF), Collections.emptyMap()); Assertions.assertFalse(compatibilityExecutionResult.isCompatible()); Assertions.assertFalse(compatibilityExecutionResult.getIncompatibleDifferences().isEmpty()); Assertions.assertEquals("The new version of the protobuf artifact is not fully compatible.", compatibilityExecutionResult.getIncompatibleDifferences().iterator().next().asRuleViolation().getDescription()); diff --git a/app/src/test/java/io/apicurio/registry/noprofile/ccompat/rest/v7/ConfluentClientTest.java b/app/src/test/java/io/apicurio/registry/noprofile/ccompat/rest/v7/ConfluentClientTest.java index d225a7c0d3..5cc8de4e90 100644 --- a/app/src/test/java/io/apicurio/registry/noprofile/ccompat/rest/v7/ConfluentClientTest.java +++ b/app/src/test/java/io/apicurio/registry/noprofile/ccompat/rest/v7/ConfluentClientTest.java @@ -822,13 +822,53 @@ public void testSchemaReferences() throws Exception { @Test public void testSchemaReferencesMultipleLevels() throws Exception { - String root = "[\"myavro.BudgetDecreased\",\"myavro.BudgetUpdated\"]"; - - String ref1 = "{\n" + " \"type\" : \"record\",\n" + " \"name\" : \"BudgetDecreased\",\n" + " \"namespace\" : \"myavro\",\n" + " \"fields\" : [ {\n" + " \"name\" : \"buyerId\",\n" + " \"type\" : \"long\"\n" + " }, {\n" + " \"name\" : \"currency\",\n" + " \"type\" : {\n" + " \"type\" : \"myavro.currencies.Currency\"" + " }\n" + " }, {\n" + " \"name\" : \"amount\",\n" + " \"type\" : \"double\"\n" + " } ]\n" + "}"; - - String ref2 = "{\n" + " \"type\" : \"record\",\n" + " \"name\" : \"BudgetUpdated\",\n" + " \"namespace\" : \"myavro\",\n" + " \"fields\" : [ {\n" + " \"name\" : \"buyerId\",\n" + " \"type\" : \"long\"\n" + " }, {\n" + " \"name\" : \"currency\",\n" + " \"type\" : {\n" + " \"type\" : \"myavro.currencies.Currency\"" + " }\n" + " }, {\n" + " \"name\" : \"updatedValue\",\n" + " \"type\" : \"double\"\n" + " } ]\n" + "}"; - - String sharedRef = "{\n" + " \"type\" : \"enum\",\n" + " \"name\" : \"Currency\",\n" + " \"namespace\" : \"myavro.currencies\",\n" + " \"symbols\" : [ \"EUR\", \"USD\" ]\n" + " }\n"; + String root = """ + ["myavro.BudgetDecreased","myavro.BudgetUpdated"]"""; + + String ref1 = """ + { + "type" : "record", + "name" : "BudgetDecreased", + "namespace" : "myavro", + "fields" : [ { + "name" : "buyerId", + "type" : "long" + }, { + "name" : "currency", + "type" : { + "type" : "myavro.currencies.Currency" } + }, { + "name" : "amount", + "type" : "double" + } ] + }"""; + + String ref2 = """ + { + "type" : "record", + "name" : "BudgetUpdated", + "namespace" : "myavro", + "fields" : [ { + "name" : "buyerId", + "type" : "long" + }, { + "name" : "currency", + "type" : { + "type" : "myavro.currencies.Currency" } + }, { + "name" : "updatedValue", + "type" : "double" + } ] + }"""; + + String sharedRef = """ + { + "type" : "enum", + "name" : "Currency", + "namespace" : "myavro.currencies", + "symbols" : [ "EUR", "USD" ] + } + """; ConfluentTestUtils.registerAndVerifySchema(confluentClient, new AvroSchema(sharedRef).canonicalString(), "shared"); diff --git a/app/src/test/java/io/apicurio/registry/noprofile/compatibility/CompatibilityRuleApplicationTest.java b/app/src/test/java/io/apicurio/registry/noprofile/compatibility/CompatibilityRuleApplicationTest.java index f065dad052..45ed243845 100644 --- a/app/src/test/java/io/apicurio/registry/noprofile/compatibility/CompatibilityRuleApplicationTest.java +++ b/app/src/test/java/io/apicurio/registry/noprofile/compatibility/CompatibilityRuleApplicationTest.java @@ -4,6 +4,7 @@ import io.apicurio.registry.AbstractResourceTestBase; import io.apicurio.registry.JsonSchemas; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.model.GroupId; import io.apicurio.registry.rest.client.models.CreateArtifact; import io.apicurio.registry.rest.client.models.CreateVersion; @@ -38,6 +39,10 @@ @QuarkusTest public class CompatibilityRuleApplicationTest extends AbstractResourceTestBase { + private static TypedContent toTypedContent(String schema) { + return TypedContent.create(ContentHandle.create(schema), ContentTypes.APPLICATION_JSON); + } + private static final String SCHEMA_SIMPLE = "{\"type\": \"string\"}"; private static final String SCHEMA_WITH_MAP = "{\r\n" + " \"type\": \"record\",\r\n" + @@ -166,7 +171,7 @@ public void testGlobalCompatibilityRuleNoArtifact() throws Exception { .body("config", equalTo("FULL")); }); - rules.applyRules("no-group", "not-existent", ArtifactType.AVRO, ContentHandle.create(SCHEMA_SIMPLE), + rules.applyRules("no-group", "not-existent", ArtifactType.AVRO, toTypedContent(SCHEMA_SIMPLE), RuleApplicationType.CREATE, Collections.emptyList(), Collections.emptyMap()); } @@ -177,7 +182,7 @@ public void testAvroCompatibility() { Assertions.assertThrows(RuleViolationException.class, () -> { RuleContext context = new RuleContext("TestGroup", "Test", "AVRO", "BACKWARD", - Collections.singletonList(ContentHandle.create(v1Schema)), ContentHandle.create(v2Schema), + Collections.singletonList(toTypedContent(v1Schema)), toTypedContent(v2Schema), Collections.emptyList(), Collections.emptyMap()); compatibility.execute(context); }); @@ -190,7 +195,7 @@ public void testJsonSchemaCompatibility() { RuleViolationException ruleViolationException = Assertions.assertThrows(RuleViolationException.class, () -> { RuleContext context = new RuleContext("TestGroup", "TestJson", ArtifactType.JSON, "FORWARD_TRANSITIVE", - Collections.singletonList(ContentHandle.create(v1Schema)), ContentHandle.create(v2Schema), + Collections.singletonList(toTypedContent(v1Schema)), toTypedContent(v2Schema), Collections.emptyList(), Collections.emptyMap()); compatibility.execute(context); }); diff --git a/app/src/test/java/io/apicurio/registry/noprofile/content/ContentCanonicalizerTest.java b/app/src/test/java/io/apicurio/registry/noprofile/content/ContentCanonicalizerTest.java index cc40c0585a..8de94b5023 100644 --- a/app/src/test/java/io/apicurio/registry/noprofile/content/ContentCanonicalizerTest.java +++ b/app/src/test/java/io/apicurio/registry/noprofile/content/ContentCanonicalizerTest.java @@ -2,8 +2,10 @@ import io.apicurio.registry.AbstractRegistryTestBase; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.content.canon.ContentCanonicalizer; import io.apicurio.registry.types.ArtifactType; +import io.apicurio.registry.types.ContentTypes; import io.apicurio.registry.types.provider.ArtifactTypeUtilProvider; import io.apicurio.registry.types.provider.ArtifactTypeUtilProviderFactory; import io.quarkus.test.junit.QuarkusTest; @@ -18,6 +20,14 @@ public class ContentCanonicalizerTest extends AbstractRegistryTestBase { @Inject ArtifactTypeUtilProviderFactory factory; + private static TypedContent toTypedContent(String content) { + return toTypedContent(content, ContentTypes.APPLICATION_JSON); + } + + private static TypedContent toTypedContent(String content, String ct) { + return TypedContent.create(ContentHandle.create(content), ct); + } + private ContentCanonicalizer getContentCanonicalizer(String type) { ArtifactTypeUtilProvider provider = factory.getArtifactTypeProvider(type); return provider.getContentCanonicalizer(); @@ -40,8 +50,8 @@ void testOpenAPI() { "}"; String expected = "{\"components\":{},\"info\":{\"title\":\"Empty 3.0 API\",\"version\":\"1.0.0\"},\"openapi\":\"3.0.2\",\"paths\":{\"/\":{}}}"; - ContentHandle content = ContentHandle.create(before); - String actual = canonicalizer.canonicalize(content, Collections.emptyMap()).content(); + TypedContent content = toTypedContent(before); + String actual = canonicalizer.canonicalize(content, Collections.emptyMap()).getContent().content(); Assertions.assertEquals(expected, actual); } @@ -60,9 +70,9 @@ void testAvro() { " ]\r\n" + "} "; String expected = "{\"type\":\"record\",\"name\":\"FullName\",\"namespace\":\"com.example\",\"doc\":\"\",\"fields\":[{\"name\":\"first\",\"type\":\"string\",\"doc\":\"\"},{\"name\":\"middle\",\"type\":\"string\",\"doc\":\"\"},{\"name\":\"last\",\"type\":\"string\",\"doc\":\"\"}]}"; - - ContentHandle content = ContentHandle.create(before); - String actual = canonicalizer.canonicalize(content, Collections.emptyMap()).content(); + + TypedContent content = toTypedContent(before); + String actual = canonicalizer.canonicalize(content, Collections.emptyMap()).getContent().content(); Assertions.assertEquals(expected, actual); } @@ -86,8 +96,8 @@ void testProtobuf() { + " optional int32 result_per_page = 3;\n" + "}\n"; - ContentHandle content = ContentHandle.create(before); - String actual = canonicalizer.canonicalize(content, Collections.emptyMap()).content(); + TypedContent content = toTypedContent(before, ContentTypes.APPLICATION_PROTOBUF); + String actual = canonicalizer.canonicalize(content, Collections.emptyMap()).getContent().content(); Assertions.assertEquals(expected, actual); } @@ -129,8 +139,8 @@ void testGraphQL() { "}\n" + ""; - ContentHandle content = ContentHandle.create(before); - String actual = canonicalizer.canonicalize(content, Collections.emptyMap()).content(); + TypedContent content = toTypedContent(before, ContentTypes.APPLICATION_GRAPHQL); + String actual = canonicalizer.canonicalize(content, Collections.emptyMap()).getContent().content(); Assertions.assertEquals(expected, actual); } @@ -151,50 +161,41 @@ void testKafkaConnect() { "}"; String expected = "{\"fields\":[{\"field\":\"bar\",\"optional\":false,\"type\":\"string\"}],\"optional\":false,\"type\":\"struct\"}"; - ContentHandle content = ContentHandle.create(before); - String actual = canonicalizer.canonicalize(content, Collections.emptyMap()).content(); + TypedContent content = toTypedContent(before); + String actual = canonicalizer.canonicalize(content, Collections.emptyMap()).getContent().content(); Assertions.assertEquals(expected, actual); } - /** - * Test method for {@link io.apicurio.registry.content.ContentCanonicalizerFactory#create(io.apicurio.registry.types.ArtifactType)}. - */ @Test void testXsd() { ContentCanonicalizer canonicalizer = getContentCanonicalizer(ArtifactType.XSD); - ContentHandle content = resourceToContentHandle("xml-schema-before.xsd"); + TypedContent content = resourceToTypedContentHandle("xml-schema-before.xsd"); String expected = resourceToString("xml-schema-expected.xsd"); - String actual = canonicalizer.canonicalize(content, Collections.emptyMap()).content(); + String actual = canonicalizer.canonicalize(content, Collections.emptyMap()).getContent().content(); Assertions.assertEquals(expected, actual); } - /** - * Test method for {@link io.apicurio.registry.content.ContentCanonicalizerFactory#create(io.apicurio.registry.types.ArtifactType)}. - */ @Test void testWsdl() { ContentCanonicalizer canonicalizer = getContentCanonicalizer(ArtifactType.WSDL); - ContentHandle content = resourceToContentHandle("wsdl-before.wsdl"); + TypedContent content = resourceToTypedContentHandle("wsdl-before.wsdl"); String expected = resourceToString("wsdl-expected.wsdl"); - String actual = canonicalizer.canonicalize(content, Collections.emptyMap()).content(); + String actual = canonicalizer.canonicalize(content, Collections.emptyMap()).getContent().content(); Assertions.assertEquals(expected, actual); } - /** - * Test method for {@link io.apicurio.registry.content.ContentCanonicalizerFactory#create(io.apicurio.registry.types.ArtifactType)}. - */ @Test void testXml() { ContentCanonicalizer canonicalizer = getContentCanonicalizer(ArtifactType.XML); - ContentHandle content = resourceToContentHandle("xml-before.xml"); + TypedContent content = resourceToTypedContentHandle("xml-before.xml"); String expected = resourceToString("xml-expected.xml"); - String actual = canonicalizer.canonicalize(content, Collections.emptyMap()).content(); + String actual = canonicalizer.canonicalize(content, Collections.emptyMap()).getContent().content(); Assertions.assertEquals(expected, actual); } } diff --git a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/AllYamlTest.java b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/AllYamlTest.java new file mode 100644 index 0000000000..f41abfcbbb --- /dev/null +++ b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/AllYamlTest.java @@ -0,0 +1,229 @@ +package io.apicurio.registry.noprofile.rest.v3; + +import io.apicurio.registry.AbstractResourceTestBase; +import io.apicurio.registry.rest.client.models.CreateArtifact; +import io.apicurio.registry.rest.client.models.CreateArtifactResponse; +import io.apicurio.registry.rest.client.models.CreateVersion; +import io.apicurio.registry.rest.client.models.Error; +import io.apicurio.registry.rest.client.models.Rule; +import io.apicurio.registry.rest.client.models.RuleType; +import io.apicurio.registry.rest.client.models.VersionSearchResults; +import io.apicurio.registry.rules.compatibility.CompatibilityLevel; +import io.apicurio.registry.rules.integrity.IntegrityLevel; +import io.apicurio.registry.rules.validity.ValidityLevel; +import io.apicurio.registry.types.ArtifactType; +import io.apicurio.registry.types.ContentTypes; +import io.apicurio.registry.utils.IoUtil; +import io.apicurio.registry.utils.tests.TestUtils; +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +@QuarkusTest +public class AllYamlTest extends AbstractResourceTestBase { + + private static String YAML_CONTENT = """ + openapi: 3.0.2 + info: + title: Empty API + version: 1.0.0 + description: Just an empty API. + paths: + /test: + get: + responses: + '200': + content: + application/json: + schema: + type: string + description: Success. + operationId: test + """; + private static String YAML_CONTENT_V2 = YAML_CONTENT.replace("Empty API", "Empty API (v2)"); + private static String YAML_CONTENT_WITH_REF = """ + openapi: 3.0.2 + info: + title: Empty API + version: 1.0.0 + description: Just an empty API. + paths: + /test: + get: + responses: + '200': + content: + application/json: + schema: + '$ref': 'http://example.com/other-types.json#/components/schemas/MissingType' + description: Success. + operationId: test + """; + private static String JSON_CONTENT = """ + { + "openapi": "3.0.2", + "info": { + "title": "Empty API", + "version": "1.0.0", + "description": "Just an empty API." + }, + "paths": { + "/test": { + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Success." + } + }, + "operationId": "test" + } + } + } + }"""; + + @Test + public void testCreateYamlArtifact() throws Exception { + String groupId = TestUtils.generateGroupId(); + String artifactId = TestUtils.generateArtifactId(); + + CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.OPENAPI, YAML_CONTENT, ContentTypes.APPLICATION_YAML); + clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); + } + + @Test + public void testCreateYamlArtifactDiscoverType() throws Exception { + String groupId = TestUtils.generateGroupId(); + String artifactId = TestUtils.generateArtifactId(); + + CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, null, YAML_CONTENT, ContentTypes.APPLICATION_YAML); + clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); + } + + @Test + public void testCreateYamlArtifactWithValidity() throws Exception { + String groupId = TestUtils.generateGroupId(); + String artifactId = TestUtils.generateArtifactId(); + + Rule createRule = new Rule(); + createRule.setType(RuleType.VALIDITY); + createRule.setConfig(ValidityLevel.FULL.name()); + clientV3.admin().rules().post(createRule); + + try { + CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.OPENAPI, YAML_CONTENT, ContentTypes.APPLICATION_YAML); + clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); + } catch (Error e) { + System.out.println("ERROR: " + e.getDetail()); + e.getCause().printStackTrace(); + throw e; + } + } + + @Test + public void testUpdateYamlArtifact() throws Exception { + String groupId = TestUtils.generateGroupId(); + String artifactId = TestUtils.generateArtifactId(); + + CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.OPENAPI, YAML_CONTENT, ContentTypes.APPLICATION_YAML); + clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); + + CreateVersion createVersion = TestUtils.clientCreateVersion(YAML_CONTENT_V2, ContentTypes.APPLICATION_YAML); + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().post(createVersion); + } + + @Test + public void testUpdateYamlArtifactWithCompatibility() throws Exception { + String groupId = TestUtils.generateGroupId(); + String artifactId = TestUtils.generateArtifactId(); + + CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.OPENAPI, YAML_CONTENT, ContentTypes.APPLICATION_YAML); + clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); + + // Enable the compatibility rule for the artifact + Rule createRule = new Rule(); + createRule.setType(RuleType.COMPATIBILITY); + createRule.setConfig(CompatibilityLevel.FULL.name()); + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).rules().post(createRule); + + // Now create a new version + CreateVersion createVersion = TestUtils.clientCreateVersion(YAML_CONTENT_V2, ContentTypes.APPLICATION_YAML); + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().post(createVersion); + } + + @Test + public void testUpdateYamlArtifactWithIntegrity() throws Exception { + String groupId = TestUtils.generateGroupId(); + String artifactId = TestUtils.generateArtifactId(); + + CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.OPENAPI, YAML_CONTENT, ContentTypes.APPLICATION_YAML); + clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); + + // Enable the compatibility rule for the artifact + Rule createRule = new Rule(); + createRule.setType(RuleType.INTEGRITY); + createRule.setConfig(IntegrityLevel.FULL.name()); + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).rules().post(createRule); + + // Now create a new version with a missing $ref + CreateVersion createVersion = TestUtils.clientCreateVersion(YAML_CONTENT_WITH_REF, ContentTypes.APPLICATION_YAML); + Assertions.assertThrows(Exception.class, () -> { + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().post(createVersion); + }); + } + + @Test + public void testCanonicalContent() throws Exception { + String testId = UUID.randomUUID().toString(); + String yamlContent = YAML_CONTENT.replace("Empty API", testId); + String jsonContent = JSON_CONTENT.replace("Empty API", testId); + + String groupId = TestUtils.generateGroupId(); + String artifactId = TestUtils.generateArtifactId(); + + CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.OPENAPI, yamlContent, ContentTypes.APPLICATION_YAML); + CreateArtifactResponse response = clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); + + // Search for the version by its content as YAML + VersionSearchResults results = clientV3.search().versions().post(IoUtil.toStream(yamlContent), ContentTypes.APPLICATION_YAML); + Assertions.assertNotNull(results); + Assertions.assertEquals(1, results.getCount()); + Assertions.assertEquals(response.getVersion().getArtifactId(), results.getVersions().get(0).getArtifactId()); + Assertions.assertEquals(response.getVersion().getGroupId(), results.getVersions().get(0).getGroupId()); + Assertions.assertEquals(response.getVersion().getVersion(), results.getVersions().get(0).getVersion()); + Assertions.assertEquals(response.getVersion().getGlobalId(), results.getVersions().get(0).getGlobalId()); + + // Search for the version by its canonical content as YAML + results = clientV3.search().versions().post(IoUtil.toStream(yamlContent), ContentTypes.APPLICATION_YAML, config -> { + config.queryParameters.canonical = true; + config.queryParameters.artifactType = ArtifactType.OPENAPI; + }); + Assertions.assertNotNull(results); + Assertions.assertEquals(1, results.getCount()); + Assertions.assertEquals(response.getVersion().getArtifactId(), results.getVersions().get(0).getArtifactId()); + Assertions.assertEquals(response.getVersion().getGroupId(), results.getVersions().get(0).getGroupId()); + Assertions.assertEquals(response.getVersion().getVersion(), results.getVersions().get(0).getVersion()); + Assertions.assertEquals(response.getVersion().getGlobalId(), results.getVersions().get(0).getGlobalId()); + + // Search for the version again by its canonical content as JSON + results = clientV3.search().versions().post(IoUtil.toStream(jsonContent), ContentTypes.APPLICATION_JSON, config -> { + config.queryParameters.canonical = true; + config.queryParameters.artifactType = ArtifactType.OPENAPI; + }); + Assertions.assertNotNull(results); + Assertions.assertEquals(1, results.getCount()); + Assertions.assertEquals(response.getVersion().getArtifactId(), results.getVersions().get(0).getArtifactId()); + Assertions.assertEquals(response.getVersion().getGroupId(), results.getVersions().get(0).getGroupId()); + Assertions.assertEquals(response.getVersion().getVersion(), results.getVersions().get(0).getVersion()); + Assertions.assertEquals(response.getVersion().getGlobalId(), results.getVersions().get(0).getGlobalId()); + } + +} diff --git a/app/src/test/java/io/apicurio/registry/util/ArtifactTypeUtilTest.java b/app/src/test/java/io/apicurio/registry/util/ArtifactTypeUtilTest.java index aa9d410e78..ad3aa61993 100644 --- a/app/src/test/java/io/apicurio/registry/util/ArtifactTypeUtilTest.java +++ b/app/src/test/java/io/apicurio/registry/util/ArtifactTypeUtilTest.java @@ -1,158 +1,152 @@ package io.apicurio.registry.util; import io.apicurio.registry.AbstractRegistryTestBase; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.storage.error.InvalidArtifactTypeException; import io.apicurio.registry.types.ArtifactType; +import io.apicurio.registry.types.provider.ArtifactTypeUtilProviderFactory; +import io.apicurio.registry.types.provider.DefaultArtifactTypeUtilProviderImpl; import org.apache.avro.Schema; import org.apache.avro.Schema.Type; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.util.List; - import static org.junit.jupiter.api.Assertions.assertEquals; class ArtifactTypeUtilTest extends AbstractRegistryTestBase { - - static List availableTypes = List.of( - ArtifactType.JSON, - ArtifactType.OPENAPI, - ArtifactType.ASYNCAPI, - ArtifactType.AVRO, - ArtifactType.PROTOBUF, - ArtifactType.WSDL, - ArtifactType.XML, - ArtifactType.XSD, - ArtifactType.GRAPHQL - ); + + static ArtifactTypeUtilProviderFactory artifactTypeUtilProviderFactory; + static { + artifactTypeUtilProviderFactory = new DefaultArtifactTypeUtilProviderImpl(); + } + /** - * Test method for {@link io.apicurio.registry.util.ArtifactTypeUtil#discoverType(ContentHandle, java.lang.String)}. + * Test method for {@link io.apicurio.registry.util.ArtifactTypeUtil#determineArtifactType(TypedContent, String, ArtifactTypeUtilProviderFactory)} */ @Test void testDiscoverType_JSON() { - ContentHandle content = resourceToContentHandle("json-schema.json"); - String type = ArtifactTypeUtil.determineArtifactType(content, null, null, availableTypes); + TypedContent content = resourceToTypedContentHandle("json-schema.json"); + String type = ArtifactTypeUtil.determineArtifactType(content, null, artifactTypeUtilProviderFactory); Assertions.assertEquals(ArtifactType.JSON, type); } /** - * Test method for {@link io.apicurio.registry.util.ArtifactTypeUtil#discoverType(ContentHandle, java.lang.String)}. + * Test method for {@link io.apicurio.registry.util.ArtifactTypeUtil#determineArtifactType(TypedContent, String, ArtifactTypeUtilProviderFactory)}. */ @Test void testDiscoverType_Avro() { - ContentHandle content = resourceToContentHandle("avro.json"); - String type = ArtifactTypeUtil.determineArtifactType(content, null, null, availableTypes); + TypedContent content = resourceToTypedContentHandle("avro.json"); + String type = ArtifactTypeUtil.determineArtifactType(content, null, artifactTypeUtilProviderFactory); Assertions.assertEquals(ArtifactType.AVRO, type); } /** - * Test method for {@link io.apicurio.registry.util.ArtifactTypeUtil#discoverType(ContentHandle, java.lang.String)}. + * Test method for {@link io.apicurio.registry.util.ArtifactTypeUtil#determineArtifactType(TypedContent, String, ArtifactTypeUtilProviderFactory)}. */ @Test void testDiscoverType_Avro_Simple() { - ContentHandle content = resourceToContentHandle("avro-simple.avsc"); - Schema s = new Schema.Parser().parse(content.content()); + TypedContent content = resourceToTypedContentHandle("avro-simple.avsc"); + Schema s = new Schema.Parser().parse(content.getContent().content()); assertEquals(Type.STRING, s.getType()); - String type = ArtifactTypeUtil.determineArtifactType(content, null, null, availableTypes); + String type = ArtifactTypeUtil.determineArtifactType(content, null, artifactTypeUtilProviderFactory); Assertions.assertEquals(ArtifactType.AVRO, type); } /** - * Test method for {@link io.apicurio.registry.util.ArtifactTypeUtil#discoverType(ContentHandle, java.lang.String)}. + * Test method for {@link io.apicurio.registry.util.ArtifactTypeUtil#determineArtifactType(TypedContent, String, ArtifactTypeUtilProviderFactory)}. */ @Test void testDiscoverType_Proto() { - ContentHandle content = resourceToContentHandle("protobuf.proto"); - String type = ArtifactTypeUtil.determineArtifactType(content, null, null, availableTypes); + TypedContent content = resourceToTypedContentHandle("protobuf.proto"); + String type = ArtifactTypeUtil.determineArtifactType(content, null, artifactTypeUtilProviderFactory); Assertions.assertEquals(ArtifactType.PROTOBUF, type); - content = resourceToContentHandle("protobuf.proto"); - type = ArtifactTypeUtil.determineArtifactType(content, null, "application/x-protobuf", availableTypes); + content = resourceToTypedContentHandle("protobuf.proto"); + type = ArtifactTypeUtil.determineArtifactType(content, null, artifactTypeUtilProviderFactory); Assertions.assertEquals(ArtifactType.PROTOBUF, type); } /** - * Test method for {@link io.apicurio.registry.util.ArtifactTypeUtil#discoverType(ContentHandle, java.lang.String)}. + * Test method for {@link io.apicurio.registry.util.ArtifactTypeUtil#determineArtifactType(TypedContent, String, ArtifactTypeUtilProviderFactory)}. */ @Test void testDiscoverType_OpenApi() { - ContentHandle content = resourceToContentHandle("openapi.json"); - String type = ArtifactTypeUtil.determineArtifactType(content, null, null, availableTypes); + TypedContent content = resourceToTypedContentHandle("openapi.json"); + String type = ArtifactTypeUtil.determineArtifactType(content, null, artifactTypeUtilProviderFactory); Assertions.assertEquals(ArtifactType.OPENAPI, type); - content = resourceToContentHandle("swagger.json"); - type = ArtifactTypeUtil.determineArtifactType(content, null, null, availableTypes); + content = resourceToTypedContentHandle("swagger.json"); + type = ArtifactTypeUtil.determineArtifactType(content, null, artifactTypeUtilProviderFactory); Assertions.assertEquals(ArtifactType.OPENAPI, type); - content = resourceToContentHandle("swagger.json"); - type = ArtifactTypeUtil.determineArtifactType(content, null, "application/json", availableTypes); + content = resourceToTypedContentHandle("swagger.json"); + type = ArtifactTypeUtil.determineArtifactType(content, null, artifactTypeUtilProviderFactory); Assertions.assertEquals(ArtifactType.OPENAPI, type); } /** - * Test method for {@link io.apicurio.registry.util.ArtifactTypeUtil#discoverType(ContentHandle, java.lang.String)}. + * Test method for {@link io.apicurio.registry.util.ArtifactTypeUtil#determineArtifactType(TypedContent, String, ArtifactTypeUtilProviderFactory)}. */ @Test void testDiscoverType_AsyncApi() { - ContentHandle content = resourceToContentHandle("asyncapi.json"); - String type = ArtifactTypeUtil.determineArtifactType(content, null, null, availableTypes); + TypedContent content = resourceToTypedContentHandle("asyncapi.json"); + String type = ArtifactTypeUtil.determineArtifactType(content, null, artifactTypeUtilProviderFactory); Assertions.assertEquals(ArtifactType.ASYNCAPI, type); } /** - * Test method for {@link io.apicurio.registry.util.ArtifactTypeUtil#discoverType(ContentHandle, java.lang.String)}. + * Test method for {@link io.apicurio.registry.util.ArtifactTypeUtil#determineArtifactType(TypedContent, String, ArtifactTypeUtilProviderFactory)}. */ @Test void testDiscoverType_GraphQL() { - ContentHandle content = resourceToContentHandle("example.graphql"); - String type = ArtifactTypeUtil.determineArtifactType(content, null, null, availableTypes); + TypedContent content = resourceToTypedContentHandle("example.graphql"); + String type = ArtifactTypeUtil.determineArtifactType(content, null, artifactTypeUtilProviderFactory); Assertions.assertEquals(ArtifactType.GRAPHQL, type); } /** - * Test method for {@link io.apicurio.registry.util.ArtifactTypeUtil#discoverType(ContentHandle, java.lang.String)}. + * Test method for {@link io.apicurio.registry.util.ArtifactTypeUtil#determineArtifactType(TypedContent, String, ArtifactTypeUtilProviderFactory)}. */ @Test void testDiscoverType_DefaultNotFound() { Assertions.assertThrows(InvalidArtifactTypeException.class, () -> { - ContentHandle content = resourceToContentHandle("example.txt"); - ArtifactTypeUtil.determineArtifactType(content, null, null, availableTypes); + TypedContent content = resourceToTypedContentHandle("example.txt"); + ArtifactTypeUtil.determineArtifactType(content, null, artifactTypeUtilProviderFactory); }); } /** - * Test method for {@link io.apicurio.registry.util.ArtifactTypeUtil#discoverType(ContentHandle, java.lang.String)}. + * Test method for {@link io.apicurio.registry.util.ArtifactTypeUtil#determineArtifactType(TypedContent, String, ArtifactTypeUtilProviderFactory)}. */ @Test void testDiscoverType_Xml() { - ContentHandle content = resourceToContentHandle("xml.xml"); - String type = ArtifactTypeUtil.determineArtifactType(content, null, null, availableTypes); + TypedContent content = resourceToTypedContentHandle("xml.xml"); + String type = ArtifactTypeUtil.determineArtifactType(content, null, artifactTypeUtilProviderFactory); Assertions.assertEquals(ArtifactType.XML, type); } /** - * Test method for {@link io.apicurio.registry.util.ArtifactTypeUtil#discoverType(ContentHandle, java.lang.String)}. + * Test method for {@link io.apicurio.registry.util.ArtifactTypeUtil#determineArtifactType(TypedContent, String, ArtifactTypeUtilProviderFactory)}. */ @Test void testDiscoverType_Xsd() { - ContentHandle content = resourceToContentHandle("xml-schema.xsd"); - String type = ArtifactTypeUtil.determineArtifactType(content, null, null, availableTypes); + TypedContent content = resourceToTypedContentHandle("xml-schema.xsd"); + String type = ArtifactTypeUtil.determineArtifactType(content, null, artifactTypeUtilProviderFactory); Assertions.assertEquals(ArtifactType.XSD, type); } /** - * Test method for {@link io.apicurio.registry.util.ArtifactTypeUtil#discoverType(ContentHandle, java.lang.String)}. + * Test method for {@link io.apicurio.registry.util.ArtifactTypeUtil#determineArtifactType(TypedContent, String, ArtifactTypeUtilProviderFactory)}. */ @Test void testDiscoverType_Wsdl() { - ContentHandle content = resourceToContentHandle("wsdl.wsdl"); - String type = ArtifactTypeUtil.determineArtifactType(content, null, null, availableTypes); + TypedContent content = resourceToTypedContentHandle("wsdl.wsdl"); + String type = ArtifactTypeUtil.determineArtifactType(content, null, artifactTypeUtilProviderFactory); Assertions.assertEquals(ArtifactType.WSDL, type); - content = resourceToContentHandle("wsdl-2.0.wsdl"); - type = ArtifactTypeUtil.determineArtifactType(content, null, null, availableTypes); + content = resourceToTypedContentHandle("wsdl-2.0.wsdl"); + type = ArtifactTypeUtil.determineArtifactType(content, null, artifactTypeUtilProviderFactory); Assertions.assertEquals(ArtifactType.WSDL, type); } diff --git a/app/src/test/java/io/apicurio/registry/util/ContentTypeUtilTest.java b/app/src/test/java/io/apicurio/registry/util/ContentTypeUtilTest.java index a6ef69aeca..eb207b94c1 100644 --- a/app/src/test/java/io/apicurio/registry/util/ContentTypeUtilTest.java +++ b/app/src/test/java/io/apicurio/registry/util/ContentTypeUtilTest.java @@ -1,5 +1,6 @@ package io.apicurio.registry.util; +import io.apicurio.registry.content.util.ContentTypeUtil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -52,7 +53,7 @@ class ContentTypeUtilTest { "}"; /** - * Test method for {@link io.apicurio.registry.util.ContentTypeUtil#yamlToJson(io.apicurio.registry.content.ContentHandle)}. + * Test method for {@link ContentTypeUtil#yamlToJson(io.apicurio.registry.content.ContentHandle)}. */ @Test void testYamlToJson() throws Exception { diff --git a/common/src/main/java/io/apicurio/registry/utils/DtoUtil.java b/common/src/main/java/io/apicurio/registry/utils/DtoUtil.java new file mode 100644 index 0000000000..34737f52fe --- /dev/null +++ b/common/src/main/java/io/apicurio/registry/utils/DtoUtil.java @@ -0,0 +1,13 @@ +package io.apicurio.registry.utils; + +public final class DtoUtil { + + public static String registryAuthPropertyToApp(String propertyName) { + return propertyName.replace("apicurio.auth.", "app.authn."); + } + + public static String appAuthPropertyToRegistry(String propertyName) { + return propertyName.replace("app.authn.", "apicurio.auth."); + } + +} diff --git a/app/src/main/java/io/apicurio/registry/util/VersionUtil.java b/common/src/main/java/io/apicurio/registry/utils/VersionUtil.java similarity index 95% rename from app/src/main/java/io/apicurio/registry/util/VersionUtil.java rename to common/src/main/java/io/apicurio/registry/utils/VersionUtil.java index 92b2674812..e65273dd61 100644 --- a/app/src/main/java/io/apicurio/registry/util/VersionUtil.java +++ b/common/src/main/java/io/apicurio/registry/utils/VersionUtil.java @@ -1,4 +1,4 @@ -package io.apicurio.registry.util; +package io.apicurio.registry.utils; public class VersionUtil { diff --git a/schema-util/asyncapi/src/main/java/io/apicurio/registry/content/canon/AsyncApiContentCanonicalizer.java b/schema-util/asyncapi/src/main/java/io/apicurio/registry/content/canon/AsyncApiContentCanonicalizer.java new file mode 100644 index 0000000000..f291a25049 --- /dev/null +++ b/schema-util/asyncapi/src/main/java/io/apicurio/registry/content/canon/AsyncApiContentCanonicalizer.java @@ -0,0 +1,4 @@ +package io.apicurio.registry.content.canon; + +public class AsyncApiContentCanonicalizer extends OpenApiContentCanonicalizer { +} diff --git a/schema-util/avro/src/main/java/io/apicurio/registry/content/canon/AvroContentCanonicalizer.java b/schema-util/avro/src/main/java/io/apicurio/registry/content/canon/AvroContentCanonicalizer.java index 4a84cfae0f..59c3aafb1b 100644 --- a/schema-util/avro/src/main/java/io/apicurio/registry/content/canon/AvroContentCanonicalizer.java +++ b/schema-util/avro/src/main/java/io/apicurio/registry/content/canon/AvroContentCanonicalizer.java @@ -1,23 +1,22 @@ package io.apicurio.registry.content.canon; -import java.util.Comparator; -import java.util.Iterator; -import java.util.Set; -import java.util.TreeSet; - -import org.apache.avro.Schema; - import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; - import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.types.ContentTypes; +import org.apache.avro.Schema; import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.TreeSet; /** * An Avro implementation of a content Canonicalizer that handles avro references. @@ -34,12 +33,12 @@ public class AvroContentCanonicalizer implements ContentCanonicalizer { }; /** - * @see ContentCanonicalizer#canonicalize(io.apicurio.registry.content.ContentHandle, Map) + * @see ContentCanonicalizer#canonicalize(io.apicurio.registry.content.TypedContent, Map) */ @Override - public ContentHandle canonicalize(ContentHandle content, Map resolvedReferences) { + public TypedContent canonicalize(TypedContent content, Map resolvedReferences) { try { - JsonNode root = mapper.readTree(content.content()); + JsonNode root = mapper.readTree(content.getContent().content()); // reorder "fields" property JsonNode fieldsNode = root.get("fields"); @@ -54,17 +53,17 @@ public ContentHandle canonicalize(ContentHandle content, Map schemaRefs = new ArrayList<>(); - for (ContentHandle referencedContent : resolvedReferences.values()) { - Schema schemaRef = parser.parse(referencedContent.content()); + for (TypedContent referencedContent : resolvedReferences.values()) { + Schema schemaRef = parser.parse(referencedContent.getContent().content()); schemaRefs.add(schemaRef); } - final Schema schema = parser.parse(content.content()); - return ContentHandle.create(schema.toString(schemaRefs, false)); + final Schema schema = parser.parse(content.getContent().content()); + return TypedContent.create(ContentHandle.create(schema.toString(schemaRefs, false)), ContentTypes.APPLICATION_JSON); } } } diff --git a/schema-util/avro/src/main/java/io/apicurio/registry/content/canon/EnhancedAvroContentCanonicalizer.java b/schema-util/avro/src/main/java/io/apicurio/registry/content/canon/EnhancedAvroContentCanonicalizer.java index fa049dc919..215c34273e 100644 --- a/schema-util/avro/src/main/java/io/apicurio/registry/content/canon/EnhancedAvroContentCanonicalizer.java +++ b/schema-util/avro/src/main/java/io/apicurio/registry/content/canon/EnhancedAvroContentCanonicalizer.java @@ -1,5 +1,8 @@ package io.apicurio.registry.content.canon; +import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.types.ContentTypes; import org.apache.avro.AvroRuntimeException; import org.apache.avro.Schema; @@ -8,8 +11,6 @@ import java.util.List; import java.util.Map; -import io.apicurio.registry.content.ContentHandle; - /** * An Avro implementation of a content Canonicalizer that handles avro references. * A custom version that can be used to check subject compatibilities. It does not reorder fields. @@ -115,11 +116,11 @@ private static String createKey(Schema schema) { } /** - * @see ContentCanonicalizer#canonicalize(ContentHandle, Map) + * @see ContentCanonicalizer#canonicalize(TypedContent, Map) */ @Override - public ContentHandle canonicalize(ContentHandle content, Map resolvedReferences) { - String normalisedSchema = normalizeSchema(content.content()).toString(); - return ContentHandle.create(normalisedSchema); + public TypedContent canonicalize(TypedContent content, Map resolvedReferences) { + String normalisedSchema = normalizeSchema(content.getContent().content()).toString(); + return TypedContent.create(ContentHandle.create(normalisedSchema), ContentTypes.APPLICATION_JSON); } } diff --git a/schema-util/avro/src/main/java/io/apicurio/registry/content/dereference/AvroDereferencer.java b/schema-util/avro/src/main/java/io/apicurio/registry/content/dereference/AvroDereferencer.java index 28e69f3186..e78ebebb21 100644 --- a/schema-util/avro/src/main/java/io/apicurio/registry/content/dereference/AvroDereferencer.java +++ b/schema-util/avro/src/main/java/io/apicurio/registry/content/dereference/AvroDereferencer.java @@ -1,28 +1,29 @@ package io.apicurio.registry.content.dereference; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.types.ContentTypes; import org.apache.avro.Schema; - import java.util.Map; public class AvroDereferencer implements ContentDereferencer { @Override - public ContentHandle dereference(ContentHandle content, Map resolvedReferences) { + public TypedContent dereference(TypedContent content, Map resolvedReferences) { final Schema.Parser parser = new Schema.Parser(); - for (ContentHandle referencedContent : resolvedReferences.values()) { - parser.parse(referencedContent.content()); + for (TypedContent referencedContent : resolvedReferences.values()) { + parser.parse(referencedContent.getContent().content()); } - final Schema schema = parser.parse(content.content()); - return ContentHandle.create(schema.toString()); + final Schema schema = parser.parse(content.getContent().content()); + return TypedContent.create(ContentHandle.create(schema.toString()), ContentTypes.APPLICATION_JSON); } /** - * @see io.apicurio.registry.content.dereference.ContentDereferencer#rewriteReferences(io.apicurio.registry.content.ContentHandle, java.util.Map) + * @see io.apicurio.registry.content.dereference.ContentDereferencer#rewriteReferences(io.apicurio.registry.content.TypedContent, java.util.Map) */ @Override - public ContentHandle rewriteReferences(ContentHandle content, Map resolvedReferenceUrls) { + public TypedContent rewriteReferences(TypedContent content, Map resolvedReferenceUrls) { // Avro does not support rewriting references. A reference in Avro is a QName of a type // defined in another .avsc file. The location of that other file is not included in the Avro // specification (in other words there is no "import" statement). So rewriting is meaningless diff --git a/schema-util/avro/src/main/java/io/apicurio/registry/content/refs/AvroReferenceFinder.java b/schema-util/avro/src/main/java/io/apicurio/registry/content/refs/AvroReferenceFinder.java index 5199ba02d5..22635c72e2 100644 --- a/schema-util/avro/src/main/java/io/apicurio/registry/content/refs/AvroReferenceFinder.java +++ b/schema-util/avro/src/main/java/io/apicurio/registry/content/refs/AvroReferenceFinder.java @@ -1,18 +1,16 @@ package io.apicurio.registry.content.refs; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Collectors; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; +import io.apicurio.registry.content.TypedContent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import io.apicurio.registry.content.ContentHandle; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; /** * An Apache Avro implementation of a reference finder. @@ -25,12 +23,12 @@ public class AvroReferenceFinder implements ReferenceFinder { private static final Set PRIMITIVE_TYPES = Set.of("null", "boolean", "int", "long", "float", "double", "bytes", "string"); /** - * @see io.apicurio.registry.content.refs.ReferenceFinder#findExternalReferences(io.apicurio.registry.content.ContentHandle) + * @see io.apicurio.registry.content.refs.ReferenceFinder#findExternalReferences(TypedContent) */ @Override - public Set findExternalReferences(ContentHandle content) { + public Set findExternalReferences(TypedContent content) { try { - JsonNode tree = mapper.readTree(content.content()); + JsonNode tree = mapper.readTree(content.getContent().content()); Set externalTypes = new HashSet<>(); findExternalTypesIn(tree, externalTypes); return externalTypes.stream().map(type -> new ExternalReference(type)).collect(Collectors.toSet()); diff --git a/schema-util/avro/src/main/java/io/apicurio/registry/rules/compatibility/AvroCompatibilityChecker.java b/schema-util/avro/src/main/java/io/apicurio/registry/rules/compatibility/AvroCompatibilityChecker.java index 95abcd63c2..b9d18068a6 100644 --- a/schema-util/avro/src/main/java/io/apicurio/registry/rules/compatibility/AvroCompatibilityChecker.java +++ b/schema-util/avro/src/main/java/io/apicurio/registry/rules/compatibility/AvroCompatibilityChecker.java @@ -1,7 +1,7 @@ package io.apicurio.registry.rules.compatibility; import com.google.common.collect.ImmutableSet; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rules.UnprocessableSchemaException; import org.apache.avro.AvroRuntimeException; import org.apache.avro.Schema; @@ -15,17 +15,17 @@ public class AvroCompatibilityChecker extends AbstractCompatibilityChecker { @Override - protected Set isBackwardsCompatibleWith(String existing, String proposed, Map resolvedReferences) { + protected Set isBackwardsCompatibleWith(String existing, String proposed, Map resolvedReferences) { try { Schema.Parser existingParser = new Schema.Parser(); - for (ContentHandle schema : resolvedReferences.values()) { - existingParser.parse(schema.content()); + for (TypedContent schema : resolvedReferences.values()) { + existingParser.parse(schema.getContent().content()); } final Schema existingSchema = existingParser.parse(existing); Schema.Parser proposingParser = new Schema.Parser(); - for (ContentHandle schema : resolvedReferences.values()) { - proposingParser.parse(schema.content()); + for (TypedContent schema : resolvedReferences.values()) { + proposingParser.parse(schema.getContent().content()); } final Schema proposedSchema = proposingParser.parse(proposed); diff --git a/schema-util/avro/src/main/java/io/apicurio/registry/rules/validity/AvroContentValidator.java b/schema-util/avro/src/main/java/io/apicurio/registry/rules/validity/AvroContentValidator.java index f3468b49e9..8f238522f6 100644 --- a/schema-util/avro/src/main/java/io/apicurio/registry/rules/validity/AvroContentValidator.java +++ b/schema-util/avro/src/main/java/io/apicurio/registry/rules/validity/AvroContentValidator.java @@ -1,32 +1,32 @@ package io.apicurio.registry.rules.validity; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import org.apache.avro.Schema; - -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rest.v3.beans.ArtifactReference; import io.apicurio.registry.rules.RuleViolation; import io.apicurio.registry.rules.RuleViolationException; import io.apicurio.registry.rules.integrity.IntegrityLevel; import io.apicurio.registry.types.RuleType; +import org.apache.avro.Schema; + +import java.util.Collections; +import java.util.List; +import java.util.Map; /** * A content validator implementation for the Avro content type. */ public class AvroContentValidator implements ContentValidator { - private static final String DUMMY_AVRO_RECORD = "{\n" - + " \"type\": \"record\",\n" - + " \"namespace\": \"NAMESPACE\",\n" - + " \"name\": \"NAME\",\n" - + " \"fields\": [\n" - + " { \"name\": \"first\", \"type\": \"string\" },\n" - + " { \"name\": \"last\", \"type\": \"string\" }\n" - + " ]\n" - + "}"; + private static final String DUMMY_AVRO_RECORD = """ + { + "type": "record", + "namespace": "NAMESPACE", + "name": "NAME", + "fields": [ + { "name": "first", "type": "string" }, + { "name": "last", "type": "string" } + ] + }"""; /** * Constructor. @@ -35,17 +35,17 @@ public AvroContentValidator() { } /** - * @see io.apicurio.registry.rules.validity.ContentValidator#validate(ValidityLevel, ContentHandle, Map) + * @see io.apicurio.registry.rules.validity.ContentValidator#validate(ValidityLevel, TypedContent, Map) */ @Override - public void validate(ValidityLevel level, ContentHandle artifactContent, Map resolvedReferences) throws RuleViolationException { + public void validate(ValidityLevel level, TypedContent content, Map resolvedReferences) throws RuleViolationException { if (level == ValidityLevel.SYNTAX_ONLY || level == ValidityLevel.FULL) { try { Schema.Parser parser = new Schema.Parser(); - for (ContentHandle schema : resolvedReferences.values()) { - parser.parse(schema.content()); + for (TypedContent schemaTC : resolvedReferences.values()) { + parser.parse(schemaTC.getContent().content()); } - parser.parse(artifactContent.content()); + parser.parse(content.getContent().content()); } catch (Exception e) { throw new RuleViolationException("Syntax violation for Avro artifact.", RuleType.VALIDITY, level.name(), e); } @@ -53,10 +53,10 @@ public void validate(ValidityLevel level, ContentHandle artifactContent, Map references) throws RuleViolationException { + public void validateReferences(TypedContent content, List references) throws RuleViolationException { try { Schema.Parser parser = new Schema.Parser(); references.forEach(ref -> { @@ -68,7 +68,7 @@ public void validateReferences(ContentHandle artifactContent, List()).content()); + private final Schema schema1 = new Schema.Parser().parse(avroCanonicalizer.canonicalize(TypedContent.create(ContentHandle.create(schemaString1), ContentTypes.APPLICATION_JSON), new HashMap<>()).getContent().content()); private final String schemaString2 = "{\"type\":\"record\"," + "\"name\":\"myrecord\"," + "\"fields\":" + "[{\"type\":\"string\",\"name\":\"f1\"}," + " {\"type\":\"string\",\"name\":\"f2\", \"default\": \"foo\"}]}"; - private final Schema schema2 = new Schema.Parser().parse(avroCanonicalizer.canonicalize(ContentHandle.create(schemaString2), new HashMap<>()).content()); + private final Schema schema2 = new Schema.Parser().parse(avroCanonicalizer.canonicalize(TypedContent.create(ContentHandle.create(schemaString2), ContentTypes.APPLICATION_JSON), new HashMap<>()).getContent().content()); private final String schemaString3 = "{\"type\":\"record\"," + "\"name\":\"myrecord\"," + "\"fields\":" + "[{\"type\":\"string\",\"name\":\"f1\"}," + " {\"type\":\"string\",\"name\":\"f2\"}]}"; - private final Schema schema3 = new Schema.Parser().parse(avroCanonicalizer.canonicalize(ContentHandle.create(schemaString3), new HashMap<>()).content()); + private final Schema schema3 = new Schema.Parser().parse(avroCanonicalizer.canonicalize(TypedContent.create(ContentHandle.create(schemaString3), ContentTypes.APPLICATION_JSON), new HashMap<>()).getContent().content()); private final String schemaString4 = "{\"type\":\"record\"," + "\"name\":\"myrecord\"," + "\"fields\":" + "[{\"type\":\"string\",\"name\":\"f1_new\", \"aliases\": [\"f1\"]}]}"; - private final Schema schema4 = new Schema.Parser().parse(avroCanonicalizer.canonicalize(ContentHandle.create(schemaString4), new HashMap<>()).content()); + private final Schema schema4 = new Schema.Parser().parse(avroCanonicalizer.canonicalize(TypedContent.create(ContentHandle.create(schemaString4), ContentTypes.APPLICATION_JSON), new HashMap<>()).getContent().content()); private final String schemaString6 = "{\"type\":\"record\"," + "\"name\":\"myrecord\"," + "\"fields\":" + "[{\"type\":[\"null\", \"string\"],\"name\":\"f1\"," + " \"doc\":\"doc of f1\"}]}"; - private final Schema schema6 = new Schema.Parser().parse(avroCanonicalizer.canonicalize(ContentHandle.create(schemaString6), new HashMap<>()).content()); + private final Schema schema6 = new Schema.Parser().parse(avroCanonicalizer.canonicalize(TypedContent.create(ContentHandle.create(schemaString6), ContentTypes.APPLICATION_JSON), new HashMap<>()).getContent().content()); private final String schemaString7 = "{\"type\":\"record\"," + "\"name\":\"myrecord\"," + "\"fields\":" + "[{\"type\":[\"null\", \"string\", \"int\"],\"name\":\"f1\"," + " \"doc\":\"doc of f1\"}]}"; - private final Schema schema7 = new Schema.Parser().parse(avroCanonicalizer.canonicalize(ContentHandle.create(schemaString7), new HashMap<>()).content()); + private final Schema schema7 = new Schema.Parser().parse(avroCanonicalizer.canonicalize(TypedContent.create(ContentHandle.create(schemaString7), ContentTypes.APPLICATION_JSON), new HashMap<>()).getContent().content()); private final String schemaString8 = "{\"type\":\"record\"," + "\"name\":\"myrecord\"," @@ -60,7 +61,7 @@ class AvroCompatibilityTest { + "[{\"type\":\"string\",\"name\":\"f1\"}," + " {\"type\":\"string\",\"name\":\"f2\", \"default\": \"foo\"}," + " {\"type\":\"string\",\"name\":\"f3\", \"default\": \"bar\"}]}"; - private final Schema schema8 = new Schema.Parser().parse(avroCanonicalizer.canonicalize(ContentHandle.create(schemaString8), new HashMap<>()).content()); + private final Schema schema8 = new Schema.Parser().parse(avroCanonicalizer.canonicalize(TypedContent.create(ContentHandle.create(schemaString8), ContentTypes.APPLICATION_JSON), new HashMap<>()).getContent().content()); /* * Backward compatibility: A new schema is backward compatible if it can be used to read the data diff --git a/schema-util/avro/src/test/java/io/apicurio/registry/content/canon/SchemaNormalizerTest.java b/schema-util/avro/src/test/java/io/apicurio/registry/content/canon/SchemaNormalizerTest.java index 61b61643ea..dc57c6f7df 100644 --- a/schema-util/avro/src/test/java/io/apicurio/registry/content/canon/SchemaNormalizerTest.java +++ b/schema-util/avro/src/test/java/io/apicurio/registry/content/canon/SchemaNormalizerTest.java @@ -1,5 +1,8 @@ package io.apicurio.registry.content.canon; +import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.types.ContentTypes; import org.apache.avro.Schema; import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.Test; @@ -11,14 +14,16 @@ import java.util.HashMap; import java.util.List; -import io.apicurio.registry.content.ContentHandle; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; class SchemaNormalizerTest { + private TypedContent toTypedContent(String content) { + return TypedContent.create(ContentHandle.create(content), ContentTypes.APPLICATION_JSON); + } + @Test void parseSchema_SchemasWithOptionalAttributesInRoot_Equal() { // prepare @@ -38,12 +43,12 @@ void parseSchema_SchemasWithOptionalAttributesInRoot_Equal() { // act EnhancedAvroContentCanonicalizer canonicalizer = new EnhancedAvroContentCanonicalizer(); - ContentHandle schema = canonicalizer.canonicalize(ContentHandle.create(schemaStr), new HashMap<>()); - ContentHandle schemaWithOptional = canonicalizer.canonicalize(ContentHandle.create(schemaWithOptionalStr), new HashMap<>()); + TypedContent schema = canonicalizer.canonicalize(toTypedContent(schemaStr), new HashMap<>()); + TypedContent schemaWithOptional = canonicalizer.canonicalize(toTypedContent(schemaWithOptionalStr), new HashMap<>()); // assert - assertEquals(schema.content(), schemaWithOptional.content()); - assertEquals(Schema.parseJsonToObject(schema.content()), Schema.parseJsonToObject(schemaWithOptional.content())); + assertEquals(schema.getContent().content(), schemaWithOptional.getContent().content()); + assertEquals(Schema.parseJsonToObject(schema.getContent().content()), Schema.parseJsonToObject(schemaWithOptional.getContent().content())); } @Test @@ -65,12 +70,12 @@ void parseSchema_SchemaWithNamespaceInNameAndInNamespaceField_Equal() { // act EnhancedAvroContentCanonicalizer canonicalizer = new EnhancedAvroContentCanonicalizer(); - ContentHandle schema = canonicalizer.canonicalize(ContentHandle.create(schemaStr), new HashMap<>()); - ContentHandle schemaWithNamespaceField = canonicalizer.canonicalize(ContentHandle.create(schemaWithNamespaceFieldStr), new HashMap<>()); + TypedContent schema = canonicalizer.canonicalize(toTypedContent(schemaStr), new HashMap<>()); + TypedContent schemaWithNamespaceField = canonicalizer.canonicalize(toTypedContent(schemaWithNamespaceFieldStr), new HashMap<>()); // assert - assertEquals(schema.content(), schemaWithNamespaceField.content()); - assertEquals(Schema.parseJsonToObject(schema.content()), Schema.parseJsonToObject(schemaWithNamespaceField.content())); + assertEquals(schema.getContent().content(), schemaWithNamespaceField.getContent().content()); + assertEquals(Schema.parseJsonToObject(schema.getContent().content()), Schema.parseJsonToObject(schemaWithNamespaceField.getContent().content())); } @Test @@ -92,12 +97,12 @@ void parseSchema_SchemaWithDifferentNamespaceInNameAndInNamespaceField_NotEqual( // act EnhancedAvroContentCanonicalizer canonicalizer = new EnhancedAvroContentCanonicalizer(); - ContentHandle schema = canonicalizer.canonicalize(ContentHandle.create(schemaStr), new HashMap<>()); - ContentHandle schemaWithNamespaceField = canonicalizer.canonicalize(ContentHandle.create(schemaWithNamespaceFieldStr), new HashMap<>()); + TypedContent schema = canonicalizer.canonicalize(toTypedContent(schemaStr), new HashMap<>()); + TypedContent schemaWithNamespaceField = canonicalizer.canonicalize(toTypedContent(schemaWithNamespaceFieldStr), new HashMap<>()); // assert - assertNotEquals(schema.content(), schemaWithNamespaceField.content()); - assertNotEquals(Schema.parseJsonToObject(schema.content()), Schema.parseJsonToObject(schemaWithNamespaceField.content())); + assertNotEquals(schema.getContent().content(), schemaWithNamespaceField.getContent().content()); + assertNotEquals(Schema.parseJsonToObject(schema.getContent().content()), Schema.parseJsonToObject(schemaWithNamespaceField.getContent().content())); } @Test @@ -120,12 +125,12 @@ void parseSchema_SchemasWithDifferenceAttributesOrderInRoot_Equal() { // act EnhancedAvroContentCanonicalizer canonicalizer = new EnhancedAvroContentCanonicalizer(); - ContentHandle schema = canonicalizer.canonicalize(ContentHandle.create(schemaStr), new HashMap<>()); - ContentHandle schemaWithDifferenceAttributesOrder = canonicalizer.canonicalize(ContentHandle.create(schemaWithDifferenceAttributesOrderStr), new HashMap<>()); + TypedContent schema = canonicalizer.canonicalize(toTypedContent(schemaStr), new HashMap<>()); + TypedContent schemaWithDifferenceAttributesOrder = canonicalizer.canonicalize(toTypedContent(schemaWithDifferenceAttributesOrderStr), new HashMap<>()); // assert - assertEquals(schema.content(), schemaWithDifferenceAttributesOrder.content()); - assertEquals(Schema.parseJsonToObject(schema.content()), Schema.parseJsonToObject(schemaWithDifferenceAttributesOrder.content())); + assertEquals(schema.getContent().content(), schemaWithDifferenceAttributesOrder.getContent().content()); + assertEquals(Schema.parseJsonToObject(schema.getContent().content()), Schema.parseJsonToObject(schemaWithDifferenceAttributesOrder.getContent().content())); } @Test @@ -156,12 +161,12 @@ void parseSchema_SchemasWithOptionalAttributesInField_Equal() { // act EnhancedAvroContentCanonicalizer canonicalizer = new EnhancedAvroContentCanonicalizer(); - ContentHandle schema = canonicalizer.canonicalize(ContentHandle.create(schemaStr), new HashMap<>()); - ContentHandle schemaWithOptionalAttributesInField = canonicalizer.canonicalize(ContentHandle.create(schemaWithOptionalAttributesInFieldStr), new HashMap<>()); + TypedContent schema = canonicalizer.canonicalize(toTypedContent(schemaStr), new HashMap<>()); + TypedContent schemaWithOptionalAttributesInField = canonicalizer.canonicalize(toTypedContent(schemaWithOptionalAttributesInFieldStr), new HashMap<>()); // assert - assertEquals(schema.content(), schemaWithOptionalAttributesInField.content()); - assertEquals(Schema.parseJsonToObject(schema.content()), Schema.parseJsonToObject(schemaWithOptionalAttributesInField.content())); + assertEquals(schema.getContent().content(), schemaWithOptionalAttributesInField.getContent().content()); + assertEquals(Schema.parseJsonToObject(schema.getContent().content()), Schema.parseJsonToObject(schemaWithOptionalAttributesInField.getContent().content())); } @Test @@ -191,12 +196,12 @@ void parseSchema_SchemasWithDifferenceAttributesOrderInField_Equal() { // Act EnhancedAvroContentCanonicalizer canonicalizer = new EnhancedAvroContentCanonicalizer(); - ContentHandle schema = canonicalizer.canonicalize(ContentHandle.create(schemaStr), new HashMap<>()); - ContentHandle schemasWithDifferenceAttributesOrderInField = canonicalizer.canonicalize(ContentHandle.create(schemasWithDifferenceAttributesOrderInFieldStr), new HashMap<>()); + TypedContent schema = canonicalizer.canonicalize(toTypedContent(schemaStr), new HashMap<>()); + TypedContent schemasWithDifferenceAttributesOrderInField = canonicalizer.canonicalize(toTypedContent(schemasWithDifferenceAttributesOrderInFieldStr), new HashMap<>()); // Assert - assertEquals(schema.content(), schemasWithDifferenceAttributesOrderInField.content()); - assertEquals(Schema.parseJsonToObject(schema.content()), Schema.parseJsonToObject(schemasWithDifferenceAttributesOrderInField.content())); + assertEquals(schema.getContent().content(), schemasWithDifferenceAttributesOrderInField.getContent().content()); + assertEquals(Schema.parseJsonToObject(schema.getContent().content()), Schema.parseJsonToObject(schemasWithDifferenceAttributesOrderInField.getContent().content())); } @Test @@ -234,12 +239,12 @@ void parseSchema_SchemasWithFieldsInDifferentOrder_NotEqual() { // Act EnhancedAvroContentCanonicalizer canonicalizer = new EnhancedAvroContentCanonicalizer(); - ContentHandle schema = canonicalizer.canonicalize(ContentHandle.create(schemaStr), new HashMap<>()); - ContentHandle schemaWithFieldsInDifferentOrder = canonicalizer.canonicalize(ContentHandle.create(schemaWithFieldsInDifferentOrderStr), new HashMap<>()); + TypedContent schema = canonicalizer.canonicalize(toTypedContent(schemaStr), new HashMap<>()); + TypedContent schemaWithFieldsInDifferentOrder = canonicalizer.canonicalize(toTypedContent(schemaWithFieldsInDifferentOrderStr), new HashMap<>()); // Assert - assertNotEquals(schema.content(), schemaWithFieldsInDifferentOrder.content()); - assertNotEquals(Schema.parseJsonToObject(schema.content()), Schema.parseJsonToObject(schemaWithFieldsInDifferentOrder.content())); + assertNotEquals(schema.getContent().content(), schemaWithFieldsInDifferentOrder.getContent().content()); + assertNotEquals(Schema.parseJsonToObject(schema.getContent().content()), Schema.parseJsonToObject(schemaWithFieldsInDifferentOrder.getContent().content())); } @Test @@ -301,18 +306,18 @@ void parseSchema_NestedSchemasWithDifferenceAttributesOrderInField_Equal() { // Act EnhancedAvroContentCanonicalizer canonicalizer = new EnhancedAvroContentCanonicalizer(); - ContentHandle nestedSchema = canonicalizer.canonicalize(ContentHandle.create(nestedSchemaStr), new HashMap<>()); - ContentHandle schemaWithDifferenceAttributesOrderInNestedSchema = canonicalizer.canonicalize(ContentHandle.create(schemaWithDifferenceAttributesOrderInNestedSchemaStr), new HashMap<>()); + TypedContent nestedSchema = canonicalizer.canonicalize(toTypedContent(nestedSchemaStr), new HashMap<>()); + TypedContent schemaWithDifferenceAttributesOrderInNestedSchema = canonicalizer.canonicalize(toTypedContent(schemaWithDifferenceAttributesOrderInNestedSchemaStr), new HashMap<>()); // Assert - assertEquals(nestedSchema.content(), schemaWithDifferenceAttributesOrderInNestedSchema.content()); - assertEquals(Schema.parseJsonToObject(nestedSchema.content()), Schema.parseJsonToObject(schemaWithDifferenceAttributesOrderInNestedSchema.content())); + assertEquals(nestedSchema.getContent().content(), schemaWithDifferenceAttributesOrderInNestedSchema.getContent().content()); + assertEquals(Schema.parseJsonToObject(nestedSchema.getContent().content()), Schema.parseJsonToObject(schemaWithDifferenceAttributesOrderInNestedSchema.getContent().content())); } @Test void parseSchema_NestedSchemasOfSameType() throws Exception { final List schemas = new ArrayList<>(); - final List normalizedSchemas = new ArrayList<>(); + final List normalizedSchemas = new ArrayList<>(); // given a schema that has a field referencing its own type schemas.add(getSchemaFromResource("avro/simple/schema-with-same-nested-schema.avsc")); schemas.add(getSchemaFromResource("avro/simple/schema-with-same-nested-schema2.avsc")); @@ -322,15 +327,15 @@ void parseSchema_NestedSchemasOfSameType() throws Exception { EnhancedAvroContentCanonicalizer canonicalizer = new EnhancedAvroContentCanonicalizer(); schemas.forEach(schema -> { - ContentHandle avroSchema = canonicalizer.canonicalize(ContentHandle.create(schema), new HashMap<>()); + TypedContent avroSchema = canonicalizer.canonicalize(toTypedContent(schema), new HashMap<>()); // the schema should be parsed without infinite recursion assertNotNull(avroSchema); normalizedSchemas.add(avroSchema); }); // and the parsed schema should still be the same - assertEquals(normalizedSchemas.get(0).content(), normalizedSchemas.get(1).content()); - assertEquals(Schema.parseJsonToObject(normalizedSchemas.get(0).content()), Schema.parseJsonToObject(normalizedSchemas.get(1).content())); + assertEquals(normalizedSchemas.get(0).getContent().content(), normalizedSchemas.get(1).getContent().content()); + assertEquals(Schema.parseJsonToObject(normalizedSchemas.get(0).getContent().content()), Schema.parseJsonToObject(normalizedSchemas.get(1).getContent().content())); } @Test @@ -341,7 +346,7 @@ void parseSchema_unionOfNullAndSelf() throws Exception { // the schema should be parsed with a non-null result EnhancedAvroContentCanonicalizer canonicalizer = new EnhancedAvroContentCanonicalizer(); - final ContentHandle parsed = canonicalizer.canonicalize(ContentHandle.create(schemaWithNullUnion), new HashMap<>()); + final TypedContent parsed = canonicalizer.canonicalize(toTypedContent(schemaWithNullUnion), new HashMap<>()); assertNotNull(parsed); } @@ -354,7 +359,7 @@ void parseSchema_withJavaType() throws Exception { // the schema should be parsed with a non-null result EnhancedAvroContentCanonicalizer canonicalizer = new EnhancedAvroContentCanonicalizer(); - final ContentHandle parsed = canonicalizer.canonicalize(ContentHandle.create(schemaWithJavaType), new HashMap<>()); + final TypedContent parsed = canonicalizer.canonicalize(toTypedContent(schemaWithJavaType), new HashMap<>()); assertNotNull(parsed); } @@ -367,7 +372,7 @@ void parseSchema_withLogicalType() throws Exception { // the schema should be parsed with a non-null result EnhancedAvroContentCanonicalizer canonicalizer = new EnhancedAvroContentCanonicalizer(); - final ContentHandle parsed = canonicalizer.canonicalize(ContentHandle.create(schemaWithCustomType), new HashMap<>()); + final TypedContent parsed = canonicalizer.canonicalize(toTypedContent(schemaWithCustomType), new HashMap<>()); assertNotNull(parsed); } @@ -380,7 +385,7 @@ void parseSchema_withNestedEnumAndDefault() throws Exception { // the schema should be parsed with a non-null result EnhancedAvroContentCanonicalizer canonicalizer = new EnhancedAvroContentCanonicalizer(); - final ContentHandle parsed = canonicalizer.canonicalize(ContentHandle.create(schemaWithCustomType), new HashMap<>()); + final TypedContent parsed = canonicalizer.canonicalize(toTypedContent(schemaWithCustomType), new HashMap<>()); assertNotNull(parsed); } diff --git a/schema-util/common/pom.xml b/schema-util/common/pom.xml index 49c1466eca..a554ddb473 100644 --- a/schema-util/common/pom.xml +++ b/schema-util/common/pom.xml @@ -32,6 +32,23 @@ commons-codec + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + + + snakeyaml + org.yaml + + + + + + org.yaml + snakeyaml + ${snakeyaml.version} + + com.fasterxml.jackson.module jackson-module-parameter-names diff --git a/schema-util/common/src/main/java/io/apicurio/registry/content/TypedContent.java b/schema-util/common/src/main/java/io/apicurio/registry/content/TypedContent.java new file mode 100644 index 0000000000..f6288d09b6 --- /dev/null +++ b/schema-util/common/src/main/java/io/apicurio/registry/content/TypedContent.java @@ -0,0 +1,20 @@ +package io.apicurio.registry.content; + +public interface TypedContent { + + static TypedContent create(ContentHandle content, String contentType) { + return TypedContentImpl.builder() + .contentType(contentType) + .content(content) + .build(); + } + static TypedContent create(String content, String contentType) { + return TypedContentImpl.builder() + .contentType(contentType) + .content(ContentHandle.create(content)) + .build(); + } + + ContentHandle getContent(); + String getContentType(); +} diff --git a/schema-util/common/src/main/java/io/apicurio/registry/content/TypedContentImpl.java b/schema-util/common/src/main/java/io/apicurio/registry/content/TypedContentImpl.java new file mode 100644 index 0000000000..3f16399b8d --- /dev/null +++ b/schema-util/common/src/main/java/io/apicurio/registry/content/TypedContentImpl.java @@ -0,0 +1,13 @@ +package io.apicurio.registry.content; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder +public class TypedContentImpl implements TypedContent { + private String contentType; + private ContentHandle content; +} diff --git a/schema-util/common/src/main/java/io/apicurio/registry/content/canon/ContentCanonicalizer.java b/schema-util/common/src/main/java/io/apicurio/registry/content/canon/ContentCanonicalizer.java index e06768cba1..38416f9f33 100644 --- a/schema-util/common/src/main/java/io/apicurio/registry/content/canon/ContentCanonicalizer.java +++ b/schema-util/common/src/main/java/io/apicurio/registry/content/canon/ContentCanonicalizer.java @@ -1,6 +1,6 @@ package io.apicurio.registry.content.canon; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import java.util.Map; @@ -16,6 +16,6 @@ public interface ContentCanonicalizer { * Called to convert the given content to its canonical form. * @param content */ - public ContentHandle canonicalize(ContentHandle content, Map resolvedReferences); + public TypedContent canonicalize(TypedContent content, Map resolvedReferences); } diff --git a/schema-util/common/src/main/java/io/apicurio/registry/content/canon/NoOpContentCanonicalizer.java b/schema-util/common/src/main/java/io/apicurio/registry/content/canon/NoOpContentCanonicalizer.java index e83cade94a..bfeab8fd14 100644 --- a/schema-util/common/src/main/java/io/apicurio/registry/content/canon/NoOpContentCanonicalizer.java +++ b/schema-util/common/src/main/java/io/apicurio/registry/content/canon/NoOpContentCanonicalizer.java @@ -1,6 +1,6 @@ package io.apicurio.registry.content.canon; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import java.util.Map; @@ -10,10 +10,10 @@ public class NoOpContentCanonicalizer implements ContentCanonicalizer { /** - * @see ContentCanonicalizer#canonicalize(io.apicurio.registry.content.ContentHandle, Map) + * @see ContentCanonicalizer#canonicalize(TypedContent, Map) */ @Override - public ContentHandle canonicalize(ContentHandle content, Map resolvedReferences) { + public TypedContent canonicalize(TypedContent content, Map resolvedReferences) { return content; } diff --git a/schema-util/common/src/main/java/io/apicurio/registry/content/dereference/ContentDereferencer.java b/schema-util/common/src/main/java/io/apicurio/registry/content/dereference/ContentDereferencer.java index 1cee92b812..cfdf7a308c 100644 --- a/schema-util/common/src/main/java/io/apicurio/registry/content/dereference/ContentDereferencer.java +++ b/schema-util/common/src/main/java/io/apicurio/registry/content/dereference/ContentDereferencer.java @@ -1,6 +1,6 @@ package io.apicurio.registry.content.dereference; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import java.util.Map; @@ -14,7 +14,7 @@ public interface ContentDereferencer { * Called to dereference the given content to its dereferenced form * @param content */ - ContentHandle dereference(ContentHandle content, Map resolvedReferences); + TypedContent dereference(TypedContent content, Map resolvedReferences); /** * Called to rewrite any references in the content so that they point to valid Registry API URLs rather than @@ -22,7 +22,7 @@ public interface ContentDereferencer { * a value of ./common-types.json#/defs/FooType this method will rewrite that property * to something like https://registry.example.com/apis/registry/v3/groups/Example/artifacts/CommonTypes/versions/1.0. * @param content - * @param resolvedReferences + * @param resolvedReferenceUrls */ - ContentHandle rewriteReferences(ContentHandle content, Map resolvedReferenceUrls); + TypedContent rewriteReferences(TypedContent content, Map resolvedReferenceUrls); } diff --git a/schema-util/common/src/main/java/io/apicurio/registry/content/extract/ContentExtractor.java b/schema-util/common/src/main/java/io/apicurio/registry/content/extract/ContentExtractor.java index b9f499cacd..2fc7c99dd3 100644 --- a/schema-util/common/src/main/java/io/apicurio/registry/content/extract/ContentExtractor.java +++ b/schema-util/common/src/main/java/io/apicurio/registry/content/extract/ContentExtractor.java @@ -1,9 +1,12 @@ package io.apicurio.registry.content.extract; -import static io.apicurio.registry.utils.StringUtil.isEmpty; - import io.apicurio.registry.content.ContentHandle; +import static io.apicurio.registry.utils.StringUtil.isEmpty; + +/** + * @deprecated only used in the v2 REST API - remove when v2 API is removed + */ public interface ContentExtractor { /** * Extract metadata from content. diff --git a/schema-util/common/src/main/java/io/apicurio/registry/content/refs/NoOpReferenceFinder.java b/schema-util/common/src/main/java/io/apicurio/registry/content/refs/NoOpReferenceFinder.java index 5558c4c1ab..4181ab7b1a 100644 --- a/schema-util/common/src/main/java/io/apicurio/registry/content/refs/NoOpReferenceFinder.java +++ b/schema-util/common/src/main/java/io/apicurio/registry/content/refs/NoOpReferenceFinder.java @@ -1,19 +1,19 @@ package io.apicurio.registry.content.refs; +import io.apicurio.registry.content.TypedContent; + import java.util.Collections; import java.util.Set; -import io.apicurio.registry.content.ContentHandle; - public class NoOpReferenceFinder implements ReferenceFinder { public static final ReferenceFinder INSTANCE = new NoOpReferenceFinder(); /** - * @see io.apicurio.registry.content.refs.ReferenceFinder#findExternalReferences(io.apicurio.registry.content.ContentHandle) + * @see io.apicurio.registry.content.refs.ReferenceFinder#findExternalReferences(TypedContent) */ @Override - public Set findExternalReferences(ContentHandle content) { + public Set findExternalReferences(TypedContent content) { return Collections.emptySet(); } diff --git a/schema-util/common/src/main/java/io/apicurio/registry/content/refs/ReferenceFinder.java b/schema-util/common/src/main/java/io/apicurio/registry/content/refs/ReferenceFinder.java index 3c239941f9..162f3ab624 100644 --- a/schema-util/common/src/main/java/io/apicurio/registry/content/refs/ReferenceFinder.java +++ b/schema-util/common/src/main/java/io/apicurio/registry/content/refs/ReferenceFinder.java @@ -1,8 +1,8 @@ package io.apicurio.registry.content.refs; -import java.util.Set; +import io.apicurio.registry.content.TypedContent; -import io.apicurio.registry.content.ContentHandle; +import java.util.Set; public interface ReferenceFinder { @@ -10,6 +10,6 @@ public interface ReferenceFinder { * Finds the set of external references in a piece of content. * @param content */ - public Set findExternalReferences(ContentHandle content); + public Set findExternalReferences(TypedContent content); } diff --git a/app/src/main/java/io/apicurio/registry/util/ContentTypeUtil.java b/schema-util/common/src/main/java/io/apicurio/registry/content/util/ContentTypeUtil.java similarity index 65% rename from app/src/main/java/io/apicurio/registry/util/ContentTypeUtil.java rename to schema-util/common/src/main/java/io/apicurio/registry/content/util/ContentTypeUtil.java index 748add8b18..02e11dc819 100644 --- a/app/src/main/java/io/apicurio/registry/util/ContentTypeUtil.java +++ b/schema-util/common/src/main/java/io/apicurio/registry/content/util/ContentTypeUtil.java @@ -1,10 +1,18 @@ -package io.apicurio.registry.util; +package io.apicurio.registry.content.util; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.types.ContentTypes; +import org.xml.sax.InputSource; +import org.xml.sax.helpers.DefaultHandler; + +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import java.io.IOException; +import java.io.StringReader; public final class ContentTypeUtil { @@ -88,12 +96,27 @@ public static boolean isParsableYaml(ContentHandle yaml) { public static boolean isParsableJson(ContentHandle content) { try { JsonNode root = jsonMapper.readTree(content.stream()); - return root != null && (root.isObject() || root.isArray()); + return root != null && !root.isNull() && !root.isMissingNode(); } catch (Throwable t) { return false; } } + /** + * Returns true if the content can be parsed as xml. + */ + public static boolean isParsableXml(ContentHandle content) { + try { + SAXParserFactory factory = SAXParserFactory.newInstance(); + SAXParser saxParser = factory.newSAXParser(); + saxParser.parse(new InputSource(new StringReader(content.content())), new DefaultHandler()); + // If no exception is thrown, the XML is valid + return true; + } catch (Exception e) { + return false; + } + } + public static ContentHandle yamlToJson(ContentHandle yaml) { try { JsonNode root = yamlMapper.readTree(yaml.stream()); @@ -103,7 +126,33 @@ public static ContentHandle yamlToJson(ContentHandle yaml) { } } - // FIXME this doesn't work for XML types! + public static JsonNode parseJson(ContentHandle content) throws IOException { + JsonNode root = jsonMapper.readTree(content.stream()); + return root; + } + + public static JsonNode parseYaml(ContentHandle content) throws IOException { + JsonNode root = yamlMapper.readTree(content.stream()); + return root; + } + + public static JsonNode parseJsonOrYaml(TypedContent content) throws IOException { + JsonNode node = null; + String contentType = content.getContentType(); + if (contentType.toLowerCase().contains("yaml") || contentType.toLowerCase().contains("yml")) { + node = ContentTypeUtil.parseYaml(content.getContent()); + } else { + node = ContentTypeUtil.parseJson(content.getContent()); + } + + if (!node.isObject()) { + throw new IOException("Input is not a valid document."); + } + + return node; + } + + // FIXME this doesn't work for GraphQL public static String determineContentType(ContentHandle content) { if (isParsableJson(content)) { return CT_APPLICATION_JSON; @@ -111,6 +160,9 @@ public static String determineContentType(ContentHandle content) { if (isParsableYaml(content)) { return CT_APPLICATION_YAML; } + if (isParsableXml(content)) { + return CT_APPLICATION_XML; + } return ContentTypes.APPLICATION_PROTOBUF; } } diff --git a/schema-util/common/src/main/java/io/apicurio/registry/rules/compatibility/AbstractCompatibilityChecker.java b/schema-util/common/src/main/java/io/apicurio/registry/rules/compatibility/AbstractCompatibilityChecker.java index d2be4ec0fe..217bf2521e 100644 --- a/schema-util/common/src/main/java/io/apicurio/registry/rules/compatibility/AbstractCompatibilityChecker.java +++ b/schema-util/common/src/main/java/io/apicurio/registry/rules/compatibility/AbstractCompatibilityChecker.java @@ -1,7 +1,7 @@ package io.apicurio.registry.rules.compatibility; import com.google.common.collect.ImmutableSet; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import java.util.HashSet; import java.util.List; @@ -15,7 +15,8 @@ public abstract class AbstractCompatibilityChecker implements CompatibilityChecker { @Override - public CompatibilityExecutionResult testCompatibility(CompatibilityLevel compatibilityLevel, List existingArtifacts, ContentHandle proposedArtifact, Map resolvedReferences) { + public CompatibilityExecutionResult testCompatibility(CompatibilityLevel compatibilityLevel, List existingArtifacts, + TypedContent proposedArtifact, Map resolvedReferences) { requireNonNull(compatibilityLevel, "compatibilityLevel MUST NOT be null"); requireNonNull(existingArtifacts, "existingSchemas MUST NOT be null"); requireNonNull(proposedArtifact, "proposedSchema MUST NOT be null"); @@ -24,10 +25,10 @@ public CompatibilityExecutionResult testCompatibility(CompatibilityLevel compati return CompatibilityExecutionResult.compatible(); } - final String proposedArtifactContent = proposedArtifact.content(); + final String proposedArtifactContent = proposedArtifact.getContent().content(); Set incompatibleDiffs = new HashSet<>(); - String lastExistingSchema = existingArtifacts.get(existingArtifacts.size() - 1).content(); + String lastExistingSchema = existingArtifacts.get(existingArtifacts.size() - 1).getContent().content(); switch (compatibilityLevel) { case BACKWARD: @@ -71,17 +72,17 @@ public CompatibilityExecutionResult testCompatibility(CompatibilityLevel compati * * @return The collected set of differences. */ - private Set transitively(List existingSchemas, String proposedSchema, + private Set transitively(List existingSchemas, String proposedSchema, BiFunction> checkExistingProposed) { Set result = new HashSet<>(); for (int i = existingSchemas.size() - 1; i >= 0; i--) { // TODO This may become too slow, more wide refactoring needed. - Set current = checkExistingProposed.apply(existingSchemas.get(i).content(), proposedSchema); + Set current = checkExistingProposed.apply(existingSchemas.get(i).getContent().content(), proposedSchema); result.addAll(current); } return result; } - protected abstract Set isBackwardsCompatibleWith(String existing, String proposed, Map resolvedReferences); + protected abstract Set isBackwardsCompatibleWith(String existing, String proposed, Map resolvedReferences); protected abstract CompatibilityDifference transform(D original); } diff --git a/schema-util/common/src/main/java/io/apicurio/registry/rules/compatibility/CompatibilityChecker.java b/schema-util/common/src/main/java/io/apicurio/registry/rules/compatibility/CompatibilityChecker.java index 0935aa9436..caa4387080 100644 --- a/schema-util/common/src/main/java/io/apicurio/registry/rules/compatibility/CompatibilityChecker.java +++ b/schema-util/common/src/main/java/io/apicurio/registry/rules/compatibility/CompatibilityChecker.java @@ -1,10 +1,9 @@ package io.apicurio.registry.rules.compatibility; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; /** * An interface that is used to determine whether a proposed artifact's content is compatible and return a set of @@ -13,14 +12,6 @@ * */ public interface CompatibilityChecker { - /** - * @param compatibilityLevel MUST NOT be null - * @param existingArtifacts MUST NOT be null and MUST NOT contain null elements, - * but may be empty if the rule is executed and the artifact does not exist - * (e.g. a global COMPATIBILITY rule with io.apicurio.registry.rules.RuleApplicationType#CREATE) - * @param proposedArtifact MUST NOT be null - */ - CompatibilityExecutionResult testCompatibility(CompatibilityLevel compatibilityLevel, List existingArtifacts, ContentHandle proposedArtifact, Map resolvedReferences); /** * @param compatibilityLevel MUST NOT be null @@ -29,8 +20,7 @@ public interface CompatibilityChecker { * (e.g. a global COMPATIBILITY rule with io.apicurio.registry.rules.RuleApplicationType#CREATE) * @param proposedArtifact MUST NOT be null */ - default CompatibilityExecutionResult testCompatibility(CompatibilityLevel compatibilityLevel, List existingArtifacts, String proposedArtifact, Map resolvedReferences) { - final List contentHandles = existingArtifacts.stream().map(ContentHandle::create).collect(Collectors.toList()); - return testCompatibility(compatibilityLevel, contentHandles, ContentHandle.create(proposedArtifact), resolvedReferences); - } + CompatibilityExecutionResult testCompatibility(CompatibilityLevel compatibilityLevel, List existingArtifacts, + TypedContent proposedArtifact, Map resolvedReferences); + } \ No newline at end of file diff --git a/schema-util/common/src/main/java/io/apicurio/registry/rules/compatibility/NoopCompatibilityChecker.java b/schema-util/common/src/main/java/io/apicurio/registry/rules/compatibility/NoopCompatibilityChecker.java index 9f40d198a0..07cefcb897 100644 --- a/schema-util/common/src/main/java/io/apicurio/registry/rules/compatibility/NoopCompatibilityChecker.java +++ b/schema-util/common/src/main/java/io/apicurio/registry/rules/compatibility/NoopCompatibilityChecker.java @@ -1,6 +1,6 @@ package io.apicurio.registry.rules.compatibility; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import java.util.List; import java.util.Map; @@ -11,10 +11,11 @@ public class NoopCompatibilityChecker implements CompatibilityChecker { public static CompatibilityChecker INSTANCE = new NoopCompatibilityChecker(); /** - * @see CompatibilityChecker#testCompatibility(io.apicurio.registry.rules.compatibility.CompatibilityLevel, java.util.List, ContentHandle, java.util.Map) + * @see CompatibilityChecker#testCompatibility(CompatibilityLevel, List, TypedContent, Map) */ @Override - public CompatibilityExecutionResult testCompatibility(CompatibilityLevel compatibilityLevel, List existingArtifacts, ContentHandle proposedArtifact, Map resolvedReferences) { + public CompatibilityExecutionResult testCompatibility(CompatibilityLevel compatibilityLevel, List existingArtifacts, + TypedContent proposedArtifact, Map resolvedReferences) { requireNonNull(compatibilityLevel, "compatibilityLevel MUST NOT be null"); requireNonNull(existingArtifacts, "existingSchemas MUST NOT be null"); requireNonNull(proposedArtifact, "proposedSchema MUST NOT be null"); diff --git a/schema-util/common/src/main/java/io/apicurio/registry/rules/validity/ContentValidator.java b/schema-util/common/src/main/java/io/apicurio/registry/rules/validity/ContentValidator.java index d21466a9a9..0c58d3f682 100644 --- a/schema-util/common/src/main/java/io/apicurio/registry/rules/validity/ContentValidator.java +++ b/schema-util/common/src/main/java/io/apicurio/registry/rules/validity/ContentValidator.java @@ -1,39 +1,29 @@ package io.apicurio.registry.rules.validity; -import java.util.List; -import java.util.Map; - -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rest.v3.beans.ArtifactReference; import io.apicurio.registry.rules.RuleViolationException; +import java.util.List; +import java.util.Map; + /** * Validates content. Syntax and semantic validations are possible based on configuration. An * implementation of this interface should exist for each content-type supported by the registry. * * Also provides validation of references. - * */ public interface ContentValidator { /** * Called to validate the given content. - * - * @param level the level - * @param artifactContent the content - * @param resolvedReferences a map containing the resolved references - * @throws RuleViolationException for any invalid content */ - public void validate(ValidityLevel level, ContentHandle artifactContent, Map resolvedReferences) throws RuleViolationException; + public void validate(ValidityLevel level, TypedContent content, Map resolvedReferences) throws RuleViolationException; /** * Ensures that all references in the content are represented in the list of passed references. This is used * to ensure that the content does not have any references that are unmapped. - * - * @param artifactContent - * @param references - * @throws RuleViolationException */ - public void validateReferences(ContentHandle artifactContent, List references) throws RuleViolationException; + public void validateReferences(TypedContent content, List references) throws RuleViolationException; } diff --git a/schema-util/common/src/test/java/io/apicurio/registry/rules/compatibility/CompatibilityTestExecutor.java b/schema-util/common/src/test/java/io/apicurio/registry/rules/compatibility/CompatibilityTestExecutor.java index 0db06b05d6..0629d80c0e 100644 --- a/schema-util/common/src/test/java/io/apicurio/registry/rules/compatibility/CompatibilityTestExecutor.java +++ b/schema-util/common/src/test/java/io/apicurio/registry/rules/compatibility/CompatibilityTestExecutor.java @@ -2,6 +2,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsonorg.JsonOrgModule; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.types.ContentTypes; import org.json.JSONArray; import org.json.JSONObject; import org.slf4j.Logger; @@ -52,12 +54,16 @@ public Set execute(String testData) throws Exception { log.info("Running test case: {}", caseId); var original = testCaseData.get("original").toString(); + var originalCT = testCaseData.has("originalContentType") ? testCaseData.get("originalContentType").toString() : ContentTypes.APPLICATION_JSON; + var originalTypedContent = TypedContent.create(original, originalCT); var updated = testCaseData.get("updated").toString(); + var updatedCT = testCaseData.has("updatedContentType") ? testCaseData.get("updatedContentType").toString() : ContentTypes.APPLICATION_JSON; + var updatedTypedContent = TypedContent.create(updated, updatedCT); - var resultBackward = checker.testCompatibility(CompatibilityLevel.BACKWARD, List.of(original), - updated, Collections.emptyMap()); - var resultForward = checker.testCompatibility(CompatibilityLevel.FORWARD, List.of(original), - updated, Collections.emptyMap()); + var resultBackward = checker.testCompatibility(CompatibilityLevel.BACKWARD, List.of(originalTypedContent), + updatedTypedContent, Collections.emptyMap()); + var resultForward = checker.testCompatibility(CompatibilityLevel.FORWARD, List.of(originalTypedContent), + updatedTypedContent, Collections.emptyMap()); switch (testCaseData.getString("compatibility")) { case "backward": diff --git a/schema-util/graphql/src/main/java/io/apicurio/registry/content/canon/GraphQLContentCanonicalizer.java b/schema-util/graphql/src/main/java/io/apicurio/registry/content/canon/GraphQLContentCanonicalizer.java index 9131a4d038..3d8fc3c90b 100644 --- a/schema-util/graphql/src/main/java/io/apicurio/registry/content/canon/GraphQLContentCanonicalizer.java +++ b/schema-util/graphql/src/main/java/io/apicurio/registry/content/canon/GraphQLContentCanonicalizer.java @@ -4,9 +4,11 @@ import graphql.schema.idl.SchemaGenerator; import graphql.schema.idl.SchemaParser; import graphql.schema.idl.SchemaPrinter; -import graphql.schema.idl.TypeDefinitionRegistry; import graphql.schema.idl.SchemaPrinter.Options; +import graphql.schema.idl.TypeDefinitionRegistry; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.types.ContentTypes; import java.util.Map; @@ -21,14 +23,14 @@ public class GraphQLContentCanonicalizer implements ContentCanonicalizer { private static final SchemaPrinter printer = new SchemaPrinter(Options.defaultOptions().includeDirectives(false)); /** - * @see ContentCanonicalizer#canonicalize(io.apicurio.registry.content.ContentHandle, Map) + * @see ContentCanonicalizer#canonicalize(TypedContent, Map) */ @Override - public ContentHandle canonicalize(ContentHandle content, Map resolvedReferences) { + public TypedContent canonicalize(TypedContent content, Map resolvedReferences) { try { - TypeDefinitionRegistry typeRegistry = sparser.parse(content.content()); + TypeDefinitionRegistry typeRegistry = sparser.parse(content.getContent().content()); String canonicalized = printer.print(schemaGenerator.makeExecutableSchema(typeRegistry, wiring)); - return ContentHandle.create(canonicalized); + return TypedContent.create(ContentHandle.create(canonicalized), ContentTypes.APPLICATION_GRAPHQL); } catch (Exception e) { // Must not be a GraphQL file } diff --git a/schema-util/graphql/src/main/java/io/apicurio/registry/rules/validity/GraphQLContentValidator.java b/schema-util/graphql/src/main/java/io/apicurio/registry/rules/validity/GraphQLContentValidator.java index a729335fe1..2e041d793d 100644 --- a/schema-util/graphql/src/main/java/io/apicurio/registry/rules/validity/GraphQLContentValidator.java +++ b/schema-util/graphql/src/main/java/io/apicurio/registry/rules/validity/GraphQLContentValidator.java @@ -1,7 +1,7 @@ package io.apicurio.registry.rules.validity; import graphql.schema.idl.SchemaParser; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rest.v3.beans.ArtifactReference; import io.apicurio.registry.rules.RuleViolationException; import io.apicurio.registry.types.RuleType; @@ -21,13 +21,13 @@ public GraphQLContentValidator() { } /** - * @see io.apicurio.registry.rules.validity.ContentValidator#validate(ValidityLevel, ContentHandle, java.util.Map) + * @see io.apicurio.registry.rules.validity.ContentValidator#validate(ValidityLevel, TypedContent, Map) */ @Override - public void validate(ValidityLevel level, ContentHandle content, Map resolvedReferences) throws RuleViolationException { + public void validate(ValidityLevel level, TypedContent content, Map resolvedReferences) throws RuleViolationException { if (level == ValidityLevel.SYNTAX_ONLY || level == ValidityLevel.FULL) { try { - new SchemaParser().parse(content.content()); + new SchemaParser().parse(content.getContent().content()); } catch (Exception e) { e.printStackTrace(); throw new RuleViolationException("Syntax violation for GraphQL artifact.", RuleType.VALIDITY, level.name(), e); @@ -36,10 +36,10 @@ public void validate(ValidityLevel level, ContentHandle content, Map references) throws RuleViolationException { + public void validateReferences(TypedContent content, List references) throws RuleViolationException { // Note: not yet implemented! } diff --git a/schema-util/json/src/main/java/io/apicurio/registry/content/canon/JsonContentCanonicalizer.java b/schema-util/json/src/main/java/io/apicurio/registry/content/canon/JsonContentCanonicalizer.java index f8abd4fbef..8b7b8aa56a 100644 --- a/schema-util/json/src/main/java/io/apicurio/registry/content/canon/JsonContentCanonicalizer.java +++ b/schema-util/json/src/main/java/io/apicurio/registry/content/canon/JsonContentCanonicalizer.java @@ -1,13 +1,14 @@ package io.apicurio.registry.content.canon; -import java.io.IOException; -import java.util.Map; - import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; - import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.types.ContentTypes; + +import java.io.IOException; +import java.util.Map; /** * A common JSON content canonicalizer. This will remove any extra formatting such as whitespace @@ -20,15 +21,15 @@ public class JsonContentCanonicalizer implements ContentCanonicalizer { private final ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS); /** - * @see ContentCanonicalizer#canonicalize(io.apicurio.registry.content.ContentHandle, Map) + * @see ContentCanonicalizer#canonicalize(TypedContent, Map) */ @Override - public ContentHandle canonicalize(ContentHandle content, Map resolvedReferences) { + public TypedContent canonicalize(TypedContent content, Map resolvedReferences) { try { JsonNode root = readAsJsonNode(content); processJsonNode(root); String converted = mapper.writeValueAsString(mapper.treeToValue(root, Object.class)); - return ContentHandle.create(converted); + return TypedContent.create(ContentHandle.create(converted), ContentTypes.APPLICATION_JSON); } catch (Throwable t) { return content; } @@ -47,8 +48,8 @@ protected void processJsonNode(JsonNode node) { * @return * @throws IOException */ - private JsonNode readAsJsonNode(ContentHandle content) throws IOException { - return mapper.readTree(content.content()); + private JsonNode readAsJsonNode(TypedContent content) throws IOException { + return mapper.readTree(content.getContent().content()); } } diff --git a/schema-util/json/src/main/java/io/apicurio/registry/content/dereference/JsonSchemaDereferencer.java b/schema-util/json/src/main/java/io/apicurio/registry/content/dereference/JsonSchemaDereferencer.java index 111da639c1..370dfd2bbb 100644 --- a/schema-util/json/src/main/java/io/apicurio/registry/content/dereference/JsonSchemaDereferencer.java +++ b/schema-util/json/src/main/java/io/apicurio/registry/content/dereference/JsonSchemaDereferencer.java @@ -10,6 +10,8 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.types.ContentTypes; import java.util.Iterator; import java.util.Map; @@ -32,20 +34,20 @@ public class JsonSchemaDereferencer implements ContentDereferencer { } @Override - public ContentHandle dereference(ContentHandle content, Map resolvedReferences) { + public TypedContent dereference(TypedContent content, Map resolvedReferences) { throw new DereferencingNotSupportedException("Content dereferencing is not supported for JSON Schema"); } /** - * @see io.apicurio.registry.content.dereference.ContentDereferencer#rewriteReferences(io.apicurio.registry.content.ContentHandle, java.util.Map) + * @see io.apicurio.registry.content.dereference.ContentDereferencer#rewriteReferences(io.apicurio.registry.content.TypedContent, java.util.Map) */ @Override - public ContentHandle rewriteReferences(ContentHandle content, Map resolvedReferenceUrls) { + public TypedContent rewriteReferences(TypedContent content, Map resolvedReferenceUrls) { try { - JsonNode tree = objectMapper.readTree(content.content()); + JsonNode tree = objectMapper.readTree(content.getContent().content()); rewriteIn(tree, resolvedReferenceUrls); String converted = objectMapper.writeValueAsString(objectMapper.treeToValue(tree, Object.class)); - return ContentHandle.create(converted); + return TypedContent.create(ContentHandle.create(converted), ContentTypes.APPLICATION_JSON); } catch (Exception e) { return content; } diff --git a/schema-util/json/src/main/java/io/apicurio/registry/content/refs/JsonSchemaReferenceFinder.java b/schema-util/json/src/main/java/io/apicurio/registry/content/refs/JsonSchemaReferenceFinder.java index 468fde1e0a..ac31f2410d 100644 --- a/schema-util/json/src/main/java/io/apicurio/registry/content/refs/JsonSchemaReferenceFinder.java +++ b/schema-util/json/src/main/java/io/apicurio/registry/content/refs/JsonSchemaReferenceFinder.java @@ -1,5 +1,11 @@ package io.apicurio.registry.content.refs; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.apicurio.registry.content.TypedContent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.util.Collections; import java.util.HashSet; import java.util.Iterator; @@ -7,14 +13,6 @@ import java.util.Set; import java.util.stream.Collectors; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -import io.apicurio.registry.content.ContentHandle; - /** * A JSON Schema implementation of a reference finder. */ @@ -24,12 +22,12 @@ public class JsonSchemaReferenceFinder implements ReferenceFinder { private static final Logger log = LoggerFactory.getLogger(JsonSchemaReferenceFinder.class); /** - * @see io.apicurio.registry.content.refs.ReferenceFinder#findExternalReferences(io.apicurio.registry.content.ContentHandle) + * @see io.apicurio.registry.content.refs.ReferenceFinder#findExternalReferences(TypedContent) */ @Override - public Set findExternalReferences(ContentHandle content) { + public Set findExternalReferences(TypedContent content) { try { - JsonNode tree = mapper.readTree(content.content()); + JsonNode tree = mapper.readTree(content.getContent().content()); Set externalTypes = new HashSet<>(); findExternalTypesIn(tree, externalTypes); diff --git a/schema-util/json/src/main/java/io/apicurio/registry/rules/compatibility/JsonSchemaCompatibilityChecker.java b/schema-util/json/src/main/java/io/apicurio/registry/rules/compatibility/JsonSchemaCompatibilityChecker.java index 804ecdb565..f6f12a0ef3 100644 --- a/schema-util/json/src/main/java/io/apicurio/registry/rules/compatibility/JsonSchemaCompatibilityChecker.java +++ b/schema-util/json/src/main/java/io/apicurio/registry/rules/compatibility/JsonSchemaCompatibilityChecker.java @@ -1,6 +1,6 @@ package io.apicurio.registry.rules.compatibility; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rules.compatibility.jsonschema.JsonSchemaDiffLibrary; import io.apicurio.registry.rules.compatibility.jsonschema.diff.Difference; @@ -10,7 +10,7 @@ public class JsonSchemaCompatibilityChecker extends AbstractCompatibilityChecker { @Override - protected Set isBackwardsCompatibleWith(String existing, String proposed, Map resolvedReferences) { + protected Set isBackwardsCompatibleWith(String existing, String proposed, Map resolvedReferences) { return JsonSchemaDiffLibrary.getIncompatibleDifferences(existing, proposed, resolvedReferences); } diff --git a/schema-util/json/src/main/java/io/apicurio/registry/rules/compatibility/jsonschema/JsonSchemaDiffLibrary.java b/schema-util/json/src/main/java/io/apicurio/registry/rules/compatibility/jsonschema/JsonSchemaDiffLibrary.java index 7a7c28bdd2..abc6933396 100644 --- a/schema-util/json/src/main/java/io/apicurio/registry/rules/compatibility/jsonschema/JsonSchemaDiffLibrary.java +++ b/schema-util/json/src/main/java/io/apicurio/registry/rules/compatibility/jsonschema/JsonSchemaDiffLibrary.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rules.compatibility.jsonschema.diff.DiffContext; import io.apicurio.registry.rules.compatibility.jsonschema.diff.Difference; import io.apicurio.registry.rules.compatibility.jsonschema.diff.SchemaDiffVisitor; @@ -32,7 +32,7 @@ public class JsonSchemaDiffLibrary { * @return an object to access the found differences: Original -> Updated * @throws IllegalArgumentException if the input is not a valid representation of a JsonSchema */ - public static DiffContext findDifferences(String original, String updated, Map resolvedReferences) { + public static DiffContext findDifferences(String original, String updated, Map resolvedReferences) { try { JsonNode originalNode = MAPPER.readTree(original); JsonNode updatedNode = MAPPER.readTree(updated); @@ -61,7 +61,7 @@ public static DiffContext findDifferences(String original, String updated, Map resolvedReferences, SchemaLoader.SchemaLoaderBuilder schemaLoaderBuilder) { + private static void loadReferences(JsonNode jsonNode, Map resolvedReferences, SchemaLoader.SchemaLoaderBuilder schemaLoaderBuilder) { SpecificationVersion spec = SpecificationVersion.DRAFT_7; if (jsonNode.has(SCHEMA_KEYWORD)) { String schema = jsonNode.get(SCHEMA_KEYWORD).asText(); @@ -78,9 +78,9 @@ private static void loadReferences(JsonNode jsonNode, Map } } - for (Map.Entry stringStringEntry : resolvedReferences.entrySet()) { + for (Map.Entry stringStringEntry : resolvedReferences.entrySet()) { URI child = ReferenceResolver.resolve(idUri, stringStringEntry.getKey()); - schemaLoaderBuilder.registerSchemaByURI(child, new JSONObject(stringStringEntry.getValue().content())); + schemaLoaderBuilder.registerSchemaByURI(child, new JSONObject(stringStringEntry.getValue().getContent().content())); } } @@ -90,11 +90,11 @@ public static DiffContext findDifferences(Schema originalSchema, Schema updatedS return rootContext; } - public static boolean isCompatible(String original, String updated, Map resolvedReferences) { + public static boolean isCompatible(String original, String updated, Map resolvedReferences) { return findDifferences(original, updated, resolvedReferences).foundAllDifferencesAreCompatible(); } - public static Set getIncompatibleDifferences(String original, String updated, Map resolvedReferences) { + public static Set getIncompatibleDifferences(String original, String updated, Map resolvedReferences) { return findDifferences(original, updated, resolvedReferences).getIncompatibleDifferences(); } } \ No newline at end of file diff --git a/schema-util/json/src/main/java/io/apicurio/registry/rules/compatibility/jsonschema/JsonUtil.java b/schema-util/json/src/main/java/io/apicurio/registry/rules/compatibility/jsonschema/JsonUtil.java index 6fdc11ba9a..fbaa46bee4 100644 --- a/schema-util/json/src/main/java/io/apicurio/registry/rules/compatibility/jsonschema/JsonUtil.java +++ b/schema-util/json/src/main/java/io/apicurio/registry/rules/compatibility/jsonschema/JsonUtil.java @@ -10,7 +10,7 @@ import com.fasterxml.jackson.datatype.jsonorg.JsonOrgModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.types.RegistryException; import org.everit.json.schema.Schema; import org.everit.json.schema.loader.SchemaLoader; @@ -48,11 +48,11 @@ public static Schema readSchema(String content) throws JsonProcessingException { return readSchema(content, Collections.emptyMap(), true); } - public static Schema readSchema(String content, Map resolvedReferences) throws JsonProcessingException { + public static Schema readSchema(String content, Map resolvedReferences) throws JsonProcessingException { return readSchema(content, resolvedReferences, true); } - public static Schema readSchema(String content, Map resolvedReferences, boolean validateDangling) throws JsonProcessingException { + public static Schema readSchema(String content, Map resolvedReferences, boolean validateDangling) throws JsonProcessingException { JsonNode jsonNode = MAPPER.readTree(content); Schema schemaObj; // Extract the $schema to use for determining the id keyword @@ -90,7 +90,7 @@ public static Schema readSchema(String content, Map resol referenceURIs.add(referenceURI); var resolvedReference = resolvedReferencesCopy.remove(referenceURI.toString()); if (resolvedReference != null) { - builder.registerSchemaByURI(referenceURI, new JSONObject(resolvedReference.content())); + builder.registerSchemaByURI(referenceURI, new JSONObject(resolvedReference.getContent().content())); } else { /* Since we do not have the referenced content, * we insert a placeholder schema, that will accept any JSON, diff --git a/schema-util/json/src/main/java/io/apicurio/registry/rules/validity/JsonSchemaContentValidator.java b/schema-util/json/src/main/java/io/apicurio/registry/rules/validity/JsonSchemaContentValidator.java index d61057680b..e7c43866b3 100644 --- a/schema-util/json/src/main/java/io/apicurio/registry/rules/validity/JsonSchemaContentValidator.java +++ b/schema-util/json/src/main/java/io/apicurio/registry/rules/validity/JsonSchemaContentValidator.java @@ -1,19 +1,17 @@ package io.apicurio.registry.rules.validity; -import java.util.Collections; -import java.util.List; - -import org.everit.json.schema.SchemaException; - import com.fasterxml.jackson.databind.ObjectMapper; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rest.v3.beans.ArtifactReference; import io.apicurio.registry.rules.RuleViolation; import io.apicurio.registry.rules.RuleViolationException; import io.apicurio.registry.rules.compatibility.jsonschema.JsonUtil; import io.apicurio.registry.types.RuleType; +import org.everit.json.schema.SchemaException; +import java.util.Collections; +import java.util.List; import java.util.Map; /** @@ -30,19 +28,19 @@ public JsonSchemaContentValidator() { } /** - * @see io.apicurio.registry.rules.validity.ContentValidator#validate(ValidityLevel, ContentHandle, Map) + * @see io.apicurio.registry.rules.validity.ContentValidator#validate(ValidityLevel, TypedContent, Map) */ @Override - public void validate(ValidityLevel level, ContentHandle artifactContent, Map resolvedReferences) throws RuleViolationException { + public void validate(ValidityLevel level, TypedContent content, Map resolvedReferences) throws RuleViolationException { if (level == ValidityLevel.SYNTAX_ONLY) { try { - objectMapper.readTree(artifactContent.bytes()); + objectMapper.readTree(content.getContent().bytes()); } catch (Exception e) { throw new RuleViolationException("Syntax violation for JSON Schema artifact.", RuleType.VALIDITY, level.name(), e); } } else if (level == ValidityLevel.FULL) { try { - JsonUtil.readSchema(artifactContent.content(), resolvedReferences); + JsonUtil.readSchema(content.getContent().content(), resolvedReferences); } catch (SchemaException e) { String context = e.getSchemaLocation(); String description = e.getMessage(); @@ -61,10 +59,10 @@ public void validate(ValidityLevel level, ContentHandle artifactContent, Map references) throws RuleViolationException { + public void validateReferences(TypedContent content, List references) throws RuleViolationException { // TODO Implement this for JSON Schema! } } diff --git a/schema-util/json/src/test/java/io/apicurio/registry/rules/compatibility/jsonschema/JsonSchemaCompatibilityCheckerTest.java b/schema-util/json/src/test/java/io/apicurio/registry/rules/compatibility/jsonschema/JsonSchemaCompatibilityCheckerTest.java index d2b8dad54d..30ad940858 100644 --- a/schema-util/json/src/test/java/io/apicurio/registry/rules/compatibility/jsonschema/JsonSchemaCompatibilityCheckerTest.java +++ b/schema-util/json/src/test/java/io/apicurio/registry/rules/compatibility/jsonschema/JsonSchemaCompatibilityCheckerTest.java @@ -1,15 +1,20 @@ package io.apicurio.registry.rules.compatibility.jsonschema; -import java.util.Collections; - -import org.junit.jupiter.api.Test; - import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rules.compatibility.CompatibilityLevel; import io.apicurio.registry.rules.compatibility.JsonSchemaCompatibilityChecker; +import io.apicurio.registry.types.ContentTypes; +import org.junit.jupiter.api.Test; + +import java.util.Collections; public class JsonSchemaCompatibilityCheckerTest { - + + private TypedContent toTypedContent(String content) { + return TypedContent.create(ContentHandle.create(content), ContentTypes.APPLICATION_JSON); + } + private static final String BEFORE = "{\r\n" + " \"$id\": \"https://example.com/blank.schema.json\",\r\n" + " \"$schema\": \"http://json-schema.org/draft-07/schema#\",\r\n" @@ -53,16 +58,16 @@ public class JsonSchemaCompatibilityCheckerTest { @Test public void testJsonSchemaCompatibilityChecker() { JsonSchemaCompatibilityChecker checker = new JsonSchemaCompatibilityChecker(); - ContentHandle existing = ContentHandle.create(BEFORE); - ContentHandle proposed = ContentHandle.create(AFTER_VALID); + TypedContent existing = toTypedContent(BEFORE); + TypedContent proposed = toTypedContent(AFTER_VALID); checker.testCompatibility(CompatibilityLevel.BACKWARD, Collections.singletonList(existing), proposed, Collections.emptyMap()); } @Test public void testJsonSchemaCompatibilityChecker_Fail() { JsonSchemaCompatibilityChecker checker = new JsonSchemaCompatibilityChecker(); - ContentHandle existing = ContentHandle.create(BEFORE); - ContentHandle proposed = ContentHandle.create(AFTER_INVALID); + TypedContent existing = toTypedContent(BEFORE); + TypedContent proposed = toTypedContent(AFTER_INVALID); checker.testCompatibility(CompatibilityLevel.BACKWARD, Collections.singletonList(existing), proposed, Collections.emptyMap()); } diff --git a/schema-util/kconnect/src/main/java/io/apicurio/registry/rules/validity/KafkaConnectContentValidator.java b/schema-util/kconnect/src/main/java/io/apicurio/registry/rules/validity/KafkaConnectContentValidator.java index e371088aa9..c7f188ffcf 100644 --- a/schema-util/kconnect/src/main/java/io/apicurio/registry/rules/validity/KafkaConnectContentValidator.java +++ b/schema-util/kconnect/src/main/java/io/apicurio/registry/rules/validity/KafkaConnectContentValidator.java @@ -1,19 +1,17 @@ package io.apicurio.registry.rules.validity; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.apache.kafka.connect.json.JsonConverter; -import org.apache.kafka.connect.json.JsonConverterConfig; - import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; - -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rest.v3.beans.ArtifactReference; import io.apicurio.registry.rules.RuleViolationException; import io.apicurio.registry.types.RuleType; +import org.apache.kafka.connect.json.JsonConverter; +import org.apache.kafka.connect.json.JsonConverterConfig; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * A content validator implementation for the Kafka Connect schema content type. @@ -38,13 +36,13 @@ public KafkaConnectContentValidator() { } /** - * @see io.apicurio.registry.rules.validity.ContentValidator#validate(ValidityLevel, ContentHandle, Map) + * @see io.apicurio.registry.rules.validity.ContentValidator#validate(ValidityLevel, TypedContent, Map) */ @Override - public void validate(ValidityLevel level, ContentHandle artifactContent, Map resolvedReferences) throws RuleViolationException { + public void validate(ValidityLevel level, TypedContent content, Map resolvedReferences) throws RuleViolationException { if (level == ValidityLevel.SYNTAX_ONLY || level == ValidityLevel.FULL) { try { - JsonNode jsonNode = mapper.readTree(artifactContent.content()); + JsonNode jsonNode = mapper.readTree(content.getContent().content()); jsonConverter.asConnectSchema(jsonNode); } catch (Exception e) { throw new RuleViolationException("Syntax violation for Kafka Connect Schema artifact.", RuleType.VALIDITY, level.name(), e); @@ -53,10 +51,10 @@ public void validate(ValidityLevel level, ContentHandle artifactContent, Map references) throws RuleViolationException { + public void validateReferences(TypedContent content, List references) throws RuleViolationException { // Note: not yet implemented! } diff --git a/schema-util/openapi/src/main/java/io/apicurio/registry/content/canon/OpenApiContentCanonicalizer.java b/schema-util/openapi/src/main/java/io/apicurio/registry/content/canon/OpenApiContentCanonicalizer.java new file mode 100644 index 0000000000..d365945492 --- /dev/null +++ b/schema-util/openapi/src/main/java/io/apicurio/registry/content/canon/OpenApiContentCanonicalizer.java @@ -0,0 +1,36 @@ +package io.apicurio.registry.content.canon; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.content.util.ContentTypeUtil; +import io.apicurio.registry.types.ContentTypes; + +import java.util.Map; + +/** + * An OpenAPI content canonicalizer. This will remove any extra formatting such as whitespace + * and also sort all fields/properties for all objects (because ordering of properties does not + * matter). + */ +public class OpenApiContentCanonicalizer implements ContentCanonicalizer { + + private final ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS); + + /** + * @see ContentCanonicalizer#canonicalize(TypedContent, Map) + */ + @Override + public TypedContent canonicalize(TypedContent content, Map resolvedReferences) { + try { + JsonNode root = ContentTypeUtil.parseJsonOrYaml(content); + String converted = mapper.writeValueAsString(mapper.treeToValue(root, Object.class)); + return TypedContent.create(ContentHandle.create(converted), ContentTypes.APPLICATION_JSON); + } catch (Throwable t) { + return content; + } + } + +} diff --git a/schema-util/openapi/src/main/java/io/apicurio/registry/content/dereference/ApicurioDataModelsContentDereferencer.java b/schema-util/openapi/src/main/java/io/apicurio/registry/content/dereference/ApicurioDataModelsContentDereferencer.java index 533d1b13b5..a07a3f0cad 100644 --- a/schema-util/openapi/src/main/java/io/apicurio/registry/content/dereference/ApicurioDataModelsContentDereferencer.java +++ b/schema-util/openapi/src/main/java/io/apicurio/registry/content/dereference/ApicurioDataModelsContentDereferencer.java @@ -1,32 +1,48 @@ package io.apicurio.registry.content.dereference; -import java.util.Map; - +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import io.apicurio.datamodels.Library; import io.apicurio.datamodels.TraverserDirection; import io.apicurio.datamodels.models.Document; import io.apicurio.datamodels.refs.IReferenceResolver; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.content.util.ContentTypeUtil; +import io.apicurio.registry.types.ContentTypes; + +import java.io.IOException; +import java.util.Map; public class ApicurioDataModelsContentDereferencer implements ContentDereferencer { @Override - public ContentHandle dereference(ContentHandle content, Map resolvedReferences) { - Document document = Library.readDocumentFromJSONString(content.content()); - IReferenceResolver resolver = new RegistryReferenceResolver(resolvedReferences); - Document dereferencedDoc = Library.dereferenceDocument(document, resolver, false); - String dereferencedContentStr = Library.writeDocumentToJSONString(dereferencedDoc); - return ContentHandle.create(dereferencedContentStr); + public TypedContent dereference(TypedContent content, Map resolvedReferences) { + try { + JsonNode node = ContentTypeUtil.parseJsonOrYaml(content); + Document document = Library.readDocument((ObjectNode) node); + IReferenceResolver resolver = new RegistryReferenceResolver(resolvedReferences); + Document dereferencedDoc = Library.dereferenceDocument(document, resolver, false); + String dereferencedContentStr = Library.writeDocumentToJSONString(dereferencedDoc); + return TypedContent.create(ContentHandle.create(dereferencedContentStr), ContentTypes.APPLICATION_JSON); + } catch (IOException e) { + throw new RuntimeException(e); + } } /** - * @see io.apicurio.registry.content.dereference.ContentDereferencer#rewriteReferences(io.apicurio.registry.content.ContentHandle, java.util.Map) + * @see io.apicurio.registry.content.dereference.ContentDereferencer#rewriteReferences(io.apicurio.registry.content.TypedContent, java.util.Map) */ @Override - public ContentHandle rewriteReferences(ContentHandle content, Map resolvedReferenceUrls) { - Document doc = Library.readDocumentFromJSONString(content.content()); - ReferenceRewriter visitor = new ReferenceRewriter(resolvedReferenceUrls); - Library.visitTree(doc, visitor, TraverserDirection.down); - return ContentHandle.create(Library.writeDocumentToJSONString(doc)); + public TypedContent rewriteReferences(TypedContent content, Map resolvedReferenceUrls) { + try { + JsonNode node = ContentTypeUtil.parseJsonOrYaml(content); + Document doc = Library.readDocument((ObjectNode) node); + ReferenceRewriter visitor = new ReferenceRewriter(resolvedReferenceUrls); + Library.visitTree(doc, visitor, TraverserDirection.down); + return TypedContent.create(ContentHandle.create(Library.writeDocumentToJSONString(doc)), ContentTypes.APPLICATION_JSON); + } catch (IOException e) { + throw new RuntimeException(e); + } } } diff --git a/schema-util/openapi/src/main/java/io/apicurio/registry/content/dereference/ReferenceRewriter.java b/schema-util/openapi/src/main/java/io/apicurio/registry/content/dereference/ReferenceRewriter.java index b3d0b9afd7..368eb28e79 100644 --- a/schema-util/openapi/src/main/java/io/apicurio/registry/content/dereference/ReferenceRewriter.java +++ b/schema-util/openapi/src/main/java/io/apicurio/registry/content/dereference/ReferenceRewriter.java @@ -1,15 +1,14 @@ package io.apicurio.registry.content.dereference; -import java.util.Map; - import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; - import io.apicurio.datamodels.models.Node; import io.apicurio.datamodels.models.Referenceable; import io.apicurio.datamodels.models.asyncapi.AsyncApiMessage; import io.apicurio.datamodels.models.visitors.AllNodeVisitor; +import java.util.Map; + /** * Rewrites all references in a data model using a map of replacements provided. */ @@ -19,7 +18,6 @@ public class ReferenceRewriter extends AllNodeVisitor { /** * Constructor. - * @param resolvedReferenceUrls */ public ReferenceRewriter(Map referenceUrls) { this.referenceUrls = referenceUrls; diff --git a/schema-util/openapi/src/main/java/io/apicurio/registry/content/dereference/RegistryReferenceResolver.java b/schema-util/openapi/src/main/java/io/apicurio/registry/content/dereference/RegistryReferenceResolver.java index 4623fda3d9..b6c6a80787 100644 --- a/schema-util/openapi/src/main/java/io/apicurio/registry/content/dereference/RegistryReferenceResolver.java +++ b/schema-util/openapi/src/main/java/io/apicurio/registry/content/dereference/RegistryReferenceResolver.java @@ -1,23 +1,27 @@ package io.apicurio.registry.content.dereference; -import java.util.Map; - +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import io.apicurio.datamodels.Library; import io.apicurio.datamodels.models.Document; import io.apicurio.datamodels.models.Node; import io.apicurio.datamodels.refs.LocalReferenceResolver; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.content.refs.JsonPointerExternalReference; +import io.apicurio.registry.content.util.ContentTypeUtil; + +import java.io.IOException; +import java.util.Map; public class RegistryReferenceResolver extends LocalReferenceResolver { - private final Map resolvedReferences; + private final Map resolvedReferences; /** * Constructor. * @param resolvedReferences */ - public RegistryReferenceResolver(Map resolvedReferences) { + public RegistryReferenceResolver(Map resolvedReferences) { this.resolvedReferences = resolvedReferences; } @@ -26,19 +30,23 @@ public RegistryReferenceResolver(Map resolvedReferences) */ @Override public Node resolveRef(String reference, Node from) { - if (resolvedReferences.containsKey(reference)) { - ContentHandle resolvedRefContent = resolvedReferences.get(reference); - Document resolvedRefDoc = Library.readDocumentFromJSONString(resolvedRefContent.content()); - JsonPointerExternalReference ref = new JsonPointerExternalReference(reference); - return super.resolveRef(ref.getComponent(), resolvedRefDoc); - // TODO if we find a Node, make sure to modify it by updating all of its $ref values to point to appropriate locations + try { + if (resolvedReferences.containsKey(reference)) { + TypedContent resolvedRefContent = resolvedReferences.get(reference); + JsonNode node = ContentTypeUtil.parseJsonOrYaml(resolvedRefContent); + Document resolvedRefDoc = Library.readDocument((ObjectNode) node); + JsonPointerExternalReference ref = new JsonPointerExternalReference(reference); + return super.resolveRef(ref.getComponent(), resolvedRefDoc); + // TODO if we find a Node, make sure to modify it by updating all of its $ref values to point to appropriate locations + } + + // TODO handle recursive $ref values (refs from refs) + + // Cannot resolve the ref, return null. + return null; + } catch (IOException e) { + throw new RuntimeException(e); } - - - // TODO handle recursive $ref values (refs from refs) - - // Cannot resolve the ref, return null. - return null; } } diff --git a/schema-util/openapi/src/main/java/io/apicurio/registry/content/refs/AbstractDataModelsReferenceFinder.java b/schema-util/openapi/src/main/java/io/apicurio/registry/content/refs/AbstractDataModelsReferenceFinder.java index 1eebac9f24..c4e58125fc 100644 --- a/schema-util/openapi/src/main/java/io/apicurio/registry/content/refs/AbstractDataModelsReferenceFinder.java +++ b/schema-util/openapi/src/main/java/io/apicurio/registry/content/refs/AbstractDataModelsReferenceFinder.java @@ -1,11 +1,7 @@ package io.apicurio.registry.content.refs; -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Collectors; - import com.fasterxml.jackson.databind.JsonNode; - +import com.fasterxml.jackson.databind.node.ObjectNode; import io.apicurio.datamodels.Library; import io.apicurio.datamodels.TraverserDirection; import io.apicurio.datamodels.models.Document; @@ -13,7 +9,13 @@ import io.apicurio.datamodels.models.Referenceable; import io.apicurio.datamodels.models.asyncapi.AsyncApiMessage; import io.apicurio.datamodels.models.visitors.AllNodeVisitor; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.content.util.ContentTypeUtil; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; /** * Implementation of a reference finder that uses Apicurio Data Models and so supports any specification @@ -24,21 +26,26 @@ public abstract class AbstractDataModelsReferenceFinder implements ReferenceFinder { /** - * @see io.apicurio.registry.content.refs.ReferenceFinder#findExternalReferences(io.apicurio.registry.content.ContentHandle) + * @see io.apicurio.registry.content.refs.ReferenceFinder#findExternalReferences(TypedContent) */ @Override - public Set findExternalReferences(ContentHandle content) { - Document doc = Library.readDocumentFromJSONString(content.content()); - - // Find all the $refs - RefFinderVisitor visitor = new RefFinderVisitor(); - Library.visitTree(doc, visitor, TraverserDirection.down); - - // Convert to ExternalReference and filter. - return visitor.allReferences.stream() - .map(ref -> new JsonPointerExternalReference(ref)) - .filter(ref -> ref.getResource() != null) - .collect(Collectors.toSet()); + public Set findExternalReferences(TypedContent content) { + try { + JsonNode node = ContentTypeUtil.parseJsonOrYaml(content); + Document doc = Library.readDocument((ObjectNode) node); + + // Find all the $refs + RefFinderVisitor visitor = new RefFinderVisitor(); + Library.visitTree(doc, visitor, TraverserDirection.down); + + // Convert to ExternalReference and filter. + return visitor.allReferences.stream() + .map(ref -> new JsonPointerExternalReference(ref)) + .filter(ref -> ref.getResource() != null) + .collect(Collectors.toSet()); + } catch (IOException e) { + throw new RuntimeException(e); + } } /** diff --git a/schema-util/openapi/src/main/java/io/apicurio/registry/rules/validity/ApicurioDataModelContentValidator.java b/schema-util/openapi/src/main/java/io/apicurio/registry/rules/validity/ApicurioDataModelContentValidator.java index f570c908bf..3ce358b698 100644 --- a/schema-util/openapi/src/main/java/io/apicurio/registry/rules/validity/ApicurioDataModelContentValidator.java +++ b/schema-util/openapi/src/main/java/io/apicurio/registry/rules/validity/ApicurioDataModelContentValidator.java @@ -1,12 +1,7 @@ package io.apicurio.registry.rules.validity; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import io.apicurio.datamodels.Library; import io.apicurio.datamodels.TraverserDirection; import io.apicurio.datamodels.models.Document; @@ -14,27 +9,36 @@ import io.apicurio.datamodels.models.Referenceable; import io.apicurio.datamodels.models.visitors.AllNodeVisitor; import io.apicurio.datamodels.validation.ValidationProblem; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.content.util.ContentTypeUtil; import io.apicurio.registry.rest.v3.beans.ArtifactReference; import io.apicurio.registry.rules.RuleViolation; import io.apicurio.registry.rules.RuleViolationException; import io.apicurio.registry.rules.integrity.IntegrityLevel; import io.apicurio.registry.types.RuleType; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + /** * A content validator implementation for the OpenAPI and AsyncAPI content types. */ public abstract class ApicurioDataModelContentValidator implements ContentValidator { /** - * @see io.apicurio.registry.rules.validity.ContentValidator#validate(ValidityLevel, ContentHandle, Map) + * @see io.apicurio.registry.rules.validity.ContentValidator#validate(ValidityLevel, TypedContent, Map) */ @Override - public void validate(ValidityLevel level, ContentHandle artifactContent, Map resolvedReferences) throws RuleViolationException { + public void validate(ValidityLevel level, TypedContent content, Map resolvedReferences) throws RuleViolationException { Document document = null; if (level == ValidityLevel.SYNTAX_ONLY || level == ValidityLevel.FULL) { try { - document = Library.readDocumentFromJSONString(artifactContent.content()); + JsonNode node = ContentTypeUtil.parseJsonOrYaml(content); + document = Library.readDocument((ObjectNode) node); } catch (Exception e) { throw new RuleViolationException("Syntax violation for " + getDataModelType() + " artifact.", RuleType.VALIDITY, level.name(), e); } @@ -54,12 +58,12 @@ public void validate(ValidityLevel level, ContentHandle artifactContent, Map references) throws RuleViolationException { + public void validateReferences(TypedContent content, List references) throws RuleViolationException { Set mappedRefs = references.stream().map(ref -> ref.getName()).collect(Collectors.toSet()); - Set all$refs = getAll$refs(artifactContent); + Set all$refs = getAll$refs(content); Set violations = all$refs.stream().filter(ref -> !mappedRefs.contains(ref)).map(missingRef -> { return new RuleViolation("Unmapped reference detected.", missingRef); }).collect(Collectors.toSet()); @@ -68,10 +72,11 @@ public void validateReferences(ContentHandle artifactContent, List getAll$refs(ContentHandle artifactContent) { + private Set getAll$refs(TypedContent content) { try { RefFinder refFinder = new RefFinder(); - Document document = Library.readDocumentFromJSONString(artifactContent.content()); + JsonNode node = ContentTypeUtil.parseJsonOrYaml(content); + Document document = Library.readDocument((ObjectNode) node); Library.visitTree(document, refFinder, TraverserDirection.down); return refFinder.references; } catch (Exception e) { diff --git a/schema-util/protobuf/src/main/java/io/apicurio/registry/content/canon/ProtobufContentCanonicalizer.java b/schema-util/protobuf/src/main/java/io/apicurio/registry/content/canon/ProtobufContentCanonicalizer.java index 730f944c72..f81f36fd6f 100644 --- a/schema-util/protobuf/src/main/java/io/apicurio/registry/content/canon/ProtobufContentCanonicalizer.java +++ b/schema-util/protobuf/src/main/java/io/apicurio/registry/content/canon/ProtobufContentCanonicalizer.java @@ -2,8 +2,9 @@ import com.squareup.wire.schema.internal.parser.ProtoFileElement; import com.squareup.wire.schema.internal.parser.ProtoParser; - import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.types.ContentTypes; import io.apicurio.registry.utils.protobuf.schema.FileDescriptorUtils; import java.util.Map; @@ -15,16 +16,16 @@ public class ProtobufContentCanonicalizer implements ContentCanonicalizer { /** - * @see io.apicurio.registry.content.canon.ContentCanonicalizer#canonicalize(io.apicurio.registry.content.ContentHandle, Map) + * @see io.apicurio.registry.content.canon.ContentCanonicalizer#canonicalize(TypedContent, Map) */ @Override - public ContentHandle canonicalize(ContentHandle content, Map resolvedReferences) { + public TypedContent canonicalize(TypedContent content, Map resolvedReferences) { try { - ProtoFileElement fileElem = ProtoParser.Companion.parse(FileDescriptorUtils.DEFAULT_LOCATION, content.content()); + ProtoFileElement fileElem = ProtoParser.Companion.parse(FileDescriptorUtils.DEFAULT_LOCATION, content.getContent().content()); //TODO maybe use FileDescriptorUtils to convert to a FileDescriptor and then convert back to ProtoFileElement - return ContentHandle.create(fileElem.toSchema()); + return TypedContent.create(ContentHandle.create(fileElem.toSchema()), ContentTypes.APPLICATION_PROTOBUF); } catch (Throwable e) { return content; } diff --git a/schema-util/protobuf/src/main/java/io/apicurio/registry/content/dereference/ProtobufDereferencer.java b/schema-util/protobuf/src/main/java/io/apicurio/registry/content/dereference/ProtobufDereferencer.java index db6863793d..6f26afe5be 100644 --- a/schema-util/protobuf/src/main/java/io/apicurio/registry/content/dereference/ProtobufDereferencer.java +++ b/schema-util/protobuf/src/main/java/io/apicurio/registry/content/dereference/ProtobufDereferencer.java @@ -3,6 +3,8 @@ import com.google.protobuf.DescriptorProtos; import com.squareup.wire.schema.internal.parser.ProtoFileElement; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.types.ContentTypes; import io.apicurio.registry.utils.protobuf.schema.FileDescriptorUtils; import io.apicurio.registry.utils.protobuf.schema.ProtobufFile; @@ -16,16 +18,16 @@ public class ProtobufDereferencer implements ContentDereferencer { @Override - public ContentHandle dereference(ContentHandle content, Map resolvedReferences) { - final ProtoFileElement protoFileElement = ProtobufFile.toProtoFileElement(content.content()); + public TypedContent dereference(TypedContent content, Map resolvedReferences) { + final ProtoFileElement protoFileElement = ProtobufFile.toProtoFileElement(content.getContent().content()); final Map schemaDefs = Collections.unmodifiableMap(resolvedReferences.entrySet() .stream() .collect(Collectors.toMap( Map.Entry::getKey, - e -> e.getValue().content() + e -> e.getValue().getContent().content() ))); - DescriptorProtos.FileDescriptorProto fileDescriptorProto = FileDescriptorUtils.toFileDescriptorProto(content.content(), FileDescriptorUtils.firstMessage(protoFileElement).getName(), Optional.ofNullable(protoFileElement.getPackageName()), schemaDefs); + DescriptorProtos.FileDescriptorProto fileDescriptorProto = FileDescriptorUtils.toFileDescriptorProto(content.getContent().content(), FileDescriptorUtils.firstMessage(protoFileElement).getName(), Optional.ofNullable(protoFileElement.getPackageName()), schemaDefs); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { @@ -35,14 +37,14 @@ public ContentHandle dereference(ContentHandle content, Map resolvedReferenceUrls) { + public TypedContent rewriteReferences(TypedContent content, Map resolvedReferenceUrls) { // TODO not yet implemented (perhaps cannot be implemented?) return content; } diff --git a/schema-util/protobuf/src/main/java/io/apicurio/registry/content/refs/ProtobufReferenceFinder.java b/schema-util/protobuf/src/main/java/io/apicurio/registry/content/refs/ProtobufReferenceFinder.java index 11f78d9825..fb248db9ad 100644 --- a/schema-util/protobuf/src/main/java/io/apicurio/registry/content/refs/ProtobufReferenceFinder.java +++ b/schema-util/protobuf/src/main/java/io/apicurio/registry/content/refs/ProtobufReferenceFinder.java @@ -1,18 +1,16 @@ package io.apicurio.registry.content.refs; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.utils.protobuf.schema.ProtobufFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.squareup.wire.schema.internal.parser.ProtoFileElement; - -import io.apicurio.registry.content.ContentHandle; -import io.apicurio.registry.utils.protobuf.schema.ProtobufFile; - /** * A Google Protocol Buffer implementation of a reference finder. */ @@ -21,12 +19,12 @@ public class ProtobufReferenceFinder implements ReferenceFinder { private static final Logger log = LoggerFactory.getLogger(ProtobufReferenceFinder.class); /** - * @see io.apicurio.registry.content.refs.ReferenceFinder#findExternalReferences(io.apicurio.registry.content.ContentHandle) + * @see io.apicurio.registry.content.refs.ReferenceFinder#findExternalReferences(TypedContent) */ @Override - public Set findExternalReferences(ContentHandle content) { + public Set findExternalReferences(TypedContent content) { try { - ProtoFileElement protoFileElement = ProtobufFile.toProtoFileElement(content.content()); + ProtoFileElement protoFileElement = ProtobufFile.toProtoFileElement(content.getContent().content()); Set allImports = new HashSet<>(); allImports.addAll(protoFileElement.getImports()); allImports.addAll(protoFileElement.getPublicImports()); diff --git a/schema-util/protobuf/src/main/java/io/apicurio/registry/rules/compatibility/ProtobufCompatibilityChecker.java b/schema-util/protobuf/src/main/java/io/apicurio/registry/rules/compatibility/ProtobufCompatibilityChecker.java index 44e8b14f42..0f8e707b6e 100644 --- a/schema-util/protobuf/src/main/java/io/apicurio/registry/rules/compatibility/ProtobufCompatibilityChecker.java +++ b/schema-util/protobuf/src/main/java/io/apicurio/registry/rules/compatibility/ProtobufCompatibilityChecker.java @@ -1,19 +1,19 @@ package io.apicurio.registry.rules.compatibility; -import static java.util.Objects.requireNonNull; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.rules.compatibility.protobuf.ProtobufCompatibilityCheckerLibrary; +import io.apicurio.registry.utils.protobuf.schema.ProtobufFile; +import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.Map; -import io.apicurio.registry.content.ContentHandle; -import io.apicurio.registry.rules.compatibility.protobuf.ProtobufCompatibilityCheckerLibrary; -import io.apicurio.registry.utils.protobuf.schema.ProtobufFile; -import org.jetbrains.annotations.NotNull; +import static java.util.Objects.requireNonNull; public class ProtobufCompatibilityChecker implements CompatibilityChecker { @Override - public CompatibilityExecutionResult testCompatibility(CompatibilityLevel compatibilityLevel, List existingArtifacts, ContentHandle proposedArtifact, Map resolvedReferences) { + public CompatibilityExecutionResult testCompatibility(CompatibilityLevel compatibilityLevel, List existingArtifacts, TypedContent proposedArtifact, Map resolvedReferences) { requireNonNull(compatibilityLevel, "compatibilityLevel MUST NOT be null"); requireNonNull(existingArtifacts, "existingArtifacts MUST NOT be null"); requireNonNull(proposedArtifact, "proposedArtifact MUST NOT be null"); @@ -22,8 +22,8 @@ public CompatibilityExecutionResult testCompatibility(CompatibilityLevel compati return CompatibilityExecutionResult.compatible(); } - ProtobufFile fileBefore = new ProtobufFile(existingArtifacts.get(existingArtifacts.size() - 1).content()); - ProtobufFile fileAfter = new ProtobufFile(proposedArtifact.content()); + ProtobufFile fileBefore = new ProtobufFile(existingArtifacts.get(existingArtifacts.size() - 1).getContent().content()); + ProtobufFile fileAfter = new ProtobufFile(proposedArtifact.getContent().content()); switch (compatibilityLevel) { case BACKWARD: { @@ -50,10 +50,10 @@ public CompatibilityExecutionResult testCompatibility(CompatibilityLevel compati } @NotNull - private CompatibilityExecutionResult testFullTransitive(List existingSchemas, ProtobufFile fileAfter) { + private CompatibilityExecutionResult testFullTransitive(List existingSchemas, ProtobufFile fileAfter) { ProtobufFile fileBefore; - for (ContentHandle existing : existingSchemas) { - fileBefore = new ProtobufFile(existing.content()); + for (TypedContent existing : existingSchemas) { + fileBefore = new ProtobufFile(existing.getContent().content()); if (!testFull(fileBefore, fileAfter).isCompatible()) { return CompatibilityExecutionResult.incompatible("The new version of the protobuf artifact is not fully compatible."); } @@ -73,10 +73,10 @@ private CompatibilityExecutionResult testFull(ProtobufFile fileBefore, ProtobufF } @NotNull - private CompatibilityExecutionResult testForwardTransitive(List existingSchemas, ProtobufFile fileAfter) { + private CompatibilityExecutionResult testForwardTransitive(List existingSchemas, ProtobufFile fileAfter) { ProtobufFile fileBefore; - for (ContentHandle existing : existingSchemas) { - fileBefore = new ProtobufFile(existing.content()); + for (TypedContent existing : existingSchemas) { + fileBefore = new ProtobufFile(existing.getContent().content()); ProtobufCompatibilityCheckerLibrary checker = new ProtobufCompatibilityCheckerLibrary(fileAfter, fileBefore); if (!checker.validate()) { return CompatibilityExecutionResult.incompatible("The new version of the protobuf artifact is not forward compatible."); @@ -96,10 +96,10 @@ private CompatibilityExecutionResult testForward(ProtobufFile fileBefore, Protob } @NotNull - private CompatibilityExecutionResult testBackwardTransitive(List existingSchemas, ProtobufFile fileAfter) { + private CompatibilityExecutionResult testBackwardTransitive(List existingSchemas, ProtobufFile fileAfter) { ProtobufFile fileBefore; - for (ContentHandle existing : existingSchemas) { - fileBefore = new ProtobufFile(existing.content()); + for (TypedContent existing : existingSchemas) { + fileBefore = new ProtobufFile(existing.getContent().content()); ProtobufCompatibilityCheckerLibrary checker = new ProtobufCompatibilityCheckerLibrary(fileBefore, fileAfter); if (!checker.validate()) { return CompatibilityExecutionResult.incompatible("The new version of the protobuf artifact is not backward compatible."); diff --git a/schema-util/protobuf/src/main/java/io/apicurio/registry/rules/validity/ProtobufContentValidator.java b/schema-util/protobuf/src/main/java/io/apicurio/registry/rules/validity/ProtobufContentValidator.java index 28dad10943..ebfe4a805e 100644 --- a/schema-util/protobuf/src/main/java/io/apicurio/registry/rules/validity/ProtobufContentValidator.java +++ b/schema-util/protobuf/src/main/java/io/apicurio/registry/rules/validity/ProtobufContentValidator.java @@ -1,26 +1,27 @@ package io.apicurio.registry.rules.validity; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - import com.google.protobuf.Descriptors; import com.squareup.wire.schema.internal.parser.MessageElement; import com.squareup.wire.schema.internal.parser.ProtoFileElement; - import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rest.v3.beans.ArtifactReference; import io.apicurio.registry.rules.RuleViolation; import io.apicurio.registry.rules.RuleViolationException; import io.apicurio.registry.rules.integrity.IntegrityLevel; +import io.apicurio.registry.types.ContentTypes; import io.apicurio.registry.types.RuleType; import io.apicurio.registry.utils.protobuf.schema.FileDescriptorUtils; import io.apicurio.registry.utils.protobuf.schema.ProtobufFile; import io.apicurio.registry.utils.protobuf.schema.ProtobufSchema; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + /** * A content validator implementation for the Protobuf content type. * @@ -34,33 +35,33 @@ public ProtobufContentValidator() { } /** - * @see io.apicurio.registry.rules.validity.ContentValidator#validate(ValidityLevel, ContentHandle, Map) + * @see io.apicurio.registry.rules.validity.ContentValidator#validate(ValidityLevel, TypedContent, Map) */ @Override - public void validate(ValidityLevel level, ContentHandle artifactContent, Map resolvedReferences) throws RuleViolationException { + public void validate(ValidityLevel level, TypedContent content, Map resolvedReferences) throws RuleViolationException { if (level == ValidityLevel.SYNTAX_ONLY || level == ValidityLevel.FULL) { try { if (resolvedReferences == null || resolvedReferences.isEmpty()) { - ProtobufFile.toProtoFileElement(artifactContent.content()); + ProtobufFile.toProtoFileElement(content.getContent().content()); } else { - final ProtoFileElement protoFileElement = ProtobufFile.toProtoFileElement(artifactContent.content()); + final ProtoFileElement protoFileElement = ProtobufFile.toProtoFileElement(content.getContent().content()); final Map dependencies = Collections.unmodifiableMap(resolvedReferences.entrySet() .stream() .collect(Collectors.toMap( Map.Entry::getKey, - e -> ProtobufFile.toProtoFileElement(e.getValue().content()) + e -> ProtobufFile.toProtoFileElement(e.getValue().getContent().content()) ))); MessageElement firstMessage = FileDescriptorUtils.firstMessage(protoFileElement); if (firstMessage != null) { try { final Descriptors.Descriptor fileDescriptor = FileDescriptorUtils.toDescriptor(firstMessage.getName(), protoFileElement, dependencies); - ContentHandle.create(fileDescriptor.toString()); + TypedContent.create(ContentHandle.create(fileDescriptor.toString()), ContentTypes.APPLICATION_PROTOBUF); } catch (IllegalStateException ise) { //If we fail to init the dynamic schema, try to get the descriptor from the proto element - ContentHandle.create(getFileDescriptorFromElement(protoFileElement).toString()); + TypedContent.create(ContentHandle.create(getFileDescriptorFromElement(protoFileElement).toString()), ContentTypes.APPLICATION_PROTOBUF); } } else { - ContentHandle.create(getFileDescriptorFromElement(protoFileElement).toString()); + TypedContent.create(ContentHandle.create(getFileDescriptorFromElement(protoFileElement).toString()), ContentTypes.APPLICATION_PROTOBUF); } } } catch (Exception e) { @@ -70,14 +71,14 @@ public void validate(ValidityLevel level, ContentHandle artifactContent, Map references) throws RuleViolationException { + public void validateReferences(TypedContent content, List references) throws RuleViolationException { try { Set mappedRefs = references.stream().map(ref -> ref.getName()).collect(Collectors.toSet()); - ProtoFileElement protoFileElement = ProtobufFile.toProtoFileElement(artifactContent.content()); + ProtoFileElement protoFileElement = ProtobufFile.toProtoFileElement(content.getContent().content()); Set allImports = new HashSet<>(); allImports.addAll(protoFileElement.getImports()); allImports.addAll(protoFileElement.getPublicImports()); diff --git a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/ArtifactTypeUtilProvider.java b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/ArtifactTypeUtilProvider.java index 27dbb9cdc3..ba0efe84ba 100644 --- a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/ArtifactTypeUtilProvider.java +++ b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/ArtifactTypeUtilProvider.java @@ -1,5 +1,6 @@ package io.apicurio.registry.types.provider; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.content.canon.ContentCanonicalizer; import io.apicurio.registry.content.dereference.ContentDereferencer; import io.apicurio.registry.content.extract.ContentExtractor; @@ -7,6 +8,8 @@ import io.apicurio.registry.rules.compatibility.CompatibilityChecker; import io.apicurio.registry.rules.validity.ContentValidator; +import java.util.Map; + /** * Interface providing different utils per artifact type * * compatibility checker @@ -19,6 +22,12 @@ public interface ArtifactTypeUtilProvider { String getArtifactType(); + /** + * Returns true if the given content is accepted as handled by the provider. Useful + * to know if e.g. some bit of content is an AVRO or OPENAPI. + */ + boolean acceptsContent(TypedContent content, Map resolvedReferences); + CompatibilityChecker getCompatibilityChecker(); ContentCanonicalizer getContentCanonicalizer(); diff --git a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/ArtifactTypeUtilProviderFactory.java b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/ArtifactTypeUtilProviderFactory.java index e36d6dd028..542f0517bb 100644 --- a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/ArtifactTypeUtilProviderFactory.java +++ b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/ArtifactTypeUtilProviderFactory.java @@ -1,12 +1,13 @@ package io.apicurio.registry.types.provider; -import jakarta.ws.rs.core.MediaType; import java.util.List; public interface ArtifactTypeUtilProviderFactory { + ArtifactTypeUtilProvider getArtifactTypeProvider(String type); List getAllArtifactTypes(); - MediaType getArtifactMediaType(String type); + List getAllArtifactTypeProviders(); + } diff --git a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/AsyncApiArtifactTypeUtilProvider.java b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/AsyncApiArtifactTypeUtilProvider.java index b0d626944e..aa5e8f7e3b 100644 --- a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/AsyncApiArtifactTypeUtilProvider.java +++ b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/AsyncApiArtifactTypeUtilProvider.java @@ -1,21 +1,46 @@ package io.apicurio.registry.types.provider; +import com.fasterxml.jackson.databind.JsonNode; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.content.canon.AsyncApiContentCanonicalizer; import io.apicurio.registry.content.canon.ContentCanonicalizer; -import io.apicurio.registry.content.canon.JsonContentCanonicalizer; import io.apicurio.registry.content.dereference.AsyncApiDereferencer; import io.apicurio.registry.content.dereference.ContentDereferencer; import io.apicurio.registry.content.extract.AsyncApiContentExtractor; import io.apicurio.registry.content.extract.ContentExtractor; import io.apicurio.registry.content.refs.AsyncApiReferenceFinder; import io.apicurio.registry.content.refs.ReferenceFinder; +import io.apicurio.registry.content.util.ContentTypeUtil; import io.apicurio.registry.rules.compatibility.CompatibilityChecker; import io.apicurio.registry.rules.compatibility.NoopCompatibilityChecker; import io.apicurio.registry.rules.validity.AsyncApiContentValidator; import io.apicurio.registry.rules.validity.ContentValidator; import io.apicurio.registry.types.ArtifactType; +import java.util.Map; + public class AsyncApiArtifactTypeUtilProvider extends AbstractArtifactTypeUtilProvider { + @Override + public boolean acceptsContent(TypedContent content, Map resolvedReferences) { + try { + String contentType = content.getContentType(); + JsonNode tree = null; + // If the content is YAML, then convert it to JSON first (the data-models library only accepts JSON). + if (contentType.toLowerCase().contains("yml") || contentType.toLowerCase().contains("yaml")) { + tree = ContentTypeUtil.parseYaml(content.getContent()); + } else { + tree = ContentTypeUtil.parseJson(content.getContent()); + } + if (tree.has("asyncapi")) { + return true; + } + } catch (Exception e) { + // Error - invalid syntax + } + return false; + } + @Override public String getArtifactType() { return ArtifactType.ASYNCAPI; @@ -28,7 +53,7 @@ protected CompatibilityChecker createCompatibilityChecker() { @Override protected ContentCanonicalizer createContentCanonicalizer() { - return new JsonContentCanonicalizer(); + return new AsyncApiContentCanonicalizer(); } @Override diff --git a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/AvroArtifactTypeUtilProvider.java b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/AvroArtifactTypeUtilProvider.java index 3ba1a14d45..802014a94c 100644 --- a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/AvroArtifactTypeUtilProvider.java +++ b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/AvroArtifactTypeUtilProvider.java @@ -1,21 +1,62 @@ package io.apicurio.registry.types.provider; -import io.apicurio.registry.content.canon.EnhancedAvroContentCanonicalizer; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.content.canon.ContentCanonicalizer; +import io.apicurio.registry.content.canon.EnhancedAvroContentCanonicalizer; import io.apicurio.registry.content.dereference.AvroDereferencer; import io.apicurio.registry.content.dereference.ContentDereferencer; import io.apicurio.registry.content.extract.AvroContentExtractor; import io.apicurio.registry.content.extract.ContentExtractor; import io.apicurio.registry.content.refs.JsonSchemaReferenceFinder; import io.apicurio.registry.content.refs.ReferenceFinder; +import io.apicurio.registry.content.util.ContentTypeUtil; import io.apicurio.registry.rules.compatibility.AvroCompatibilityChecker; import io.apicurio.registry.rules.compatibility.CompatibilityChecker; import io.apicurio.registry.rules.validity.AvroContentValidator; import io.apicurio.registry.rules.validity.ContentValidator; import io.apicurio.registry.types.ArtifactType; +import org.apache.avro.Schema; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; public class AvroArtifactTypeUtilProvider extends AbstractArtifactTypeUtilProvider { + private static final Pattern QUOTED_BRACKETS = Pattern.compile(": *\"\\{}\""); + + /** + * Given a content removes any quoted brackets. This is useful for some validation corner cases in avro where some libraries detects quoted brackets as valid and others as invalid + */ + private static String removeQuotedBrackets(String content) { + return QUOTED_BRACKETS.matcher(content).replaceAll(":{}"); + } + + @Override + public boolean acceptsContent(TypedContent content, Map resolvedReferences) { + try { + String contentType = content.getContentType(); + if (contentType.toLowerCase().contains("json") && ContentTypeUtil.isParsableJson(content.getContent())) { + // Avro without quote + final Schema.Parser parser = new Schema.Parser(); + final List schemaRefs = new ArrayList<>(); + for (Map.Entry referencedContent : resolvedReferences.entrySet()) { + if (!parser.getTypes().containsKey(referencedContent.getKey())) { + Schema schemaRef = parser.parse(referencedContent.getValue().getContent().content()); + schemaRefs.add(schemaRef); + } + } + final Schema schema = parser.parse(removeQuotedBrackets(content.getContent().content())); + schema.toString(schemaRefs, false); + return true; + } + } catch (Exception e) { + //ignored + } + return false; + } + @Override public String getArtifactType() { return ArtifactType.AVRO; diff --git a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/DefaultArtifactTypeUtilProviderImpl.java b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/DefaultArtifactTypeUtilProviderImpl.java index 4e3ce7a9ce..ebe9e26ed0 100644 --- a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/DefaultArtifactTypeUtilProviderImpl.java +++ b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/DefaultArtifactTypeUtilProviderImpl.java @@ -1,9 +1,5 @@ package io.apicurio.registry.types.provider; -import io.apicurio.registry.types.ArtifactMediaTypes; -import io.apicurio.registry.types.ArtifactType; - -import jakarta.ws.rs.core.MediaType; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -16,16 +12,16 @@ public class DefaultArtifactTypeUtilProviderImpl implements ArtifactTypeUtilProv protected List providers = new ArrayList( List.of( + new ProtobufArtifactTypeUtilProvider(), + new OpenApiArtifactTypeUtilProvider(), new AsyncApiArtifactTypeUtilProvider(), + new JsonArtifactTypeUtilProvider(), new AvroArtifactTypeUtilProvider(), new GraphQLArtifactTypeUtilProvider(), - new JsonArtifactTypeUtilProvider(), new KConnectArtifactTypeUtilProvider(), - new OpenApiArtifactTypeUtilProvider(), - new ProtobufArtifactTypeUtilProvider(), new WsdlArtifactTypeUtilProvider(), - new XmlArtifactTypeUtilProvider(), - new XsdArtifactTypeUtilProvider()) + new XsdArtifactTypeUtilProvider(), + new XmlArtifactTypeUtilProvider()) ); @Override @@ -45,19 +41,7 @@ public List getAllArtifactTypes() { } @Override - public MediaType getArtifactMediaType(String type) { - // The content-type will be different for protobuf artifacts, graphql artifacts, and XML artifacts - MediaType contentType = ArtifactMediaTypes.JSON; - if (type.equals(ArtifactType.PROTOBUF)) { - contentType = ArtifactMediaTypes.PROTO; - } - if (type.equals(ArtifactType.GRAPHQL)) { - contentType = ArtifactMediaTypes.GRAPHQL; - } - if (type.equals(ArtifactType.WSDL) || type.equals(ArtifactType.XSD) || type.equals(ArtifactType.XML)) { - contentType = ArtifactMediaTypes.XML; - } - - return contentType; + public List getAllArtifactTypeProviders() { + return providers; } } diff --git a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/GraphQLArtifactTypeUtilProvider.java b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/GraphQLArtifactTypeUtilProvider.java index 9f37ebfd8b..eed629d501 100644 --- a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/GraphQLArtifactTypeUtilProvider.java +++ b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/GraphQLArtifactTypeUtilProvider.java @@ -1,5 +1,8 @@ package io.apicurio.registry.types.provider; +import graphql.schema.idl.SchemaParser; +import graphql.schema.idl.TypeDefinitionRegistry; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.content.canon.ContentCanonicalizer; import io.apicurio.registry.content.canon.GraphQLContentCanonicalizer; import io.apicurio.registry.content.dereference.ContentDereferencer; @@ -13,7 +16,26 @@ import io.apicurio.registry.rules.validity.GraphQLContentValidator; import io.apicurio.registry.types.ArtifactType; +import java.util.Map; + public class GraphQLArtifactTypeUtilProvider extends AbstractArtifactTypeUtilProvider { + + @Override + public boolean acceptsContent(TypedContent content, Map resolvedReferences) { + try { + String contentType = content.getContentType(); + if (contentType.toLowerCase().contains("graph")) { + TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(content.getContent().content()); + if (typeRegistry != null) { + return true; + } + } + } catch (Exception e) { + // Must not be a GraphQL file + } + return false; + } + @Override public String getArtifactType() { return ArtifactType.GRAPHQL; diff --git a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/JsonArtifactTypeUtilProvider.java b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/JsonArtifactTypeUtilProvider.java index a0705cc79e..d65e28cead 100644 --- a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/JsonArtifactTypeUtilProvider.java +++ b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/JsonArtifactTypeUtilProvider.java @@ -1,21 +1,42 @@ package io.apicurio.registry.types.provider; +import com.fasterxml.jackson.databind.JsonNode; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.content.canon.ContentCanonicalizer; import io.apicurio.registry.content.canon.JsonContentCanonicalizer; -import io.apicurio.registry.content.dereference.ContentDereferencer; import io.apicurio.registry.content.dereference.AsyncApiDereferencer; +import io.apicurio.registry.content.dereference.ContentDereferencer; import io.apicurio.registry.content.extract.ContentExtractor; import io.apicurio.registry.content.extract.JsonContentExtractor; import io.apicurio.registry.content.refs.JsonSchemaReferenceFinder; import io.apicurio.registry.content.refs.ReferenceFinder; +import io.apicurio.registry.content.util.ContentTypeUtil; import io.apicurio.registry.rules.compatibility.CompatibilityChecker; import io.apicurio.registry.rules.compatibility.JsonSchemaCompatibilityChecker; import io.apicurio.registry.rules.validity.ContentValidator; import io.apicurio.registry.rules.validity.JsonSchemaContentValidator; import io.apicurio.registry.types.ArtifactType; +import java.util.Map; + public class JsonArtifactTypeUtilProvider extends AbstractArtifactTypeUtilProvider { + @Override + public boolean acceptsContent(TypedContent content, Map resolvedReferences) { + try { + String contentType = content.getContentType(); + if (contentType.toLowerCase().contains("json") && ContentTypeUtil.isParsableJson(content.getContent())) { + JsonNode tree = ContentTypeUtil.parseJson(content.getContent()); + if (tree.has("$schema") && tree.get("$schema").asText().contains("json-schema.org") || tree.has("properties")) { + return true; + } + } + } catch (Exception e) { + // Error - invalid syntax + } + return false; + } + @Override public String getArtifactType() { return ArtifactType.JSON; diff --git a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/KConnectArtifactTypeUtilProvider.java b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/KConnectArtifactTypeUtilProvider.java index f15ad23afc..d04e9fe96b 100644 --- a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/KConnectArtifactTypeUtilProvider.java +++ b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/KConnectArtifactTypeUtilProvider.java @@ -1,5 +1,6 @@ package io.apicurio.registry.types.provider; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.content.canon.ContentCanonicalizer; import io.apicurio.registry.content.canon.KafkaConnectContentCanonicalizer; import io.apicurio.registry.content.dereference.ContentDereferencer; @@ -13,7 +14,14 @@ import io.apicurio.registry.rules.validity.KafkaConnectContentValidator; import io.apicurio.registry.types.ArtifactType; +import java.util.Map; + public class KConnectArtifactTypeUtilProvider extends AbstractArtifactTypeUtilProvider { + @Override + public boolean acceptsContent(TypedContent content, Map resolvedReferences) { + return false; + } + @Override public String getArtifactType() { return ArtifactType.KCONNECT; diff --git a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/OpenApiArtifactTypeUtilProvider.java b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/OpenApiArtifactTypeUtilProvider.java index bc190a51da..d5b401090e 100644 --- a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/OpenApiArtifactTypeUtilProvider.java +++ b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/OpenApiArtifactTypeUtilProvider.java @@ -1,21 +1,46 @@ package io.apicurio.registry.types.provider; +import com.fasterxml.jackson.databind.JsonNode; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.content.canon.ContentCanonicalizer; -import io.apicurio.registry.content.canon.JsonContentCanonicalizer; -import io.apicurio.registry.content.dereference.ContentDereferencer; +import io.apicurio.registry.content.canon.OpenApiContentCanonicalizer; import io.apicurio.registry.content.dereference.AsyncApiDereferencer; +import io.apicurio.registry.content.dereference.ContentDereferencer; import io.apicurio.registry.content.extract.ContentExtractor; import io.apicurio.registry.content.extract.OpenApiContentExtractor; import io.apicurio.registry.content.refs.OpenApiReferenceFinder; import io.apicurio.registry.content.refs.ReferenceFinder; +import io.apicurio.registry.content.util.ContentTypeUtil; import io.apicurio.registry.rules.compatibility.CompatibilityChecker; import io.apicurio.registry.rules.compatibility.NoopCompatibilityChecker; import io.apicurio.registry.rules.validity.ContentValidator; import io.apicurio.registry.rules.validity.OpenApiContentValidator; import io.apicurio.registry.types.ArtifactType; +import java.util.Map; + public class OpenApiArtifactTypeUtilProvider extends AbstractArtifactTypeUtilProvider { + @Override + public boolean acceptsContent(TypedContent content, Map resolvedReferences) { + try { + String contentType = content.getContentType(); + JsonNode tree = null; + // If the content is YAML, then convert it to JSON first (the data-models library only accepts JSON). + if (contentType.toLowerCase().contains("yml") || contentType.toLowerCase().contains("yaml")) { + tree = ContentTypeUtil.parseYaml(content.getContent()); + } else { + tree = ContentTypeUtil.parseJson(content.getContent()); + } + if (tree.has("openapi") || tree.has("swagger")) { + return true; + } + } catch (Exception e) { + // Error - invalid syntax + } + return false; + } + @Override public String getArtifactType() { return ArtifactType.OPENAPI; @@ -28,7 +53,7 @@ protected CompatibilityChecker createCompatibilityChecker() { @Override protected ContentCanonicalizer createContentCanonicalizer() { - return new JsonContentCanonicalizer(); + return new OpenApiContentCanonicalizer(); } @Override diff --git a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/ProtobufArtifactTypeUtilProvider.java b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/ProtobufArtifactTypeUtilProvider.java index 13ef1aa180..6d2dfc61f2 100644 --- a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/ProtobufArtifactTypeUtilProvider.java +++ b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/ProtobufArtifactTypeUtilProvider.java @@ -1,5 +1,7 @@ package io.apicurio.registry.types.provider; +import com.google.protobuf.DescriptorProtos; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.content.canon.ContentCanonicalizer; import io.apicurio.registry.content.canon.ProtobufContentCanonicalizer; import io.apicurio.registry.content.dereference.ContentDereferencer; @@ -13,8 +15,34 @@ import io.apicurio.registry.rules.validity.ContentValidator; import io.apicurio.registry.rules.validity.ProtobufContentValidator; import io.apicurio.registry.types.ArtifactType; +import io.apicurio.registry.utils.protobuf.schema.FileDescriptorUtils; +import io.apicurio.registry.utils.protobuf.schema.ProtobufFile; + +import java.util.Base64; +import java.util.Map; public class ProtobufArtifactTypeUtilProvider extends AbstractArtifactTypeUtilProvider { + @Override + public boolean acceptsContent(TypedContent content, Map resolvedReferences) { + try { + String contentType = content.getContentType(); + if (contentType.toLowerCase().contains("proto")) { + ProtobufFile.toProtoFileElement(content.getContent().content()); + return true; + } + } catch (Exception e) { + try { + // Attempt to parse binary FileDescriptorProto + byte[] bytes = Base64.getDecoder().decode(content.getContent().content()); + FileDescriptorUtils.fileDescriptorToProtoFile(DescriptorProtos.FileDescriptorProto.parseFrom(bytes)); + return true; + } catch (Exception pe) { + // Doesn't seem to be protobuf + } + } + return false; + } + @Override public String getArtifactType() { return ArtifactType.PROTOBUF; diff --git a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/WsdlArtifactTypeUtilProvider.java b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/WsdlArtifactTypeUtilProvider.java index b26020471d..ef76e32776 100644 --- a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/WsdlArtifactTypeUtilProvider.java +++ b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/WsdlArtifactTypeUtilProvider.java @@ -1,5 +1,6 @@ package io.apicurio.registry.types.provider; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.content.canon.ContentCanonicalizer; import io.apicurio.registry.content.canon.XmlContentCanonicalizer; import io.apicurio.registry.content.dereference.ContentDereferencer; @@ -7,14 +8,38 @@ import io.apicurio.registry.content.extract.WsdlOrXsdContentExtractor; import io.apicurio.registry.content.refs.NoOpReferenceFinder; import io.apicurio.registry.content.refs.ReferenceFinder; +import io.apicurio.registry.content.util.ContentTypeUtil; import io.apicurio.registry.rules.compatibility.CompatibilityChecker; import io.apicurio.registry.rules.compatibility.NoopCompatibilityChecker; import io.apicurio.registry.rules.validity.ContentValidator; import io.apicurio.registry.rules.validity.WsdlContentValidator; import io.apicurio.registry.types.ArtifactType; +import io.apicurio.registry.util.DocumentBuilderAccessor; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import java.util.Map; public class WsdlArtifactTypeUtilProvider extends AbstractArtifactTypeUtilProvider { + @Override + public boolean acceptsContent(TypedContent content, Map resolvedReferences) { + try { + String contentType = content.getContentType(); + if (contentType.toLowerCase().contains("xml") && ContentTypeUtil.isParsableXml(content.getContent())) { + Document xmlDocument = DocumentBuilderAccessor.getDocumentBuilder().parse(content.getContent().stream()); + Element root = xmlDocument.getDocumentElement(); + String ns = root.getNamespaceURI(); + if (ns != null && (ns.equals("http://schemas.xmlsoap.org/wsdl/") + || ns.equals("http://www.w3.org/ns/wsdl/"))) { + return true; + } + } + } catch (Exception e) { + } + return false; + } + /** * @see io.apicurio.registry.types.provider.ArtifactTypeUtilProvider#getArtifactType() */ diff --git a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/XmlArtifactTypeUtilProvider.java b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/XmlArtifactTypeUtilProvider.java index d94e7c2e3c..edc5db5d79 100644 --- a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/XmlArtifactTypeUtilProvider.java +++ b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/XmlArtifactTypeUtilProvider.java @@ -1,5 +1,6 @@ package io.apicurio.registry.types.provider; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.content.canon.ContentCanonicalizer; import io.apicurio.registry.content.canon.XmlContentCanonicalizer; import io.apicurio.registry.content.dereference.ContentDereferencer; @@ -7,14 +8,42 @@ import io.apicurio.registry.content.extract.NoopContentExtractor; import io.apicurio.registry.content.refs.NoOpReferenceFinder; import io.apicurio.registry.content.refs.ReferenceFinder; +import io.apicurio.registry.content.util.ContentTypeUtil; import io.apicurio.registry.rules.compatibility.CompatibilityChecker; import io.apicurio.registry.rules.compatibility.NoopCompatibilityChecker; import io.apicurio.registry.rules.validity.ContentValidator; import io.apicurio.registry.rules.validity.XmlContentValidator; import io.apicurio.registry.types.ArtifactType; +import io.apicurio.registry.util.DocumentBuilderAccessor; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import java.util.Map; public class XmlArtifactTypeUtilProvider extends AbstractArtifactTypeUtilProvider { + @Override + public boolean acceptsContent(TypedContent content, Map resolvedReferences) { + try { + String contentType = content.getContentType(); + if (contentType.toLowerCase().contains("xml") && ContentTypeUtil.isParsableXml(content.getContent())) { + Document xmlDocument = DocumentBuilderAccessor.getDocumentBuilder().parse(content.getContent().stream()); + Element root = xmlDocument.getDocumentElement(); + String ns = root.getNamespaceURI(); + if (ns != null && ns.equals("http://www.w3.org/2001/XMLSchema")) { + return false; + } else if (ns != null && (ns.equals("http://schemas.xmlsoap.org/wsdl/") + || ns.equals("http://www.w3.org/ns/wsdl/"))) { + return false; + } else { + return true; + } + } + } catch (Exception e) { + } + return false; + } + /** * @see io.apicurio.registry.types.provider.ArtifactTypeUtilProvider#getArtifactType() */ diff --git a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/XsdArtifactTypeUtilProvider.java b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/XsdArtifactTypeUtilProvider.java index 2c0bd37afb..d5f08acff6 100644 --- a/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/XsdArtifactTypeUtilProvider.java +++ b/schema-util/util-provider/src/main/java/io/apicurio/registry/types/provider/XsdArtifactTypeUtilProvider.java @@ -1,5 +1,6 @@ package io.apicurio.registry.types.provider; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.content.canon.ContentCanonicalizer; import io.apicurio.registry.content.canon.XmlContentCanonicalizer; import io.apicurio.registry.content.dereference.ContentDereferencer; @@ -7,14 +8,37 @@ import io.apicurio.registry.content.extract.WsdlOrXsdContentExtractor; import io.apicurio.registry.content.refs.NoOpReferenceFinder; import io.apicurio.registry.content.refs.ReferenceFinder; +import io.apicurio.registry.content.util.ContentTypeUtil; import io.apicurio.registry.rules.compatibility.CompatibilityChecker; import io.apicurio.registry.rules.compatibility.NoopCompatibilityChecker; import io.apicurio.registry.rules.validity.ContentValidator; import io.apicurio.registry.rules.validity.XsdContentValidator; import io.apicurio.registry.types.ArtifactType; +import io.apicurio.registry.util.DocumentBuilderAccessor; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import java.util.Map; public class XsdArtifactTypeUtilProvider extends AbstractArtifactTypeUtilProvider { + @Override + public boolean acceptsContent(TypedContent content, Map resolvedReferences) { + try { + String contentType = content.getContentType(); + if (contentType.toLowerCase().contains("xml") && ContentTypeUtil.isParsableXml(content.getContent())) { + Document xmlDocument = DocumentBuilderAccessor.getDocumentBuilder().parse(content.getContent().stream()); + Element root = xmlDocument.getDocumentElement(); + String ns = root.getNamespaceURI(); + if (ns != null && ns.equals("http://www.w3.org/2001/XMLSchema")) { + return true; + } + } + } catch (Exception e) { + } + return false; + } + /** * @see io.apicurio.registry.types.provider.ArtifactTypeUtilProvider#getArtifactType() */ diff --git a/schema-util/util-provider/src/test/java/io/apicurio/registry/content/dereference/AsyncApiContentDereferencerTest.java b/schema-util/util-provider/src/test/java/io/apicurio/registry/content/dereference/AsyncApiContentDereferencerTest.java index 37c74ca3be..76f8130c7f 100644 --- a/schema-util/util-provider/src/test/java/io/apicurio/registry/content/dereference/AsyncApiContentDereferencerTest.java +++ b/schema-util/util-provider/src/test/java/io/apicurio/registry/content/dereference/AsyncApiContentDereferencerTest.java @@ -1,25 +1,24 @@ package io.apicurio.registry.content.dereference; -import java.util.Map; -import java.util.Set; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.content.refs.AsyncApiReferenceFinder; import io.apicurio.registry.content.refs.ExternalReference; import io.apicurio.registry.content.refs.JsonPointerExternalReference; -import io.apicurio.registry.content.refs.AsyncApiReferenceFinder; import io.apicurio.registry.content.refs.ReferenceFinder; import io.apicurio.registry.rules.validity.ArtifactUtilProviderTestBase; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Map; +import java.util.Set; public class AsyncApiContentDereferencerTest extends ArtifactUtilProviderTestBase { @Test public void testRewriteReferences() { - ContentHandle content = resourceToContentHandle("asyncapi-to-rewrite.json"); + TypedContent content = resourceToTypedContentHandle("asyncapi-to-rewrite.json"); AsyncApiDereferencer dereferencer = new AsyncApiDereferencer(); - ContentHandle modifiedContent = dereferencer.rewriteReferences(content, Map.of( + TypedContent modifiedContent = dereferencer.rewriteReferences(content, Map.of( "./TradeKey.avsc", "https://www.example.org/schemas/TradeKey.avsc", "./common-types.json#/components/schemas/User", "https://www.example.org/schemas/common-types.json#/components/schemas/User")); diff --git a/schema-util/util-provider/src/test/java/io/apicurio/registry/content/dereference/JsonSchemaContentDereferencerTest.java b/schema-util/util-provider/src/test/java/io/apicurio/registry/content/dereference/JsonSchemaContentDereferencerTest.java index 92393da83a..12a962640c 100644 --- a/schema-util/util-provider/src/test/java/io/apicurio/registry/content/dereference/JsonSchemaContentDereferencerTest.java +++ b/schema-util/util-provider/src/test/java/io/apicurio/registry/content/dereference/JsonSchemaContentDereferencerTest.java @@ -1,25 +1,24 @@ package io.apicurio.registry.content.dereference; -import java.util.Map; -import java.util.Set; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.content.refs.ExternalReference; import io.apicurio.registry.content.refs.JsonPointerExternalReference; import io.apicurio.registry.content.refs.JsonSchemaReferenceFinder; import io.apicurio.registry.content.refs.ReferenceFinder; import io.apicurio.registry.rules.validity.ArtifactUtilProviderTestBase; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Map; +import java.util.Set; public class JsonSchemaContentDereferencerTest extends ArtifactUtilProviderTestBase { @Test public void testRewriteReferences() { - ContentHandle content = resourceToContentHandle("json-schema-to-rewrite.json"); + TypedContent content = resourceToTypedContentHandle("json-schema-to-rewrite.json"); JsonSchemaDereferencer dereferencer = new JsonSchemaDereferencer(); - ContentHandle modifiedContent = dereferencer.rewriteReferences(content, Map.of( + TypedContent modifiedContent = dereferencer.rewriteReferences(content, Map.of( "./address.json", "https://www.example.org/schemas/address.json", "./ssn.json", "https://www.example.org/schemas/ssn.json")); diff --git a/schema-util/util-provider/src/test/java/io/apicurio/registry/content/dereference/OpenApiContentDereferencerTest.java b/schema-util/util-provider/src/test/java/io/apicurio/registry/content/dereference/OpenApiContentDereferencerTest.java index 3476e37b3b..96f560dc67 100644 --- a/schema-util/util-provider/src/test/java/io/apicurio/registry/content/dereference/OpenApiContentDereferencerTest.java +++ b/schema-util/util-provider/src/test/java/io/apicurio/registry/content/dereference/OpenApiContentDereferencerTest.java @@ -1,25 +1,24 @@ package io.apicurio.registry.content.dereference; -import java.util.Map; -import java.util.Set; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.content.refs.ExternalReference; import io.apicurio.registry.content.refs.JsonPointerExternalReference; import io.apicurio.registry.content.refs.OpenApiReferenceFinder; import io.apicurio.registry.content.refs.ReferenceFinder; import io.apicurio.registry.rules.validity.ArtifactUtilProviderTestBase; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Map; +import java.util.Set; public class OpenApiContentDereferencerTest extends ArtifactUtilProviderTestBase { @Test public void testRewriteReferences() { - ContentHandle content = resourceToContentHandle("openapi-to-rewrite.json"); + TypedContent content = resourceToTypedContentHandle("openapi-to-rewrite.json"); OpenApiDereferencer dereferencer = new OpenApiDereferencer(); - ContentHandle modifiedContent = dereferencer.rewriteReferences(content, Map.of( + TypedContent modifiedContent = dereferencer.rewriteReferences(content, Map.of( "./types/bar-types.json#/components/schemas/Bar", "https://www.example.org/schemas/bar-types.json#/components/schemas/Bar", "./types/foo-types.json#/components/schemas/Foo", "https://www.example.org/schemas/foo-types.json#/components/schemas/Foo")); diff --git a/schema-util/util-provider/src/test/java/io/apicurio/registry/content/refs/AsyncApiReferenceFinderTest.java b/schema-util/util-provider/src/test/java/io/apicurio/registry/content/refs/AsyncApiReferenceFinderTest.java index 85da2cead7..b374d6c4d6 100644 --- a/schema-util/util-provider/src/test/java/io/apicurio/registry/content/refs/AsyncApiReferenceFinderTest.java +++ b/schema-util/util-provider/src/test/java/io/apicurio/registry/content/refs/AsyncApiReferenceFinderTest.java @@ -1,21 +1,20 @@ package io.apicurio.registry.content.refs; -import java.util.Set; - +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.rules.validity.ArtifactUtilProviderTestBase; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import io.apicurio.registry.content.ContentHandle; -import io.apicurio.registry.rules.validity.ArtifactUtilProviderTestBase; +import java.util.Set; public class AsyncApiReferenceFinderTest extends ArtifactUtilProviderTestBase { /** - * Test method for {@link io.apicurio.registry.content.refs.AsyncApiReferenceFinder#findExternalReferences(io.apicurio.registry.content.ContentHandle)}. + * Test method for {@link io.apicurio.registry.content.refs.AsyncApiReferenceFinder#findExternalReferences(TypedContent)} */ @Test public void testFindExternalReferences() { - ContentHandle content = resourceToContentHandle("asyncapi-with-refs.json"); + TypedContent content = resourceToTypedContentHandle("asyncapi-with-refs.json"); AsyncApiReferenceFinder finder = new AsyncApiReferenceFinder(); Set foundReferences = finder.findExternalReferences(content); Assertions.assertNotNull(foundReferences); diff --git a/schema-util/util-provider/src/test/java/io/apicurio/registry/content/refs/AvroReferenceFinderTest.java b/schema-util/util-provider/src/test/java/io/apicurio/registry/content/refs/AvroReferenceFinderTest.java index a1129a5e1a..9b51dd7180 100644 --- a/schema-util/util-provider/src/test/java/io/apicurio/registry/content/refs/AvroReferenceFinderTest.java +++ b/schema-util/util-provider/src/test/java/io/apicurio/registry/content/refs/AvroReferenceFinderTest.java @@ -1,12 +1,11 @@ package io.apicurio.registry.content.refs; -import java.util.Set; - +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.rules.validity.ArtifactUtilProviderTestBase; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import io.apicurio.registry.content.ContentHandle; -import io.apicurio.registry.rules.validity.ArtifactUtilProviderTestBase; +import java.util.Set; public class AvroReferenceFinderTest extends ArtifactUtilProviderTestBase { @@ -15,7 +14,7 @@ public class AvroReferenceFinderTest extends ArtifactUtilProviderTestBase { */ @Test public void testFindExternalReferences() { - ContentHandle content = resourceToContentHandle("avro-with-refs.avsc"); + TypedContent content = resourceToTypedContentHandle("avro-with-refs.avsc"); AvroReferenceFinder finder = new AvroReferenceFinder(); Set foundReferences = finder.findExternalReferences(content); Assertions.assertNotNull(foundReferences); diff --git a/schema-util/util-provider/src/test/java/io/apicurio/registry/content/refs/JsonSchemaReferenceFinderTest.java b/schema-util/util-provider/src/test/java/io/apicurio/registry/content/refs/JsonSchemaReferenceFinderTest.java index c15ee5be9a..42ca7c9437 100644 --- a/schema-util/util-provider/src/test/java/io/apicurio/registry/content/refs/JsonSchemaReferenceFinderTest.java +++ b/schema-util/util-provider/src/test/java/io/apicurio/registry/content/refs/JsonSchemaReferenceFinderTest.java @@ -1,12 +1,11 @@ package io.apicurio.registry.content.refs; -import java.util.Set; - +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.rules.validity.ArtifactUtilProviderTestBase; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import io.apicurio.registry.content.ContentHandle; -import io.apicurio.registry.rules.validity.ArtifactUtilProviderTestBase; +import java.util.Set; public class JsonSchemaReferenceFinderTest extends ArtifactUtilProviderTestBase { @@ -15,7 +14,7 @@ public class JsonSchemaReferenceFinderTest extends ArtifactUtilProviderTestBase */ @Test public void testFindExternalReferences() { - ContentHandle content = resourceToContentHandle("json-schema-with-refs.json"); + TypedContent content = resourceToTypedContentHandle("json-schema-with-refs.json"); JsonSchemaReferenceFinder finder = new JsonSchemaReferenceFinder(); Set foundReferences = finder.findExternalReferences(content); Assertions.assertNotNull(foundReferences); diff --git a/schema-util/util-provider/src/test/java/io/apicurio/registry/content/refs/OpenApiReferenceFinderTest.java b/schema-util/util-provider/src/test/java/io/apicurio/registry/content/refs/OpenApiReferenceFinderTest.java index 1a5756e5ab..c6a222af11 100644 --- a/schema-util/util-provider/src/test/java/io/apicurio/registry/content/refs/OpenApiReferenceFinderTest.java +++ b/schema-util/util-provider/src/test/java/io/apicurio/registry/content/refs/OpenApiReferenceFinderTest.java @@ -1,12 +1,11 @@ package io.apicurio.registry.content.refs; -import java.util.Set; - +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.rules.validity.ArtifactUtilProviderTestBase; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import io.apicurio.registry.content.ContentHandle; -import io.apicurio.registry.rules.validity.ArtifactUtilProviderTestBase; +import java.util.Set; public class OpenApiReferenceFinderTest extends ArtifactUtilProviderTestBase { @@ -15,7 +14,7 @@ public class OpenApiReferenceFinderTest extends ArtifactUtilProviderTestBase { */ @Test public void testFindExternalReferences() { - ContentHandle content = resourceToContentHandle("openapi-with-refs.json"); + TypedContent content = resourceToTypedContentHandle("openapi-with-refs.json"); OpenApiReferenceFinder finder = new OpenApiReferenceFinder(); Set foundReferences = finder.findExternalReferences(content); Assertions.assertNotNull(foundReferences); diff --git a/schema-util/util-provider/src/test/java/io/apicurio/registry/content/refs/ProtobufReferenceFinderTest.java b/schema-util/util-provider/src/test/java/io/apicurio/registry/content/refs/ProtobufReferenceFinderTest.java index 44b8cf5ef9..38a462ec9c 100644 --- a/schema-util/util-provider/src/test/java/io/apicurio/registry/content/refs/ProtobufReferenceFinderTest.java +++ b/schema-util/util-provider/src/test/java/io/apicurio/registry/content/refs/ProtobufReferenceFinderTest.java @@ -1,12 +1,11 @@ package io.apicurio.registry.content.refs; -import java.util.Set; - +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.rules.validity.ArtifactUtilProviderTestBase; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import io.apicurio.registry.content.ContentHandle; -import io.apicurio.registry.rules.validity.ArtifactUtilProviderTestBase; +import java.util.Set; public class ProtobufReferenceFinderTest extends ArtifactUtilProviderTestBase { @@ -15,7 +14,7 @@ public class ProtobufReferenceFinderTest extends ArtifactUtilProviderTestBase { */ @Test public void testFindExternalReferences() { - ContentHandle content = resourceToContentHandle("protobuf-with-refs.proto"); + TypedContent content = resourceToTypedContentHandle("protobuf-with-refs.proto"); ProtobufReferenceFinder finder = new ProtobufReferenceFinder(); Set foundReferences = finder.findExternalReferences(content); Assertions.assertNotNull(foundReferences); diff --git a/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/ArtifactUtilProviderTestBase.java b/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/ArtifactUtilProviderTestBase.java index 1fa98af78a..a4c79dfcb9 100644 --- a/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/ArtifactUtilProviderTestBase.java +++ b/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/ArtifactUtilProviderTestBase.java @@ -1,5 +1,10 @@ package io.apicurio.registry.rules.validity; +import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.types.ContentTypes; +import org.junit.jupiter.api.Assertions; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -7,10 +12,6 @@ import java.nio.charset.StandardCharsets; import java.util.stream.Collectors; -import org.junit.jupiter.api.Assertions; - -import io.apicurio.registry.content.ContentHandle; - public class ArtifactUtilProviderTestBase { protected final String resourceToString(String resourceName) { @@ -27,4 +28,21 @@ protected final ContentHandle resourceToContentHandle(String resourceName) { return ContentHandle.create(resourceToString(resourceName)); } + protected final TypedContent resourceToTypedContentHandle(String resourceName) { + String ct = ContentTypes.APPLICATION_JSON; + if (resourceName.toLowerCase().endsWith("yaml") || resourceName.toLowerCase().endsWith("yml")) { + ct = ContentTypes.APPLICATION_YAML; + } + if (resourceName.toLowerCase().endsWith("xml") || resourceName.toLowerCase().endsWith("wsdl") || resourceName.toLowerCase().endsWith("xsd") ) { + ct = ContentTypes.APPLICATION_XML; + } + if (resourceName.toLowerCase().endsWith("proto")) { + ct = ContentTypes.APPLICATION_PROTOBUF; + } + if (resourceName.toLowerCase().endsWith("graphql")) { + ct = ContentTypes.APPLICATION_GRAPHQL; + } + return TypedContent.create(resourceToContentHandle(resourceName), ct); + } + } diff --git a/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/AsyncApiContentValidatorTest.java b/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/AsyncApiContentValidatorTest.java index e719e3e3ce..581b8100ae 100644 --- a/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/AsyncApiContentValidatorTest.java +++ b/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/AsyncApiContentValidatorTest.java @@ -1,8 +1,7 @@ package io.apicurio.registry.rules.validity; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rules.RuleViolationException; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -15,28 +14,28 @@ public class AsyncApiContentValidatorTest extends ArtifactUtilProviderTestBase { @Test public void testValidSyntax() throws Exception { - ContentHandle content = resourceToContentHandle("asyncapi-valid-syntax.json"); + TypedContent content = resourceToTypedContentHandle("asyncapi-valid-syntax.json"); AsyncApiContentValidator validator = new AsyncApiContentValidator(); validator.validate(ValidityLevel.SYNTAX_ONLY, content, Collections.emptyMap()); } @Test public void testValidSyntax_AsyncApi25() throws Exception { - ContentHandle content = resourceToContentHandle("asyncapi-valid-syntax-asyncapi25.json"); + TypedContent content = resourceToTypedContentHandle("asyncapi-valid-syntax-asyncapi25.json"); AsyncApiContentValidator validator = new AsyncApiContentValidator(); validator.validate(ValidityLevel.SYNTAX_ONLY, content, Collections.emptyMap()); } @Test public void testValidSemantics() throws Exception { - ContentHandle content = resourceToContentHandle("asyncapi-valid-semantics.json"); + TypedContent content = resourceToTypedContentHandle("asyncapi-valid-semantics.json"); AsyncApiContentValidator validator = new AsyncApiContentValidator(); validator.validate(ValidityLevel.FULL, content, Collections.emptyMap()); } @Test public void testInvalidSyntax() throws Exception { - ContentHandle content = resourceToContentHandle("asyncapi-invalid-syntax.json"); + TypedContent content = resourceToTypedContentHandle("asyncapi-invalid-syntax.json"); AsyncApiContentValidator validator = new AsyncApiContentValidator(); Assertions.assertThrows(RuleViolationException.class, () -> { validator.validate(ValidityLevel.SYNTAX_ONLY, content, Collections.emptyMap()); @@ -45,7 +44,7 @@ public void testInvalidSyntax() throws Exception { @Test public void testInvalidSemantics() throws Exception { - ContentHandle content = resourceToContentHandle("asyncapi-invalid-semantics.json"); + TypedContent content = resourceToTypedContentHandle("asyncapi-invalid-semantics.json"); AsyncApiContentValidator validator = new AsyncApiContentValidator(); Assertions.assertThrows(RuleViolationException.class, () -> { validator.validate(ValidityLevel.FULL, content, Collections.emptyMap()); diff --git a/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/AvroContentValidatorTest.java b/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/AvroContentValidatorTest.java index 32a11c7444..16df91ce22 100644 --- a/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/AvroContentValidatorTest.java +++ b/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/AvroContentValidatorTest.java @@ -1,9 +1,8 @@ package io.apicurio.registry.rules.validity; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rest.v3.beans.ArtifactReference; import io.apicurio.registry.rules.RuleViolationException; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -18,14 +17,14 @@ public class AvroContentValidatorTest extends ArtifactUtilProviderTestBase { @Test public void testValidAvroSchema() throws Exception { - ContentHandle content = resourceToContentHandle("avro-valid.json"); + TypedContent content = resourceToTypedContentHandle("avro-valid.json"); AvroContentValidator validator = new AvroContentValidator(); validator.validate(ValidityLevel.SYNTAX_ONLY, content, Collections.emptyMap()); } @Test public void testInvalidAvroSchema() throws Exception { - ContentHandle content = resourceToContentHandle("avro-invalid.json"); + TypedContent content = resourceToTypedContentHandle("avro-invalid.json"); AvroContentValidator validator = new AvroContentValidator(); Assertions.assertThrows(RuleViolationException.class, () -> { validator.validate(ValidityLevel.SYNTAX_ONLY, content, Collections.emptyMap()); @@ -34,7 +33,7 @@ public void testInvalidAvroSchema() throws Exception { @Test public void testValidateReferences() throws Exception { - ContentHandle content = resourceToContentHandle("avro-valid-with-refs.json"); + TypedContent content = resourceToTypedContentHandle("avro-valid-with-refs.json"); AvroContentValidator validator = new AvroContentValidator(); // Properly map both required references - success. diff --git a/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/GraphQLContentValidatorTest.java b/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/GraphQLContentValidatorTest.java index bbaf2ef41d..9c8b32537b 100644 --- a/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/GraphQLContentValidatorTest.java +++ b/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/GraphQLContentValidatorTest.java @@ -1,11 +1,10 @@ package io.apicurio.registry.rules.validity; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.rules.RuleViolationException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import io.apicurio.registry.content.ContentHandle; -import io.apicurio.registry.rules.RuleViolationException; - import java.util.Collections; /** @@ -15,14 +14,14 @@ public class GraphQLContentValidatorTest extends ArtifactUtilProviderTestBase { @Test public void testValidSyntax() throws Exception { - ContentHandle content = resourceToContentHandle("graphql-valid.graphql"); + TypedContent content = resourceToTypedContentHandle("graphql-valid.graphql"); GraphQLContentValidator validator = new GraphQLContentValidator(); validator.validate(ValidityLevel.SYNTAX_ONLY, content, Collections.emptyMap()); } @Test public void testInvalidSyntax() throws Exception { - ContentHandle content = resourceToContentHandle("graphql-invalid.graphql"); + TypedContent content = resourceToTypedContentHandle("graphql-invalid.graphql"); GraphQLContentValidator validator = new GraphQLContentValidator(); Assertions.assertThrows(RuleViolationException.class, () -> { validator.validate(ValidityLevel.SYNTAX_ONLY, content, Collections.emptyMap()); diff --git a/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/JsonSchemaContentValidatorTest.java b/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/JsonSchemaContentValidatorTest.java index acb876baaa..130dbfb3b4 100644 --- a/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/JsonSchemaContentValidatorTest.java +++ b/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/JsonSchemaContentValidatorTest.java @@ -1,11 +1,10 @@ package io.apicurio.registry.rules.validity; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.rules.RuleViolationException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import io.apicurio.registry.content.ContentHandle; -import io.apicurio.registry.rules.RuleViolationException; - import java.util.Collections; /** @@ -15,14 +14,14 @@ public class JsonSchemaContentValidatorTest extends ArtifactUtilProviderTestBase @Test public void testValidJsonSchema() throws Exception { - ContentHandle content = resourceToContentHandle("jsonschema-valid.json"); + TypedContent content = resourceToTypedContentHandle("jsonschema-valid.json"); JsonSchemaContentValidator validator = new JsonSchemaContentValidator(); validator.validate(ValidityLevel.SYNTAX_ONLY, content, Collections.emptyMap()); } @Test public void testInvalidJsonSchema() throws Exception { - ContentHandle content = resourceToContentHandle("jsonschema-invalid.json"); + TypedContent content = resourceToTypedContentHandle("jsonschema-invalid.json"); JsonSchemaContentValidator validator = new JsonSchemaContentValidator(); Assertions.assertThrows(RuleViolationException.class, () -> { validator.validate(ValidityLevel.SYNTAX_ONLY, content, Collections.emptyMap()); @@ -31,14 +30,14 @@ public void testInvalidJsonSchema() throws Exception { @Test public void testInvalidJsonSchemaVersion() throws Exception { - ContentHandle content = resourceToContentHandle("jsonschema-valid-d7.json"); + TypedContent content = resourceToTypedContentHandle("jsonschema-valid-d7.json"); JsonSchemaContentValidator validator = new JsonSchemaContentValidator(); validator.validate(ValidityLevel.FULL, content, Collections.emptyMap()); } @Test public void testInvalidJsonSchemaFull() throws Exception { - ContentHandle content = resourceToContentHandle("bad-json-schema-v1.json"); + TypedContent content = resourceToTypedContentHandle("bad-json-schema-v1.json"); JsonSchemaContentValidator validator = new JsonSchemaContentValidator(); RuleViolationException error = Assertions.assertThrows(RuleViolationException.class, () -> { validator.validate(ValidityLevel.FULL, content, Collections.emptyMap()); @@ -50,8 +49,8 @@ public void testInvalidJsonSchemaFull() throws Exception { @Test public void testJsonSchemaWithReferences() throws Exception { - ContentHandle city = resourceToContentHandle("city.json"); - ContentHandle citizen = resourceToContentHandle("citizen.json"); + TypedContent city = resourceToTypedContentHandle("city.json"); + TypedContent citizen = resourceToTypedContentHandle("citizen.json"); JsonSchemaContentValidator validator = new JsonSchemaContentValidator(); validator.validate(ValidityLevel.FULL, citizen, Collections.singletonMap("https://example.com/city.json", city)); } diff --git a/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/KafkaConnectContentValidatorTest.java b/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/KafkaConnectContentValidatorTest.java index 2baba725cd..718096288e 100644 --- a/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/KafkaConnectContentValidatorTest.java +++ b/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/KafkaConnectContentValidatorTest.java @@ -1,11 +1,10 @@ package io.apicurio.registry.rules.validity; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.rules.RuleViolationException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import io.apicurio.registry.content.ContentHandle; -import io.apicurio.registry.rules.RuleViolationException; - import java.util.Collections; /** @@ -15,14 +14,14 @@ public class KafkaConnectContentValidatorTest extends ArtifactUtilProviderTestBa @Test public void testValidSyntax() throws Exception { - ContentHandle content = resourceToContentHandle("kconnect-valid.json"); + TypedContent content = resourceToTypedContentHandle("kconnect-valid.json"); KafkaConnectContentValidator validator = new KafkaConnectContentValidator(); validator.validate(ValidityLevel.SYNTAX_ONLY, content, Collections.emptyMap()); } @Test public void testInvalidSyntax() throws Exception { - ContentHandle content = resourceToContentHandle("kconnect-invalid.json"); + TypedContent content = resourceToTypedContentHandle("kconnect-invalid.json"); KafkaConnectContentValidator validator = new KafkaConnectContentValidator(); Assertions.assertThrows(RuleViolationException.class, () -> { validator.validate(ValidityLevel.SYNTAX_ONLY, content, Collections.emptyMap()); diff --git a/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/OpenApiContentValidatorTest.java b/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/OpenApiContentValidatorTest.java index b800ff1de0..a77a7a8ee4 100644 --- a/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/OpenApiContentValidatorTest.java +++ b/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/OpenApiContentValidatorTest.java @@ -1,15 +1,14 @@ package io.apicurio.registry.rules.validity; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.rest.v3.beans.ArtifactReference; +import io.apicurio.registry.rules.RuleViolationException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import io.apicurio.registry.content.ContentHandle; -import io.apicurio.registry.rest.v3.beans.ArtifactReference; -import io.apicurio.registry.rules.RuleViolationException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; /** * Tests the OpenAPI content validator. @@ -18,7 +17,7 @@ public class OpenApiContentValidatorTest extends ArtifactUtilProviderTestBase { @Test public void testValidSyntax() throws Exception { - ContentHandle content = resourceToContentHandle("openapi-valid-syntax.json"); + TypedContent content = resourceToTypedContentHandle("openapi-valid-syntax.json"); OpenApiContentValidator validator = new OpenApiContentValidator(); validator.validate(ValidityLevel.SYNTAX_ONLY, content, Collections.emptyMap()); } @@ -26,21 +25,21 @@ public void testValidSyntax() throws Exception { @Test public void testValidSyntax_OpenApi31() throws Exception { - ContentHandle content = resourceToContentHandle("openapi-valid-syntax-openapi31.json"); + TypedContent content = resourceToTypedContentHandle("openapi-valid-syntax-openapi31.json"); OpenApiContentValidator validator = new OpenApiContentValidator(); validator.validate(ValidityLevel.SYNTAX_ONLY, content, Collections.emptyMap()); } @Test public void testValidSemantics() throws Exception { - ContentHandle content = resourceToContentHandle("openapi-valid-semantics.json"); + TypedContent content = resourceToTypedContentHandle("openapi-valid-semantics.json"); OpenApiContentValidator validator = new OpenApiContentValidator(); validator.validate(ValidityLevel.FULL, content, Collections.emptyMap()); } @Test public void testInvalidSyntax() throws Exception { - ContentHandle content = resourceToContentHandle("openapi-invalid-syntax.json"); + TypedContent content = resourceToTypedContentHandle("openapi-invalid-syntax.json"); OpenApiContentValidator validator = new OpenApiContentValidator(); Assertions.assertThrows(RuleViolationException.class, () -> { validator.validate(ValidityLevel.SYNTAX_ONLY, content, Collections.emptyMap()); @@ -49,7 +48,7 @@ public void testInvalidSyntax() throws Exception { @Test public void testInvalidSemantics() throws Exception { - ContentHandle content = resourceToContentHandle("openapi-invalid-semantics.json"); + TypedContent content = resourceToTypedContentHandle("openapi-invalid-semantics.json"); OpenApiContentValidator validator = new OpenApiContentValidator(); Assertions.assertThrows(RuleViolationException.class, () -> { validator.validate(ValidityLevel.FULL, content, Collections.emptyMap()); @@ -58,7 +57,7 @@ public void testInvalidSemantics() throws Exception { @Test public void testValidateRefs() throws Exception { - ContentHandle content = resourceToContentHandle("openapi-valid-with-refs.json"); + TypedContent content = resourceToTypedContentHandle("openapi-valid-with-refs.json"); OpenApiContentValidator validator = new OpenApiContentValidator(); validator.validate(ValidityLevel.SYNTAX_ONLY, content, Collections.emptyMap()); diff --git a/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/ProtobufContentValidatorTest.java b/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/ProtobufContentValidatorTest.java index 66ea8d1b08..b84b7df81a 100644 --- a/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/ProtobufContentValidatorTest.java +++ b/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/ProtobufContentValidatorTest.java @@ -1,11 +1,10 @@ package io.apicurio.registry.rules.validity; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rest.v3.beans.ArtifactReference; import io.apicurio.registry.rules.RuleViolationException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.Collections; @@ -18,14 +17,14 @@ public class ProtobufContentValidatorTest extends ArtifactUtilProviderTestBase { @Test public void testValidProtobufSchema() throws Exception { - ContentHandle content = resourceToContentHandle("protobuf-valid.proto"); + TypedContent content = resourceToTypedContentHandle("protobuf-valid.proto"); ProtobufContentValidator validator = new ProtobufContentValidator(); validator.validate(ValidityLevel.SYNTAX_ONLY, content, Collections.emptyMap()); } @Test public void testInvalidProtobufSchema() throws Exception { - ContentHandle content = resourceToContentHandle("protobuf-invalid.proto"); + TypedContent content = resourceToTypedContentHandle("protobuf-invalid.proto"); ProtobufContentValidator validator = new ProtobufContentValidator(); Assertions.assertThrows(RuleViolationException.class, () -> { validator.validate(ValidityLevel.SYNTAX_ONLY, content, Collections.emptyMap()); @@ -34,15 +33,15 @@ public void testInvalidProtobufSchema() throws Exception { @Test public void testValidateProtobufWithImports() throws Exception { - ContentHandle mode = resourceToContentHandle("mode.proto"); - ContentHandle tableInfo = resourceToContentHandle("table_info.proto"); + TypedContent mode = resourceToTypedContentHandle("mode.proto"); + TypedContent tableInfo = resourceToTypedContentHandle("table_info.proto"); ProtobufContentValidator validator = new ProtobufContentValidator(); validator.validate(ValidityLevel.SYNTAX_ONLY, tableInfo, Collections.singletonMap("mode.proto", mode)); } @Test public void testValidateReferences() throws Exception { - ContentHandle content = resourceToContentHandle("protobuf-valid-with-refs.proto"); + TypedContent content = resourceToTypedContentHandle("protobuf-valid-with-refs.proto"); ProtobufContentValidator validator = new ProtobufContentValidator(); // Properly map both required references - success. diff --git a/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/WsdlContentValidatorTest.java b/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/WsdlContentValidatorTest.java index 7411c93207..5bbb70a91a 100644 --- a/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/WsdlContentValidatorTest.java +++ b/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/WsdlContentValidatorTest.java @@ -1,26 +1,25 @@ package io.apicurio.registry.rules.validity; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.rules.RuleViolationException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import io.apicurio.registry.content.ContentHandle; -import io.apicurio.registry.rules.RuleViolationException; - import java.util.Collections; public class WsdlContentValidatorTest extends ArtifactUtilProviderTestBase { @Test public void testValidSyntax() throws Exception { - ContentHandle contentA = resourceToContentHandle("wsdl-valid.wsdl"); + TypedContent contentA = resourceToTypedContentHandle("wsdl-valid.wsdl"); WsdlContentValidator validator = new WsdlContentValidator(); validator.validate(ValidityLevel.SYNTAX_ONLY, contentA, Collections.emptyMap()); - ContentHandle contentB = resourceToContentHandle("wsdl-invalid-semantics.wsdl"); + TypedContent contentB = resourceToTypedContentHandle("wsdl-invalid-semantics.wsdl"); validator.validate(ValidityLevel.SYNTAX_ONLY, contentB, Collections.emptyMap()); } @Test public void testinValidSyntax() throws Exception { - ContentHandle content = resourceToContentHandle("wsdl-invalid-syntax.wsdl"); + TypedContent content = resourceToTypedContentHandle("wsdl-invalid-syntax.wsdl"); WsdlContentValidator validator = new WsdlContentValidator(); Assertions.assertThrows(RuleViolationException.class, () -> { validator.validate(ValidityLevel.SYNTAX_ONLY, content, Collections.emptyMap()); @@ -29,14 +28,14 @@ public void testinValidSyntax() throws Exception { @Test public void testValidSemantics() throws Exception { - ContentHandle content = resourceToContentHandle("wsdl-valid.wsdl"); + TypedContent content = resourceToTypedContentHandle("wsdl-valid.wsdl"); WsdlContentValidator validator = new WsdlContentValidator(); validator.validate(ValidityLevel.FULL, content, Collections.emptyMap()); } @Test public void testinValidSemantics() throws Exception { - ContentHandle content = resourceToContentHandle("wsdl-invalid-semantics.wsdl"); + TypedContent content = resourceToTypedContentHandle("wsdl-invalid-semantics.wsdl"); WsdlContentValidator validator = new WsdlContentValidator(); Assertions.assertThrows(RuleViolationException.class, () -> { //WSDLException faultCode=INVALID_WSDL: Encountered illegal extension element '{http://schemas.xmlsoap.org/wsdl/}element' in the context of a 'javax.wsdl.Types'. Extension elements must be in a namespace other than WSDL's diff --git a/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/XmlContentValidatorTest.java b/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/XmlContentValidatorTest.java index 8af0152fce..48afa8a147 100644 --- a/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/XmlContentValidatorTest.java +++ b/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/XmlContentValidatorTest.java @@ -1,24 +1,23 @@ package io.apicurio.registry.rules.validity; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.rules.RuleViolationException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import io.apicurio.registry.content.ContentHandle; -import io.apicurio.registry.rules.RuleViolationException; - import java.util.Collections; public class XmlContentValidatorTest extends ArtifactUtilProviderTestBase { @Test public void testValidSyntax() throws Exception { - ContentHandle content = resourceToContentHandle("xml-valid.xml"); + TypedContent content = resourceToTypedContentHandle("xml-valid.xml"); XsdContentValidator validator = new XsdContentValidator(); validator.validate(ValidityLevel.SYNTAX_ONLY, content, Collections.emptyMap()); } @Test public void testInvalidSyntax() throws Exception { - ContentHandle content = resourceToContentHandle("xml-invalid-syntax.xml"); + TypedContent content = resourceToTypedContentHandle("xml-invalid-syntax.xml"); XsdContentValidator validator = new XsdContentValidator(); Assertions.assertThrows(RuleViolationException.class, () -> { validator.validate(ValidityLevel.SYNTAX_ONLY, content, Collections.emptyMap()); diff --git a/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/XsdContentValidatorTest.java b/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/XsdContentValidatorTest.java index 1ccfb6215f..548499fbf0 100644 --- a/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/XsdContentValidatorTest.java +++ b/schema-util/util-provider/src/test/java/io/apicurio/registry/rules/validity/XsdContentValidatorTest.java @@ -1,26 +1,25 @@ package io.apicurio.registry.rules.validity; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.rules.RuleViolationException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import io.apicurio.registry.content.ContentHandle; -import io.apicurio.registry.rules.RuleViolationException; - import java.util.Collections; public class XsdContentValidatorTest extends ArtifactUtilProviderTestBase { @Test public void testValidSyntax() throws Exception { - ContentHandle contentA = resourceToContentHandle("xml-schema-valid.xsd"); + TypedContent contentA = resourceToTypedContentHandle("xml-schema-valid.xsd"); XsdContentValidator validator = new XsdContentValidator(); validator.validate(ValidityLevel.SYNTAX_ONLY, contentA, Collections.emptyMap()); - ContentHandle contentB = resourceToContentHandle("xml-schema-invalid-semantics.xsd"); + TypedContent contentB = resourceToTypedContentHandle("xml-schema-invalid-semantics.xsd"); validator.validate(ValidityLevel.SYNTAX_ONLY, contentB, Collections.emptyMap()); } @Test public void testInvalidSyntax() throws Exception { - ContentHandle content = resourceToContentHandle("xml-schema-invalid-syntax.xsd"); + TypedContent content = resourceToTypedContentHandle("xml-schema-invalid-syntax.xsd"); XsdContentValidator validator = new XsdContentValidator(); Assertions.assertThrows(RuleViolationException.class, () -> { validator.validate(ValidityLevel.SYNTAX_ONLY, content, Collections.emptyMap()); @@ -29,14 +28,14 @@ public void testInvalidSyntax() throws Exception { @Test public void testValidSemantics() throws Exception { - ContentHandle content = resourceToContentHandle("xml-schema-valid.xsd"); + TypedContent content = resourceToTypedContentHandle("xml-schema-valid.xsd"); XsdContentValidator validator = new XsdContentValidator(); validator.validate(ValidityLevel.FULL, content, Collections.emptyMap()); } @Test public void testInvalidSemantics() throws Exception { - ContentHandle content = resourceToContentHandle("xml-schema-invalid-semantics.xsd"); + TypedContent content = resourceToTypedContentHandle("xml-schema-invalid-semantics.xsd"); XsdContentValidator validator = new XsdContentValidator(); Assertions.assertThrows(RuleViolationException.class, () -> { validator.validate(ValidityLevel.FULL, content, Collections.emptyMap()); diff --git a/schema-util/wsdl/src/main/java/io/apicurio/registry/rules/validity/WsdlContentValidator.java b/schema-util/wsdl/src/main/java/io/apicurio/registry/rules/validity/WsdlContentValidator.java index bcbf6bc167..fae28ed53c 100644 --- a/schema-util/wsdl/src/main/java/io/apicurio/registry/rules/validity/WsdlContentValidator.java +++ b/schema-util/wsdl/src/main/java/io/apicurio/registry/rules/validity/WsdlContentValidator.java @@ -1,15 +1,14 @@ package io.apicurio.registry.rules.validity; -import java.io.InputStream; -import java.util.Map; - -import org.w3c.dom.Document; - -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rules.RuleViolationException; import io.apicurio.registry.types.RuleType; import io.apicurio.registry.util.DocumentBuilderAccessor; import io.apicurio.registry.util.WSDLReaderAccessor; +import org.w3c.dom.Document; + +import java.io.InputStream; +import java.util.Map; public class WsdlContentValidator extends XmlContentValidator { @@ -20,12 +19,12 @@ public WsdlContentValidator() { } /** - * @see io.apicurio.registry.rules.validity.ContentValidator#validate(ValidityLevel, ContentHandle, Map) + * @see io.apicurio.registry.rules.validity.ContentValidator#validate(ValidityLevel, TypedContent, Map) */ @Override - public void validate(ValidityLevel level, ContentHandle artifactContent, Map resolvedReferences) throws RuleViolationException { + public void validate(ValidityLevel level, TypedContent content, Map resolvedReferences) throws RuleViolationException { if (level == ValidityLevel.SYNTAX_ONLY || level == ValidityLevel.FULL) { - try (InputStream stream = artifactContent.stream()) { + try (InputStream stream = content.getContent().stream()) { Document wsdlDoc = DocumentBuilderAccessor.getDocumentBuilder().parse(stream); if (level == ValidityLevel.FULL) { // validate that its a valid schema diff --git a/schema-util/xml/src/main/java/io/apicurio/registry/content/canon/XmlContentCanonicalizer.java b/schema-util/xml/src/main/java/io/apicurio/registry/content/canon/XmlContentCanonicalizer.java index c5828f3c81..7f70dbcd91 100644 --- a/schema-util/xml/src/main/java/io/apicurio/registry/content/canon/XmlContentCanonicalizer.java +++ b/schema-util/xml/src/main/java/io/apicurio/registry/content/canon/XmlContentCanonicalizer.java @@ -1,6 +1,8 @@ package io.apicurio.registry.content.canon; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.types.ContentTypes; import org.apache.xml.security.Init; import org.apache.xml.security.c14n.CanonicalizationException; import org.apache.xml.security.c14n.Canonicalizer; @@ -32,16 +34,16 @@ public class XmlContentCanonicalizer implements ContentCanonicalizer { } /** - * @see ContentCanonicalizer#canonicalize(io.apicurio.registry.content.ContentHandle, Map) + * @see ContentCanonicalizer#canonicalize(TypedContent, Map) */ - @Override public ContentHandle canonicalize(ContentHandle content, - Map resolvedReferences) { + @Override public TypedContent canonicalize(TypedContent content, + Map resolvedReferences) { try { Canonicalizer canon = xmlCanonicalizer.get(); - var out = new ByteArrayOutputStream(content.getSizeBytes()); - canon.canonicalize(content.bytes(), out, false); // TODO secureValidation? + var out = new ByteArrayOutputStream(content.getContent().getSizeBytes()); + canon.canonicalize(content.getContent().bytes(), out, false); // TODO secureValidation? var canonicalized = out.toString(Canonicalizer.ENCODING); - return ContentHandle.create(canonicalized); + return TypedContent.create(ContentHandle.create(canonicalized), ContentTypes.APPLICATION_XML); } catch (CanonicalizationException | IOException | XMLParserException e) { } return content; diff --git a/schema-util/xml/src/main/java/io/apicurio/registry/rules/validity/XmlContentValidator.java b/schema-util/xml/src/main/java/io/apicurio/registry/rules/validity/XmlContentValidator.java index 9acc03ecf6..5db7abff35 100644 --- a/schema-util/xml/src/main/java/io/apicurio/registry/rules/validity/XmlContentValidator.java +++ b/schema-util/xml/src/main/java/io/apicurio/registry/rules/validity/XmlContentValidator.java @@ -1,15 +1,15 @@ package io.apicurio.registry.rules.validity; -import java.io.InputStream; -import java.util.List; -import java.util.Map; - -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rest.v3.beans.ArtifactReference; import io.apicurio.registry.rules.RuleViolationException; import io.apicurio.registry.types.RuleType; import io.apicurio.registry.util.DocumentBuilderAccessor; +import java.io.InputStream; +import java.util.List; +import java.util.Map; + public class XmlContentValidator implements ContentValidator { /** @@ -19,12 +19,12 @@ public XmlContentValidator() { } /** - * @see io.apicurio.registry.rules.validity.ContentValidator#validate(ValidityLevel, ContentHandle, java.util.Map) + * @see io.apicurio.registry.rules.validity.ContentValidator#validate(ValidityLevel, TypedContent, Map) */ @Override - public void validate(ValidityLevel level, ContentHandle artifactContent, Map resolvedReferences) throws RuleViolationException { + public void validate(ValidityLevel level, TypedContent content, Map resolvedReferences) throws RuleViolationException { if (level == ValidityLevel.SYNTAX_ONLY || level == ValidityLevel.FULL) { - try (InputStream stream = artifactContent.stream()) { + try (InputStream stream = content.getContent().stream()) { DocumentBuilderAccessor.getDocumentBuilder().parse(stream); } catch (Exception e) { throw new RuleViolationException("Syntax violation for XML artifact.", RuleType.VALIDITY, level.name(), e); @@ -33,10 +33,10 @@ public void validate(ValidityLevel level, ContentHandle artifactContent, Map references) throws RuleViolationException { + public void validateReferences(TypedContent content, List references) throws RuleViolationException { // Note: not yet implemented! } diff --git a/schema-util/xsd/src/main/java/io/apicurio/registry/rules/validity/XsdContentValidator.java b/schema-util/xsd/src/main/java/io/apicurio/registry/rules/validity/XsdContentValidator.java index 7ff08c8df1..4fcdf2e6d3 100644 --- a/schema-util/xsd/src/main/java/io/apicurio/registry/rules/validity/XsdContentValidator.java +++ b/schema-util/xsd/src/main/java/io/apicurio/registry/rules/validity/XsdContentValidator.java @@ -1,16 +1,15 @@ package io.apicurio.registry.rules.validity; -import java.io.InputStream; -import java.util.Map; - -import javax.xml.transform.Source; -import javax.xml.transform.stream.StreamSource; - -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rules.RuleViolationException; import io.apicurio.registry.types.RuleType; import io.apicurio.registry.util.SchemaFactoryAccessor; +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; +import java.io.InputStream; +import java.util.Map; + public class XsdContentValidator extends XmlContentValidator { /** @@ -19,14 +18,14 @@ public class XsdContentValidator extends XmlContentValidator { public XsdContentValidator() { } /** - * @see io.apicurio.registry.rules.validity.ContentValidator#validate(ValidityLevel, ContentHandle, Map) + * @see io.apicurio.registry.rules.validity.ContentValidator#validate(ValidityLevel, TypedContent, Map) */ @Override - public void validate(ValidityLevel level, ContentHandle artifactContent, Map resolvedReferences) throws RuleViolationException { - super.validate(level, artifactContent, resolvedReferences); + public void validate(ValidityLevel level, TypedContent content, Map resolvedReferences) throws RuleViolationException { + super.validate(level, content, resolvedReferences); if (level == ValidityLevel.FULL) { - try (InputStream semanticStream = artifactContent.stream()) { + try (InputStream semanticStream = content.getContent().stream()) { // validate that its a valid schema Source source = new StreamSource(semanticStream); SchemaFactoryAccessor.getSchemaFactory().newSchema(source); diff --git a/ui/ui-app/src/app/pages/version/components/tabs/ContentTabContent.tsx b/ui/ui-app/src/app/pages/version/components/tabs/ContentTabContent.tsx index f2c8e95526..3379acb68a 100644 --- a/ui/ui-app/src/app/pages/version/components/tabs/ContentTabContent.tsx +++ b/ui/ui-app/src/app/pages/version/components/tabs/ContentTabContent.tsx @@ -4,32 +4,33 @@ import { ToggleGroup, ToggleGroupItem } from "@patternfly/react-core"; import YAML from "yaml"; import useResizeObserver from "use-resize-observer"; import Editor from "@monaco-editor/react"; +import { detectContentType } from "@utils/content.utils.ts"; +import { ContentTypes } from "@models/contentTypes.model.ts"; +const TYPE_MAP: any = {}; +TYPE_MAP[ContentTypes.APPLICATION_PROTOBUF] = "protobuf"; +TYPE_MAP[ContentTypes.APPLICATION_XML] = "xml"; +TYPE_MAP[ContentTypes.APPLICATION_JSON] = "json"; +TYPE_MAP[ContentTypes.APPLICATION_YAML] = "yaml"; +TYPE_MAP[ContentTypes.APPLICATION_GRAPHQL] = "graphqlschema"; -const getEditorMode = (artifactType: string): string => { - if (artifactType === "PROTOBUF") { - return "protobuf"; - } - if (artifactType === "WSDL" || artifactType === "XSD" || artifactType === "XML") { - return "xml"; - } - if (artifactType === "GRAPHQL") { - return "graphqlschema"; - } - return "json"; -}; -const formatContent = (artifactContent: string): string => { - try { - const pval: any = JSON.parse(artifactContent); - if (pval) { - return JSON.stringify(pval, null, 2); - } - } catch (e) { - // Do nothing - } - return artifactContent; +const getEditorMode = (artifactType: string, content: string): string => { + const ct: string = detectContentType(artifactType, content); + return TYPE_MAP[ct]; }; +// +// const formatContent = (artifactContent: string): string => { +// try { +// const pval: any = JSON.parse(artifactContent); +// if (pval) { +// return JSON.stringify(pval, null, 2); +// } +// } catch (e) { +// // Do nothing +// } +// return artifactContent; +// }; /** @@ -45,8 +46,8 @@ export type ContentTabContentProps = { * Models the content of the Artifact Content tab. */ export const ContentTabContent: FunctionComponent = (props: ContentTabContentProps) => { - const [content, setContent] = useState(formatContent(props.versionContent)); - const [editorMode, setEditorMode] = useState(getEditorMode(props.artifactType)); + const [content, setContent] = useState(props.versionContent); + const [editorMode, setEditorMode] = useState(getEditorMode(props.artifactType, props.versionContent)); const [compactButtons, setCompactButtons] = useState(false); const { ref, width = 0, height = 0 } = useResizeObserver(); @@ -55,25 +56,24 @@ export const ContentTabContent: FunctionComponent = (pro setCompactButtons(width < 500); }, [width, height]); - const switchJsonYaml = (mode: string): (() => void) => { - return () => { - if (mode === editorMode) { - return; - } else { - let content: string = `Error formatting code to: ${mode}`; - try { - if (mode === "yaml") { - content = YAML.stringify(JSON.parse(props.versionContent), null, 4); - } else { - content = JSON.stringify(YAML.parse(props.versionContent), null, 2); - } - } catch (e) { - handleInvalidContentError(e); + const switchJsonYaml = (mode: string): void => { + console.info("SWITCHING TO: ", mode); + if (mode === editorMode) { + return; + } else { + let content: string = `Error formatting code to: ${mode}`; + try { + if (mode === "yaml") { + content = YAML.stringify(JSON.parse(content), null, 4); + } else { + content = JSON.stringify(YAML.parse(content), null, 2); } - setEditorMode(mode); - setContent(content); + } catch (e) { + handleInvalidContentError(e); } - }; + setEditorMode(mode); + setContent(content); + } }; const handleInvalidContentError = (error: any): void => { @@ -88,14 +88,14 @@ export const ContentTabContent: FunctionComponent = (pro text="JSON" buttonId="json" isSelected={editorMode === "json"} - onChange={switchJsonYaml("json")} + onChange={() => switchJsonYaml("json")} isDisabled={editorMode === "json"} /> switchJsonYaml("yaml")} isDisabled={editorMode === "yaml"} /> diff --git a/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/AbstractDirectoryParser.java b/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/AbstractDirectoryParser.java index 5734ec21d5..6822100cb2 100644 --- a/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/AbstractDirectoryParser.java +++ b/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/AbstractDirectoryParser.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rest.client.RegistryClient; import io.apicurio.registry.rest.client.models.ArtifactReference; import io.apicurio.registry.rest.client.models.CreateArtifact; @@ -38,7 +39,7 @@ public AbstractDirectoryParser(RegistryClient client) { public abstract ParsedDirectoryWrapper parse(File rootSchema); - public abstract List handleSchemaReferences(RegisterArtifact rootArtifact, Schema schema, Map fileContents) throws FileNotFoundException, ExecutionException, InterruptedException; + public abstract List handleSchemaReferences(RegisterArtifact rootArtifact, Schema schema, Map fileContents) throws FileNotFoundException, ExecutionException, InterruptedException; protected ContentHandle readSchemaContent(File schemaFile) { try { diff --git a/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/AvroDirectoryParser.java b/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/AvroDirectoryParser.java index a6a1d6ea22..c161122d22 100644 --- a/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/AvroDirectoryParser.java +++ b/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/AvroDirectoryParser.java @@ -2,8 +2,10 @@ import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rest.client.RegistryClient; import io.apicurio.registry.rest.client.models.ArtifactReference; +import io.apicurio.registry.types.ContentTypes; import org.apache.avro.Schema; import org.apache.avro.SchemaParseException; import org.slf4j.Logger; @@ -37,7 +39,7 @@ public ParsedDirectoryWrapper parse(File rootSchemaFile) { } @Override - public List handleSchemaReferences(RegisterArtifact rootArtifact, Schema rootSchema, Map fileContents) throws FileNotFoundException, ExecutionException, InterruptedException { + public List handleSchemaReferences(RegisterArtifact rootArtifact, Schema rootSchema, Map fileContents) throws FileNotFoundException, ExecutionException, InterruptedException { Set references = new HashSet<>(); @@ -52,11 +54,11 @@ public List handleSchemaReferences(RegisterArtifact rootArtif nestedArtifactReferences = handleSchemaReferences(nestedSchema, field.schema(), fileContents); } - references.add(registerNestedSchema(field.schema().getFullName(), nestedArtifactReferences, nestedSchema, fileContents.get(field.schema().getFullName()).content())); + references.add(registerNestedSchema(field.schema().getFullName(), nestedArtifactReferences, nestedSchema, fileContents.get(field.schema().getFullName()).getContent().content())); } else if (field.schema().getType() == Schema.Type.ENUM) { //If the nested schema is an enum, just register RegisterArtifact nestedSchema = buildFromRoot(rootArtifact, field.schema().getFullName()); - references.add(registerNestedSchema(field.schema().getFullName(), nestedArtifactReferences, nestedSchema, fileContents.get(field.schema().getFullName()).content())); + references.add(registerNestedSchema(field.schema().getFullName(), nestedArtifactReferences, nestedSchema, fileContents.get(field.schema().getFullName()).getContent().content())); } else if (isArrayWithSubschemaElement(field)) { //If the nested schema is an array and the element is a sub-schema, handle it Schema elementSchema = field.schema().getElementType(); @@ -67,7 +69,7 @@ public List handleSchemaReferences(RegisterArtifact rootArtif nestedArtifactReferences = handleSchemaReferences(nestedSchema, elementSchema, fileContents); } - references.add(registerNestedSchema(elementSchema.getFullName(), nestedArtifactReferences, nestedSchema, fileContents.get(elementSchema.getFullName()).content())); + references.add(registerNestedSchema(elementSchema.getFullName(), nestedArtifactReferences, nestedSchema, fileContents.get(elementSchema.getFullName()).getContent().content())); } } return new ArrayList<>(references); @@ -78,7 +80,7 @@ private ParsedDirectoryWrapper parseDirectory(File directory, File rootS .filter(file -> !file.getName().equals(rootSchema.getName())).collect(Collectors.toSet()); Map processed = new HashMap<>(); - Map schemaContents = new HashMap<>(); + Map schemaContents = new HashMap<>(); Schema.Parser rootSchemaParser = new Schema.Parser(); Schema.Parser partialParser = new Schema.Parser(); @@ -91,9 +93,11 @@ private ParsedDirectoryWrapper parseDirectory(File directory, File rootS } try { final ContentHandle schemaContent = readSchemaContent(typeToAdd); + final String contentType = ContentTypes.APPLICATION_JSON; + final TypedContent typedSchemaContent = TypedContent.create(schemaContent, contentType); final Schema schema = partialParser.parse(schemaContent.content()); processed.put(schema.getFullName(), schema); - schemaContents.put(schema.getFullName(), schemaContent); + schemaContents.put(schema.getFullName(), typedSchemaContent); fileParsed = true; } catch (SchemaParseException ex) { log.warn("Error processing Avro schema with name {}. This usually means that the references are not ready yet to parse it", typeToAdd.getName()); @@ -120,9 +124,9 @@ private boolean isArrayWithSubschemaElement(Schema.Field field) { public static class AvroSchemaWrapper implements ParsedDirectoryWrapper { final Schema schema; - final Map fileContents; //Original file contents from the file system. + final Map fileContents; //Original file contents from the file system. - public AvroSchemaWrapper(Schema schema, Map fileContents) { + public AvroSchemaWrapper(Schema schema, Map fileContents) { this.schema = schema; this.fileContents = fileContents; } @@ -133,7 +137,7 @@ public Schema getSchema() { } @Override - public Map getSchemaContents() { + public Map getSchemaContents() { return fileContents; } } diff --git a/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/JsonSchemaDirectoryParser.java b/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/JsonSchemaDirectoryParser.java index 706f4bc327..3e9bfdf248 100644 --- a/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/JsonSchemaDirectoryParser.java +++ b/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/JsonSchemaDirectoryParser.java @@ -2,9 +2,11 @@ import com.fasterxml.jackson.core.JsonProcessingException; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rest.client.RegistryClient; import io.apicurio.registry.rest.client.models.ArtifactReference; import io.apicurio.registry.rules.compatibility.jsonschema.JsonUtil; +import io.apicurio.registry.types.ContentTypes; import org.everit.json.schema.ArraySchema; import org.everit.json.schema.ObjectSchema; import org.everit.json.schema.ReferenceSchema; @@ -41,7 +43,7 @@ public ParsedDirectoryWrapper parse(File rootSchemaFile) { } @Override - public List handleSchemaReferences(RegisterArtifact rootArtifact, org.everit.json.schema.Schema rootSchema, Map fileContents) throws FileNotFoundException, ExecutionException, InterruptedException { + public List handleSchemaReferences(RegisterArtifact rootArtifact, org.everit.json.schema.Schema rootSchema, Map fileContents) throws FileNotFoundException, ExecutionException, InterruptedException { if (rootSchema instanceof ObjectSchema) { @@ -64,7 +66,8 @@ public List handleSchemaReferences(RegisterArtifact rootArtif nestedArtifactReferences = handleSchemaReferences(nestedRegisterArtifact, nestedObjectSchema, fileContents); } - references.add(registerNestedSchema(nestedSchema.getSchemaLocation(), nestedArtifactReferences, nestedRegisterArtifact, fileContents.get(nestedSchema.getSchemaLocation()).content())); + references.add(registerNestedSchema(nestedSchema.getSchemaLocation(), nestedArtifactReferences, + nestedRegisterArtifact, fileContents.get(nestedSchema.getSchemaLocation()).getContent().content())); } else if (rootSchemaPropertySchemas.get(schemaKey) instanceof ArraySchema) { @@ -78,7 +81,8 @@ public List handleSchemaReferences(RegisterArtifact rootArtif nestedArtifactReferences = handleSchemaReferences(nestedRegisterArtifact, arrayElementSchema, fileContents); } - references.add(registerNestedSchema(arrayElementSchema.getSchemaLocation(), nestedArtifactReferences, nestedRegisterArtifact, fileContents.get(arrayElementSchema.getSchemaLocation()).content())); + references.add(registerNestedSchema(arrayElementSchema.getSchemaLocation(), nestedArtifactReferences, + nestedRegisterArtifact, fileContents.get(arrayElementSchema.getSchemaLocation()).getContent().content())); } } } @@ -93,7 +97,7 @@ private JsonSchemaWrapper parseDirectory(File directory, File rootSchema) { .filter(file -> !file.getName().equals(rootSchema.getName())).collect(Collectors.toSet()); Map processed = new HashMap<>(); - Map schemaContents = new HashMap<>(); + Map schemaContents = new HashMap<>(); while (processed.size() != typesToAdd.size()) { boolean fileParsed = false; @@ -103,9 +107,10 @@ private JsonSchemaWrapper parseDirectory(File directory, File rootSchema) { } try { final ContentHandle schemaContent = readSchemaContent(typeToAdd); + final TypedContent typedSchemaContent = TypedContent.create(schemaContent, ContentTypes.APPLICATION_JSON); final Schema schema = JsonUtil.readSchema(schemaContent.content(), schemaContents, false); processed.put(schema.getId(), schema); - schemaContents.put(schema.getId(), schemaContent); + schemaContents.put(schema.getId(), typedSchemaContent); fileParsed = true; } catch (JsonProcessingException ex) { log.warn("Error processing json schema with name {}. This usually means that the references are not ready yet to parse it", typeToAdd.getName()); @@ -127,9 +132,9 @@ private JsonSchemaWrapper parseDirectory(File directory, File rootSchema) { public static class JsonSchemaWrapper implements ParsedDirectoryWrapper { final Schema schema; - final Map fileContents; + final Map fileContents; - public JsonSchemaWrapper(Schema schema, Map fileContents) { + public JsonSchemaWrapper(Schema schema, Map fileContents) { this.schema = schema; this.fileContents = fileContents; } @@ -140,7 +145,7 @@ public Schema getSchema() { } @Override - public Map getSchemaContents() { + public Map getSchemaContents() { return fileContents; } } diff --git a/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/ParsedDirectoryWrapper.java b/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/ParsedDirectoryWrapper.java index f2eba16c7f..6ec52134aa 100644 --- a/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/ParsedDirectoryWrapper.java +++ b/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/ParsedDirectoryWrapper.java @@ -1,6 +1,6 @@ package io.apicurio.registry.maven; -import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import java.util.Map; @@ -8,5 +8,5 @@ public interface ParsedDirectoryWrapper { public Schema getSchema(); - public Map getSchemaContents(); + public Map getSchemaContents(); } diff --git a/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/ProtobufDirectoryParser.java b/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/ProtobufDirectoryParser.java index be0ca6e60c..d5270db84a 100644 --- a/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/ProtobufDirectoryParser.java +++ b/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/ProtobufDirectoryParser.java @@ -3,8 +3,10 @@ import com.google.protobuf.Descriptors; import com.squareup.wire.schema.internal.parser.ProtoFileElement; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.rest.client.RegistryClient; import io.apicurio.registry.rest.client.models.ArtifactReference; +import io.apicurio.registry.types.ContentTypes; import io.apicurio.registry.utils.protobuf.schema.FileDescriptorUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,7 +44,7 @@ public ParsedDirectoryWrapper parse(File protoFile) final Map requiredSchemaDefs = new HashMap<>(); final Descriptors.FileDescriptor schemaDescriptor = FileDescriptorUtils.parseProtoFileWithDependencies(protoFile, protoFiles, requiredSchemaDefs); assert allDependenciesHaveSamePackageName(requiredSchemaDefs, schemaDescriptor.getPackage()) : "All dependencies must have the same package name as the main proto file"; - Map schemaContents = convertSchemaDefs(requiredSchemaDefs, schemaDescriptor.getPackage()); + Map schemaContents = convertSchemaDefs(requiredSchemaDefs, schemaDescriptor.getPackage()); return new DescriptorWrapper(schemaDescriptor, schemaContents); } catch (Descriptors.DescriptorValidationException e) { throw new RuntimeException("Failed to read schema file: " + protoFile, e); @@ -64,14 +66,15 @@ private static boolean allDependenciesHaveSamePackageName(Map sc * which is not needed for the schema registry, given that the dependent schemas are *always* in the same package * of the main proto file. */ - private Map convertSchemaDefs(Map requiredSchemaDefs, String mainProtoPackageName) { + private Map convertSchemaDefs(Map requiredSchemaDefs, String mainProtoPackageName) { if (requiredSchemaDefs.isEmpty()) { return Map.of(); } - Map schemaDefs = new HashMap<>(requiredSchemaDefs.size()); + Map schemaDefs = new HashMap<>(requiredSchemaDefs.size()); for (Map.Entry entry : requiredSchemaDefs.entrySet()) { - if (schemaDefs.put(FileDescriptorUtils.extractProtoFileName(entry.getKey()), - ContentHandle.create(entry.getValue())) != null) { + String fileName = FileDescriptorUtils.extractProtoFileName(entry.getKey()); + TypedContent content = TypedContent.create(ContentHandle.create(entry.getValue()), ContentTypes.APPLICATION_PROTOBUF); + if (schemaDefs.put(fileName, content) != null) { log.warn("There's a clash of dependency name, likely due to stripping the expected package name ie {}: dependencies: {}", mainProtoPackageName, Arrays.toString(requiredSchemaDefs.keySet().toArray(new Object[0]))); } @@ -80,7 +83,7 @@ private Map convertSchemaDefs(Map require } @Override - public List handleSchemaReferences(RegisterArtifact rootArtifact, Descriptors.FileDescriptor protoSchema, Map fileContents) throws FileNotFoundException, InterruptedException, ExecutionException { + public List handleSchemaReferences(RegisterArtifact rootArtifact, Descriptors.FileDescriptor protoSchema, Map fileContents) throws FileNotFoundException, InterruptedException, ExecutionException { Set references = new HashSet<>(); final Set baseDeps = new HashSet<>(Arrays.asList(FileDescriptorUtils.baseDependencies())); final ProtoFileElement rootSchemaElement = FileDescriptorUtils.fileDescriptorToProtoFile(protoSchema.toProto()); @@ -97,7 +100,8 @@ public List handleSchemaReferences(RegisterArtifact rootArtif nestedArtifactReferences = handleSchemaReferences(nestedArtifact, dependency, fileContents); } - references.add(registerNestedSchema(dependencyFullName, nestedArtifactReferences, nestedArtifact, fileContents.get(dependency.getName()).content())); + references.add(registerNestedSchema(dependencyFullName, nestedArtifactReferences, nestedArtifact, + fileContents.get(dependency.getName()).getContent().content())); } } @@ -106,9 +110,9 @@ public List handleSchemaReferences(RegisterArtifact rootArtif public static class DescriptorWrapper implements ParsedDirectoryWrapper { final Descriptors.FileDescriptor fileDescriptor; - final Map schemaContents; //used to store the original file content to register the content as-is. + final Map schemaContents; //used to store the original file content to register the content as-is. - public DescriptorWrapper(Descriptors.FileDescriptor fileDescriptor, Map schemaContents) { + public DescriptorWrapper(Descriptors.FileDescriptor fileDescriptor, Map schemaContents) { this.fileDescriptor = fileDescriptor; this.schemaContents = schemaContents; } @@ -118,7 +122,7 @@ public Descriptors.FileDescriptor getSchema() { return fileDescriptor; } - public Map getSchemaContents() { + public Map getSchemaContents() { return schemaContents; } } diff --git a/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/RegisterRegistryMojo.java b/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/RegisterRegistryMojo.java index 734c4361c9..95b3f10ed6 100644 --- a/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/RegisterRegistryMojo.java +++ b/utils/maven-plugin/src/main/java/io/apicurio/registry/maven/RegisterRegistryMojo.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.protobuf.Descriptors.FileDescriptor; import io.apicurio.registry.content.ContentHandle; +import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.content.refs.ExternalReference; import io.apicurio.registry.content.refs.ReferenceFinder; import io.apicurio.registry.maven.refs.IndexedResource; @@ -159,11 +160,13 @@ private CreateArtifactResponse registerWithAutoRefs(RegisterArtifact artifact, R // Read the artifact content. ContentHandle artifactContent = readContent(artifact.getFile()); + String artifactContentType = getContentTypeByExtension(artifact.getFile().getName()); + TypedContent typedArtifactContent = TypedContent.create(artifactContent, artifactContentType); // Find all references in the content ArtifactTypeUtilProvider provider = this.utilProviderFactory.getArtifactTypeProvider(artifact.getArtifactType()); ReferenceFinder referenceFinder = provider.getReferenceFinder(); - Set externalReferences = referenceFinder.findExternalReferences(artifactContent); + Set externalReferences = referenceFinder.findExternalReferences(typedArtifactContent); // Register all of the references first, then register the artifact. List registeredReferences = externalReferences.stream().map(externalRef -> {