From 82b076aeeffb1f3d1be3e65fe72f2cde7d59c37f Mon Sep 17 00:00:00 2001 From: Eric Wittmann Date: Fri, 9 Aug 2024 11:52:42 -0400 Subject: [PATCH] Support for automatically generating SemVer branches (#5000) * Added support for automatically generating SemVer branches * Revert a temporary snapshot version in root pom.xml * Fix a broken UI integration test --- app/pom.xml | 19 ++-- .../semver/SemVerConfigProperties.java | 28 ++++++ .../impl/sql/AbstractHandleFactory.java | 57 ++++++----- .../impl/sql/AbstractSqlRegistryStorage.java | 80 ++++++++++++++- app/src/main/resources/application.properties | 4 +- .../noprofile/rest/v3/BranchesTest.java | 8 ++ .../noprofile/rest/v3/SemVerBranchesTest.java | 99 +++++++++++++++++++ .../src/main/resources/META-INF/openapi.json | 7 +- .../ref-registry-all-configs.adoc | 26 +++++ ...artifacts_item_versions_request_builder.go | 7 +- .../groups/item_artifacts_request_builder.go | 6 +- go-sdk/pkg/registryclient-v3/kiota-lock.json | 2 +- pom.xml | 6 ++ ui/tests/specs/settings.spec.ts | 2 +- .../src/app/components/errorPage/index.ts | 10 +- ui/ui-app/src/app/pages/PageErrorHandler.tsx | 4 +- .../src/app/pages/settings/SettingsPage.tsx | 9 ++ .../tests/SemanticVersioningProfile.java | 18 ++++ 18 files changed, 337 insertions(+), 55 deletions(-) create mode 100644 app/src/main/java/io/apicurio/registry/semver/SemVerConfigProperties.java create mode 100644 app/src/test/java/io/apicurio/registry/noprofile/rest/v3/SemVerBranchesTest.java create mode 100644 utils/tests/src/main/java/io/apicurio/registry/utils/tests/SemanticVersioningProfile.java diff --git a/app/pom.xml b/app/pom.xml index 2a0be34d7c..ff18180547 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -136,7 +136,6 @@ io.quarkus quarkus-logging-json - io.quarkus quarkus-smallrye-fault-tolerance @@ -160,21 +159,23 @@ quarkus-jdbc-mssql + + + io.strimzi + kafka-oauth-client + org.eclipse.jgit org.eclipse.jgit - - commons-io - commons-io + org.semver4j + semver4j - io.strimzi - kafka-oauth-client + commons-io + commons-io - - commons-codec commons-codec @@ -198,13 +199,11 @@ - org.yaml snakeyaml ${snakeyaml.version} - com.google.guava guava diff --git a/app/src/main/java/io/apicurio/registry/semver/SemVerConfigProperties.java b/app/src/main/java/io/apicurio/registry/semver/SemVerConfigProperties.java new file mode 100644 index 0000000000..73dfa1862b --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/semver/SemVerConfigProperties.java @@ -0,0 +1,28 @@ +package io.apicurio.registry.semver; + +import io.apicurio.common.apps.config.Dynamic; +import io.apicurio.common.apps.config.Info; +import jakarta.inject.Singleton; +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import java.util.function.Supplier; + +@Singleton +public class SemVerConfigProperties { + + @Dynamic(label = "Ensure all version numbers are 'semver' compatible", description = "When enabled, validate that all artifact versions conform to Semantic Versioning 2 format (https://semver.org).") + @ConfigProperty(name = "apicurio.semver.validation.enabled", defaultValue = "false") + @Info(category = "semver", description = "Validate that all artifact versions conform to Semantic Versioning 2 format (https://semver.org).", availableSince = "3.0.0") + public Supplier validationEnabled; + + @Dynamic(label = "Automatically create semver branches", description = "When enabled, automatically create or update branches for major ('A.x') and minor ('A.B.x') artifact versions.") + @ConfigProperty(name = "apicurio.semver.branching.enabled", defaultValue = "false") + @Info(category = "semver", description = "Automatically create or update branches for major ('A.x') and minor ('A.B.x') artifact versions.", availableSince = "3.0.0") + public Supplier branchingEnabled; + + @Dynamic(label = "Coerce invalid semver versions", description = "When enabled and automatically creating semver branches, invalid versions will be coerced to Semantic Versioning 2 format (https://semver.org) if possible.", requires = "apicurio.semver.branching.enabled=true") + @ConfigProperty(name = "apicurio.semver.branching.coerce", defaultValue = "false") + @Info(category = "semver", description = "If true, invalid versions will be coerced to Semantic Versioning 2 format (https://semver.org) if possible.", availableSince = "3.0.0") + public Supplier coerceInvalidVersions; + +} diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractHandleFactory.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractHandleFactory.java index cda0112807..fc20d8b35f 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractHandleFactory.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractHandleFactory.java @@ -32,11 +32,12 @@ protected void initialize(AgroalDataSource dataSource, String dataSourceId, Logg public R withHandle(HandleCallback callback) throws X { LocalState state = state(); try { - // Create a new handle, or throw if one already exists (only one handle allowed at a time) + // Create a new handle if necessary. Increment the "level" if a handle already exists. if (state.handle == null) { state.handle = new HandleImpl(dataSource.getConnection()); + state.level = 0; } else { - throw new RegistryStorageException("Attempt to acquire a nested DB Handle."); + state.level++; } // Invoke the callback with the handle. This will either return a value (success) @@ -54,32 +55,39 @@ public R withHandle(HandleCallback callback) thro } throw e; } finally { - // Commit or rollback the transaction - try { - if (state.handle != null) { - if (state.handle.isRollback()) { - log.trace("Rollback: {} #{}", state.handle.getConnection(), - state.handle.getConnection().hashCode()); - state.handle.getConnection().rollback(); - } else { - log.trace("Commit: {} #{}", state.handle.getConnection(), - state.handle.getConnection().hashCode()); - state().handle.getConnection().commit(); + if (state.level > 0) { + log.trace("Exiting nested call (level {}): {} #{}", state().level, + state().handle.getConnection(), state().handle.getConnection().hashCode()); + state.level--; + } else { + // Commit or rollback the transaction + try { + if (state.handle != null) { + if (state.handle.isRollback()) { + log.trace("Rollback: {} #{}", state.handle.getConnection(), + state.handle.getConnection().hashCode()); + state.handle.getConnection().rollback(); + } else { + log.trace("Commit: {} #{}", state.handle.getConnection(), + state.handle.getConnection().hashCode()); + state().handle.getConnection().commit(); + } } + } catch (Exception e) { + log.error("Could not release database connection/transaction", e); } - } catch (Exception e) { - log.error("Could not release database connection/transaction", e); - } - // Close the connection - try { - if (state.handle != null) { - state.handle.close(); - state.handle = null; + // Close the connection + try { + if (state.handle != null) { + state.handle.close(); + state.handle = null; + state.level = 0; + } + } catch (Exception ex) { + // Nothing we can do + log.error("Could not close a database connection.", ex); } - } catch (Exception ex) { - // Nothing we can do - log.error("Could not close a database connection.", ex); } } } @@ -109,5 +117,6 @@ private LocalState state() { private static class LocalState { HandleImpl handle; + int level = 0; } } 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 fa76919c53..93bdde04fc 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 @@ -11,6 +11,7 @@ import io.apicurio.registry.model.GA; import io.apicurio.registry.model.GAV; import io.apicurio.registry.model.VersionId; +import io.apicurio.registry.semver.SemVerConfigProperties; import io.apicurio.registry.storage.RegistryStorage; import io.apicurio.registry.storage.StorageBehaviorProperties; import io.apicurio.registry.storage.StorageEvent; @@ -113,9 +114,11 @@ import io.quarkus.security.identity.SecurityIdentity; import jakarta.enterprise.event.Event; import jakarta.inject.Inject; +import jakarta.validation.ValidationException; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.semver4j.Semver; import org.slf4j.Logger; import java.sql.ResultSet; @@ -187,6 +190,9 @@ public abstract class AbstractSqlRegistryStorage implements RegistryStorage { @Inject RegistryStorageContentUtils utils; + @Inject + SemVerConfigProperties semVerConfigProps; + protected SqlStatements sqlStatements() { return sqlStatements; } @@ -554,7 +560,6 @@ private ArtifactVersionMetaDataDto createArtifactVersionRaw(Handle handle, boole .bind(12, contentId).execute(); gav = new GAV(groupId, artifactId, finalVersion1); - createOrUpdateBranchRaw(handle, gav, BranchId.LATEST, true); } else { handle.createUpdate(sqlStatements.insertVersion(false)).bind(0, globalId) .bind(1, normalizeGroupId(groupId)).bind(2, artifactId).bind(3, version) @@ -571,7 +576,6 @@ private ArtifactVersionMetaDataDto createArtifactVersionRaw(Handle handle, boole } gav = getGAVByGlobalId(handle, globalId); - createOrUpdateBranchRaw(handle, gav, BranchId.LATEST, true); } // Insert labels into the "version_labels" table @@ -583,6 +587,10 @@ private ArtifactVersionMetaDataDto createArtifactVersionRaw(Handle handle, boole }); } + // Update system generated branches + createOrUpdateBranchRaw(handle, gav, BranchId.LATEST, true); + createOrUpdateSemverBranches(handle, gav); + // Create any user defined branches if (branches != null && !branches.isEmpty()) { branches.forEach(branch -> { @@ -595,6 +603,54 @@ private ArtifactVersionMetaDataDto createArtifactVersionRaw(Handle handle, boole .map(ArtifactVersionMetaDataDtoMapper.instance).one(); } + /** + * If SemVer support is enabled, create (or update) the automatic system generated semantic versioning + * branches. + * + * @param handle + * @param gav + */ + private void createOrUpdateSemverBranches(Handle handle, GAV gav) { + boolean validationEnabled = semVerConfigProps.validationEnabled.get(); + boolean branchingEnabled = semVerConfigProps.branchingEnabled.get(); + boolean coerceInvalidVersions = semVerConfigProps.coerceInvalidVersions.get(); + + // Validate the version if validation is enabled. + if (validationEnabled) { + Semver semver = Semver.parse(gav.getRawVersionId()); + if (semver == null) { + throw new ValidationException("Version '" + gav.getRawVersionId() + + "' does not conform to Semantic Versioning 2 format."); + } + } + + // Create branches if branching is enabled + if (!branchingEnabled) { + return; + } + + Semver semver = null; + if (coerceInvalidVersions) { + semver = Semver.coerce(gav.getRawVersionId()); + if (semver == null) { + throw new ValidationException("Version '" + gav.getRawVersionId() + + "' cannot be coerced to Semantic Versioning 2 format."); + } + } else { + semver = Semver.parse(gav.getRawVersionId()); + if (semver == null) { + throw new ValidationException("Version '" + gav.getRawVersionId() + + "' does not conform to Semantic Versioning 2 format."); + } + } + if (semver == null) { + throw new UnreachableCodeException("Unexpectedly reached unreachable code!"); + } + createOrUpdateBranchRaw(handle, gav, new BranchId(semver.getMajor() + ".x"), true); + createOrUpdateBranchRaw(handle, gav, new BranchId(semver.getMajor() + "." + semver.getMinor() + ".x"), + true); + } + /** * Store the content in the database and return the content ID of the new row. If the content already * exists, just return the content ID of the existing row. @@ -3031,6 +3087,11 @@ public BranchMetaDataDto createBranch(GA ga, BranchId branchId, String descripti @Override public void updateBranchMetaData(GA ga, BranchId branchId, EditableBranchMetaDataDto dto) { + BranchMetaDataDto bmd = getBranchMetaData(ga, branchId); + if (bmd.isSystemDefined()) { + throw new NotAllowedException("System generated branches cannot be modified."); + } + String modifiedBy = securityIdentity.getPrincipal().getName(); Date modifiedOn = new Date(); log.debug("Updating metadata for branch {} of {}/{}.", branchId, ga.getRawGroupIdWithNull(), @@ -3220,6 +3281,11 @@ public VersionSearchResultsDto getBranchVersions(GA ga, BranchId branchId, int o @Override public void appendVersionToBranch(GA ga, BranchId branchId, VersionId version) { + BranchMetaDataDto bmd = getBranchMetaData(ga, branchId); + if (bmd.isSystemDefined()) { + throw new NotAllowedException("System generated branches cannot be modified."); + } + try { handles.withHandle(handle -> { appendVersionToBranchRaw(handle, ga, branchId, version); @@ -3257,6 +3323,11 @@ private void appendVersionToBranchRaw(Handle handle, GA ga, BranchId branchId, V @Override public void replaceBranchVersions(GA ga, BranchId branchId, List versions) { + BranchMetaDataDto bmd = getBranchMetaData(ga, branchId); + if (bmd.isSystemDefined()) { + throw new NotAllowedException("System generated branches cannot be modified."); + } + handles.withHandle(handle -> { // Delete all previous versions. handle.createUpdate(sqlStatements.deleteBranchVersions()).bind(0, ga.getRawGroupId()) @@ -3341,8 +3412,9 @@ private GAV getGAVByGlobalId(Handle handle, long globalId) { @Override public void deleteBranch(GA ga, BranchId branchId) { - if (BranchId.LATEST.equals(branchId)) { - throw new NotAllowedException("Artifact branch 'latest' cannot be deleted."); + BranchMetaDataDto bmd = getBranchMetaData(ga, branchId); + if (bmd.isSystemDefined()) { + throw new NotAllowedException("System generated branches cannot be deleted."); } handles.withHandleNoException(handle -> { diff --git a/app/src/main/resources/application.properties b/app/src/main/resources/application.properties index 69dcaf38f7..0f07eb3ae9 100644 --- a/app/src/main/resources/application.properties +++ b/app/src/main/resources/application.properties @@ -127,7 +127,9 @@ apicurio.authn.basic-client-credentials.enabled.dynamic.allow=${apicurio.config. apicurio.rest.deletion.group.enabled.dynamic.allow=${apicurio.config.dynamic.allow-all} apicurio.rest.deletion.artifact.enabled.dynamic.allow=${apicurio.config.dynamic.allow-all} apicurio.rest.deletion.artifactVersion.enabled.dynamic.allow=${apicurio.config.dynamic.allow-all} - +apicurio.semver.validation.enabled.dynamic.allow=${apicurio.config.dynamic.allow-all} +apicurio.semver.branching.enabled.dynamic.allow=${apicurio.config.dynamic.allow-all} +apicurio.semver.branching.coerce.dynamic.allow=${apicurio.config.dynamic.allow-all} # Error apicurio.api.errors.include-stack-in-response=false diff --git a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/BranchesTest.java b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/BranchesTest.java index 897b27d676..3614ff0e73 100644 --- a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/BranchesTest.java +++ b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/BranchesTest.java @@ -7,6 +7,7 @@ import io.apicurio.registry.rest.client.models.CreateBranch; import io.apicurio.registry.rest.client.models.CreateVersion; import io.apicurio.registry.rest.client.models.EditableBranchMetaData; +import io.apicurio.registry.rest.client.models.Error; import io.apicurio.registry.rest.client.models.ReplaceBranchVersions; import io.apicurio.registry.rest.client.models.VersionMetaData; import io.apicurio.registry.rest.client.models.VersionSearchResults; @@ -39,6 +40,13 @@ public void testLatestBranch() throws Exception { VersionSearchResults versions = clientV3.groups().byGroupId(groupId).artifacts() .byArtifactId(artifactId).branches().byBranchId("latest").versions().get(); Assertions.assertEquals(2, versions.getCount()); + + // Not allowed to delete the latest branch. + var error = Assertions.assertThrows(Error.class, () -> { + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).branches() + .byBranchId("latest").delete(); + }); + Assertions.assertEquals("System generated branches cannot be deleted.", error.getMessageEscaped()); } @Test diff --git a/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/SemVerBranchesTest.java b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/SemVerBranchesTest.java new file mode 100644 index 0000000000..da2bda1522 --- /dev/null +++ b/app/src/test/java/io/apicurio/registry/noprofile/rest/v3/SemVerBranchesTest.java @@ -0,0 +1,99 @@ +package io.apicurio.registry.noprofile.rest.v3; + +import io.apicurio.registry.AbstractResourceTestBase; +import io.apicurio.registry.rest.client.models.BranchMetaData; +import io.apicurio.registry.rest.client.models.BranchSearchResults; +import io.apicurio.registry.rest.client.models.CreateArtifact; +import io.apicurio.registry.rest.client.models.CreateGroup; +import io.apicurio.registry.rest.client.models.CreateVersion; +import io.apicurio.registry.rest.client.models.Error; +import io.apicurio.registry.rest.client.models.VersionSearchResults; +import io.apicurio.registry.types.ArtifactType; +import io.apicurio.registry.types.ContentTypes; +import io.apicurio.registry.utils.tests.SemanticVersioningProfile; +import io.apicurio.registry.utils.tests.TestUtils; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +@QuarkusTest +@TestProfile(SemanticVersioningProfile.class) +public class SemVerBranchesTest extends AbstractResourceTestBase { + + @Test + public void testSemVerBranches() throws Exception { + String groupId = "SemVerBranchesTest"; + String artifactId = "testSemVerBranches"; + + CreateGroup createGroup = new CreateGroup(); + createGroup.setGroupId(groupId); + clientV3.groups().post(createGroup); + + createSemVerArtifact(groupId, artifactId); + createSemVerVersion(groupId, artifactId, "1.0.1"); + createSemVerVersion(groupId, artifactId, "1.0.2"); + createSemVerVersion(groupId, artifactId, "1.0.3"); + createSemVerVersion(groupId, artifactId, "1.1.0"); + createSemVerVersion(groupId, artifactId, "1.1.1"); + createSemVerVersion(groupId, artifactId, "2.5.1"); + createSemVerVersion(groupId, artifactId, "2.5.2"); + createSemVerVersion(groupId, artifactId, "3.0.0"); + + BranchSearchResults results = clientV3.groups().byGroupId(groupId).artifacts() + .byArtifactId(artifactId).branches().get(); + // The following branches should have been automatically created: + // latest, 1.x, 2.x, 3.x, 1.0.x, 1.1.x, 2.5.x, 3.0.x + Assertions.assertEquals(8, results.getCount()); + + validateSemVerBranch(groupId, artifactId, "latest", 9, "3.0.0"); + validateSemVerBranch(groupId, artifactId, "1.x", 6, "1.1.1"); + validateSemVerBranch(groupId, artifactId, "1.0.x", 4, "1.0.3"); + validateSemVerBranch(groupId, artifactId, "2.x", 2, "2.5.2"); + validateSemVerBranch(groupId, artifactId, "2.5.x", 2, "2.5.2"); + validateSemVerBranch(groupId, artifactId, "3.x", 1, "3.0.0"); + validateSemVerBranch(groupId, artifactId, "3.0.x", 1, "3.0.0"); + + // 4.x does not exist. + Error error = Assertions.assertThrows(Error.class, () -> { + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).branches() + .byBranchId("4.x").versions().get(); + }); + Assertions.assertEquals("No branch '4.x' was found in SemVerBranchesTest/testSemVerBranches.", + error.getMessageEscaped()); + } + + private void validateSemVerBranch(String groupId, String artifactId, String branchId, + int expectedVersionCount, String expectedLatestVersion) { + BranchMetaData bmd = clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId) + .branches().byBranchId(branchId).get(); + Assertions.assertNotNull(bmd); + Assertions.assertEquals(branchId, bmd.getBranchId()); + Assertions.assertEquals(Boolean.TRUE, bmd.getSystemDefined()); + + VersionSearchResults versions = clientV3.groups().byGroupId(groupId).artifacts() + .byArtifactId(artifactId).branches().byBranchId(branchId).versions().get(); + Assertions.assertEquals(expectedVersionCount, versions.getCount()); + Assertions.assertEquals(expectedLatestVersion, versions.getVersions().get(0).getVersion()); + + Error error = Assertions.assertThrows(Error.class, () -> { + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).branches() + .byBranchId(branchId).delete(); + }); + Assertions.assertEquals("System generated branches cannot be deleted.", error.getMessageEscaped()); + } + + private void createSemVerArtifact(String groupId, String artifactId) { + CreateArtifact createArtifact = TestUtils.clientCreateArtifact(artifactId, ArtifactType.AVRO, "{}", + ContentTypes.APPLICATION_JSON); + createArtifact.getFirstVersion().setVersion("1.0.0"); + clientV3.groups().byGroupId(groupId).artifacts().post(createArtifact); + } + + private void createSemVerVersion(String groupId, String artifactId, String version) throws Exception { + CreateVersion cv = TestUtils.clientCreateVersion("{}", ContentTypes.APPLICATION_JSON); + cv.setVersion(version); + clientV3.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().post(cv); + } + +} diff --git a/common/src/main/resources/META-INF/openapi.json b/common/src/main/resources/META-INF/openapi.json index bfea527749..672f465073 100644 --- a/common/src/main/resources/META-INF/openapi.json +++ b/common/src/main/resources/META-INF/openapi.json @@ -1457,7 +1457,7 @@ }, "operationId": "createArtifact", "summary": "Create artifact", - "description": "Creates a new artifact. The body of the request should be a `CreateArtifact` \nobject, which includes the metadata of the new artifact and, optionally, the \nmetadata and content of the first version.\n\nIf the artifact type is not provided, the registry attempts to figure out what \nkind of artifact is being added from the\nfollowing supported list:\n\n* Avro (`AVRO`)\n* Protobuf (`PROTOBUF`)\n* JSON Schema (`JSON`)\n* Kafka Connect (`KCONNECT`)\n* OpenAPI (`OPENAPI`)\n* AsyncAPI (`ASYNCAPI`)\n* GraphQL (`GRAPHQL`)\n* Web Services Description Language (`WSDL`)\n* XML Schema (`XSD`)\n\nAn artifact will be created using the unique artifact ID that can optionally be \nprovided in the request body. If not provided in the request, the server will\ngenerate a unique ID for the artifact. It is typically recommended that callers\nprovide the ID, because it is typically a meaningful identifier, and as such\nfor most use cases should be supplied by the caller.\n\nIf an artifact with the provided artifact ID already exists, the default behavior\nis for the server to reject the content with a 409 error. However, the caller can\nsupply the `ifExists` query parameter to alter this default behavior. The `ifExists`\nquery parameter can have one of the following values:\n\n* `FAIL` (*default*) - server rejects the content with a 409 error\n* `UPDATE` - server updates the existing artifact and returns the new metadata\n* `RETURN` - server does not create or add content to the server, but instead \nreturns the metadata for the existing artifact\n* `RETURN_OR_UPDATE` - server returns an existing **version** that matches the \nprovided content if such a version exists, otherwise a new version is created\n\nThis operation may fail for one of the following reasons:\n\n* An invalid `ArtifactType` was indicated (HTTP error `400`)\n* No `ArtifactType` was indicated and the server could not determine one from the content (HTTP error `400`)\n* Provided content (request body) was empty (HTTP error `400`)\n* An artifact with the provided ID already exists (HTTP error `409`)\n* The content violates one of the configured global rules (HTTP error `409`)\n* A server error occurred (HTTP error `500`)\n\nNote that if the `dryRun` query parameter is set to `true`, then this operation\nwill not actually make any changes. Instead it will succeed or fail based on \nwhether it **would have worked**. Use this option to, for example, check if an\nartifact is valid or if a new version passes configured compatibility checks." + "description": "Creates a new artifact. The body of the request should be a `CreateArtifact` \nobject, which includes the metadata of the new artifact and, optionally, the \nmetadata and content of the first version.\n\nIf the artifact type is not provided, the registry attempts to figure out what \nkind of artifact is being added from the\nfollowing supported list:\n\n* Avro (`AVRO`)\n* Protobuf (`PROTOBUF`)\n* JSON Schema (`JSON`)\n* Kafka Connect (`KCONNECT`)\n* OpenAPI (`OPENAPI`)\n* AsyncAPI (`ASYNCAPI`)\n* GraphQL (`GRAPHQL`)\n* Web Services Description Language (`WSDL`)\n* XML Schema (`XSD`)\n\nAn artifact will be created using the unique artifact ID that can optionally be \nprovided in the request body. If not provided in the request, the server will\ngenerate a unique ID for the artifact. It is typically recommended that callers\nprovide the ID, because it is typically a meaningful identifier, and as such\nfor most use cases should be supplied by the caller.\n\nIf an artifact with the provided artifact ID already exists, the default behavior\nis for the server to reject the content with a 409 error. However, the caller can\nsupply the `ifExists` query parameter to alter this default behavior. The `ifExists`\nquery parameter can have one of the following values:\n\n* `FAIL` (*default*) - server rejects the content with a 409 error\n* `UPDATE` - server updates the existing artifact and returns the new metadata\n* `RETURN` - server does not create or add content to the server, but instead \nreturns the metadata for the existing artifact\n* `RETURN_OR_UPDATE` - server returns an existing **version** that matches the \nprovided content if such a version exists, otherwise a new version is created\n\nThis operation may fail for one of the following reasons:\n\n* An invalid `ArtifactType` was indicated (HTTP error `400`)\n* No `ArtifactType` was indicated and the server could not determine one from the content (HTTP error `400`)\n* Provided content (request body) was empty (HTTP error `400`)\n* An invalid version number was used for the optional included first version (HTTP error `400`)\n* An artifact with the provided ID already exists (HTTP error `409`)\n* The content violates one of the configured global rules (HTTP error `409`)\n* A server error occurred (HTTP error `500`)\n\nNote that if the `dryRun` query parameter is set to `true`, then this operation\nwill not actually make any changes. Instead it will succeed or fail based on \nwhether it **would have worked**. Use this option to, for example, check if an\nartifact is valid or if a new version passes configured compatibility checks." }, "delete": { "tags": [ @@ -1999,6 +1999,9 @@ }, "description": "The artifact version was successfully created." }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "404": { "$ref": "#/components/responses/NotFound" }, @@ -2011,7 +2014,7 @@ }, "operationId": "createArtifactVersion", "summary": "Create artifact version", - "description": "Creates a new version of the artifact by uploading new content. The configured rules for\nthe artifact are applied, and if they all pass, the new content is added as the most recent \nversion of the artifact. If any of the rules fail, an error is returned.\n\nThe body of the request can be the raw content of the new artifact version, or the raw content \nand a set of references pointing to other artifacts, and the type\nof that content should match the artifact's type (for example if the artifact type is `AVRO`\nthen the content of the request should be an Apache Avro document).\n\nThis operation can fail for the following reasons:\n\n* Provided content (request body) was empty (HTTP error `400`)\n* No artifact with this `artifactId` exists (HTTP error `404`)\n* The new content violates one of the rules configured for the artifact (HTTP error `409`)\n* A server error occurred (HTTP error `500`)\n" + "description": "Creates a new version of the artifact by uploading new content. The configured rules for\nthe artifact are applied, and if they all pass, the new content is added as the most recent \nversion of the artifact. If any of the rules fail, an error is returned.\n\nThe body of the request can be the raw content of the new artifact version, or the raw content \nand a set of references pointing to other artifacts, and the type\nof that content should match the artifact's type (for example if the artifact type is `AVRO`\nthen the content of the request should be an Apache Avro document).\n\nThis operation can fail for the following reasons:\n\n* Provided content (request body) was empty (HTTP error `400`)\n* An invalid version number was provided (HTTP error `400`)\n* No artifact with this `artifactId` exists (HTTP error `404`)\n* The new content violates one of the rules configured for the artifact (HTTP error `409`)\n* A server error occurred (HTTP error `500`)\n" }, "parameters": [ { diff --git a/docs/modules/ROOT/partials/getting-started/ref-registry-all-configs.adoc b/docs/modules/ROOT/partials/getting-started/ref-registry-all-configs.adoc index b75749b962..5bbb7f081a 100644 --- a/docs/modules/ROOT/partials/getting-started/ref-registry-all-configs.adoc +++ b/docs/modules/ROOT/partials/getting-started/ref-registry-all-configs.adoc @@ -529,6 +529,32 @@ The following {registry} configuration options are available for each component |Enables group deletion |=== +== semver +.semver configuration options +[.table-expandable,width="100%",cols="6,3,2,3,5",options="header"] +|=== +|Name +|Type +|Default +|Available from +|Description +|`apicurio.semver.branching.coerce` +|`boolean [dynamic]` +|`false` +|`3.0.0` +|If true, invalid versions will be coerced to Semantic Versioning 2 format (https://semver.org) if possible. +|`apicurio.semver.branching.enabled` +|`boolean [dynamic]` +|`false` +|`3.0.0` +|Automatically create or update branches for major ('A.x') and minor ('A.B.x') artifact versions. +|`apicurio.semver.validation.enabled` +|`boolean [dynamic]` +|`false` +|`3.0.0` +|Validate that all artifact versions conform to Semantic Versioning 2 format (https://semver.org). +|=== + == storage .storage configuration options [.table-expandable,width="100%",cols="6,3,2,3,5",options="header"] diff --git a/go-sdk/pkg/registryclient-v3/groups/item_artifacts_item_versions_request_builder.go b/go-sdk/pkg/registryclient-v3/groups/item_artifacts_item_versions_request_builder.go index 5fff034cd6..26f2a93af6 100644 --- a/go-sdk/pkg/registryclient-v3/groups/item_artifacts_item_versions_request_builder.go +++ b/go-sdk/pkg/registryclient-v3/groups/item_artifacts_item_versions_request_builder.go @@ -39,7 +39,7 @@ type ItemArtifactsItemVersionsRequestBuilderGetRequestConfiguration struct { QueryParameters *ItemArtifactsItemVersionsRequestBuilderGetQueryParameters } -// ItemArtifactsItemVersionsRequestBuilderPostQueryParameters creates a new version of the artifact by uploading new content. The configured rules forthe artifact are applied, and if they all pass, the new content is added as the most recent version of the artifact. If any of the rules fail, an error is returned.The body of the request can be the raw content of the new artifact version, or the raw content and a set of references pointing to other artifacts, and the typeof that content should match the artifact's type (for example if the artifact type is `AVRO`then the content of the request should be an Apache Avro document).This operation can fail for the following reasons:* Provided content (request body) was empty (HTTP error `400`)* No artifact with this `artifactId` exists (HTTP error `404`)* The new content violates one of the rules configured for the artifact (HTTP error `409`)* A server error occurred (HTTP error `500`) +// ItemArtifactsItemVersionsRequestBuilderPostQueryParameters creates a new version of the artifact by uploading new content. The configured rules forthe artifact are applied, and if they all pass, the new content is added as the most recent version of the artifact. If any of the rules fail, an error is returned.The body of the request can be the raw content of the new artifact version, or the raw content and a set of references pointing to other artifacts, and the typeof that content should match the artifact's type (for example if the artifact type is `AVRO`then the content of the request should be an Apache Avro document).This operation can fail for the following reasons:* Provided content (request body) was empty (HTTP error `400`)* An invalid version number was provided (HTTP error `400`)* No artifact with this `artifactId` exists (HTTP error `404`)* The new content violates one of the rules configured for the artifact (HTTP error `409`)* A server error occurred (HTTP error `500`) type ItemArtifactsItemVersionsRequestBuilderPostQueryParameters struct { // When set to `true`, the operation will not result in any changes. Instead, itwill return a result based on whether the operation **would have succeeded**. DryRun *bool `uriparametername:"dryRun"` @@ -102,13 +102,14 @@ func (m *ItemArtifactsItemVersionsRequestBuilder) Get(ctx context.Context, reque return res.(i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.VersionSearchResultsable), nil } -// Post creates a new version of the artifact by uploading new content. The configured rules forthe artifact are applied, and if they all pass, the new content is added as the most recent version of the artifact. If any of the rules fail, an error is returned.The body of the request can be the raw content of the new artifact version, or the raw content and a set of references pointing to other artifacts, and the typeof that content should match the artifact's type (for example if the artifact type is `AVRO`then the content of the request should be an Apache Avro document).This operation can fail for the following reasons:* Provided content (request body) was empty (HTTP error `400`)* No artifact with this `artifactId` exists (HTTP error `404`)* The new content violates one of the rules configured for the artifact (HTTP error `409`)* A server error occurred (HTTP error `500`) +// Post creates a new version of the artifact by uploading new content. The configured rules forthe artifact are applied, and if they all pass, the new content is added as the most recent version of the artifact. If any of the rules fail, an error is returned.The body of the request can be the raw content of the new artifact version, or the raw content and a set of references pointing to other artifacts, and the typeof that content should match the artifact's type (for example if the artifact type is `AVRO`then the content of the request should be an Apache Avro document).This operation can fail for the following reasons:* Provided content (request body) was empty (HTTP error `400`)* An invalid version number was provided (HTTP error `400`)* No artifact with this `artifactId` exists (HTTP error `404`)* The new content violates one of the rules configured for the artifact (HTTP error `409`)* A server error occurred (HTTP error `500`) func (m *ItemArtifactsItemVersionsRequestBuilder) Post(ctx context.Context, body i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateVersionable, requestConfiguration *ItemArtifactsItemVersionsRequestBuilderPostRequestConfiguration) (i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.VersionMetaDataable, error) { requestInfo, err := m.ToPostRequestInformation(ctx, body, requestConfiguration) if err != nil { return nil, err } errorMapping := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ErrorMappings{ + "400": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateErrorFromDiscriminatorValue, "404": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateErrorFromDiscriminatorValue, "409": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateErrorFromDiscriminatorValue, "500": i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateErrorFromDiscriminatorValue, @@ -137,7 +138,7 @@ func (m *ItemArtifactsItemVersionsRequestBuilder) ToGetRequestInformation(ctx co return requestInfo, nil } -// ToPostRequestInformation creates a new version of the artifact by uploading new content. The configured rules forthe artifact are applied, and if they all pass, the new content is added as the most recent version of the artifact. If any of the rules fail, an error is returned.The body of the request can be the raw content of the new artifact version, or the raw content and a set of references pointing to other artifacts, and the typeof that content should match the artifact's type (for example if the artifact type is `AVRO`then the content of the request should be an Apache Avro document).This operation can fail for the following reasons:* Provided content (request body) was empty (HTTP error `400`)* No artifact with this `artifactId` exists (HTTP error `404`)* The new content violates one of the rules configured for the artifact (HTTP error `409`)* A server error occurred (HTTP error `500`) +// ToPostRequestInformation creates a new version of the artifact by uploading new content. The configured rules forthe artifact are applied, and if they all pass, the new content is added as the most recent version of the artifact. If any of the rules fail, an error is returned.The body of the request can be the raw content of the new artifact version, or the raw content and a set of references pointing to other artifacts, and the typeof that content should match the artifact's type (for example if the artifact type is `AVRO`then the content of the request should be an Apache Avro document).This operation can fail for the following reasons:* Provided content (request body) was empty (HTTP error `400`)* An invalid version number was provided (HTTP error `400`)* No artifact with this `artifactId` exists (HTTP error `404`)* The new content violates one of the rules configured for the artifact (HTTP error `409`)* A server error occurred (HTTP error `500`) func (m *ItemArtifactsItemVersionsRequestBuilder) ToPostRequestInformation(ctx context.Context, body i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateVersionable, requestConfiguration *ItemArtifactsItemVersionsRequestBuilderPostRequestConfiguration) (*i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestInformation, error) { requestInfo := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewRequestInformationWithMethodAndUrlTemplateAndPathParameters(i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.POST, m.BaseRequestBuilder.UrlTemplate, m.BaseRequestBuilder.PathParameters) if requestConfiguration != nil { diff --git a/go-sdk/pkg/registryclient-v3/groups/item_artifacts_request_builder.go b/go-sdk/pkg/registryclient-v3/groups/item_artifacts_request_builder.go index 35db320d9e..b6e2578bf2 100644 --- a/go-sdk/pkg/registryclient-v3/groups/item_artifacts_request_builder.go +++ b/go-sdk/pkg/registryclient-v3/groups/item_artifacts_request_builder.go @@ -47,7 +47,7 @@ type ItemArtifactsRequestBuilderGetRequestConfiguration struct { QueryParameters *ItemArtifactsRequestBuilderGetQueryParameters } -// ItemArtifactsRequestBuilderPostQueryParameters creates a new artifact. The body of the request should be a `CreateArtifact` object, which includes the metadata of the new artifact and, optionally, the metadata and content of the first version.If the artifact type is not provided, the registry attempts to figure out what kind of artifact is being added from thefollowing supported list:* Avro (`AVRO`)* Protobuf (`PROTOBUF`)* JSON Schema (`JSON`)* Kafka Connect (`KCONNECT`)* OpenAPI (`OPENAPI`)* AsyncAPI (`ASYNCAPI`)* GraphQL (`GRAPHQL`)* Web Services Description Language (`WSDL`)* XML Schema (`XSD`)An artifact will be created using the unique artifact ID that can optionally be provided in the request body. If not provided in the request, the server willgenerate a unique ID for the artifact. It is typically recommended that callersprovide the ID, because it is typically a meaningful identifier, and as suchfor most use cases should be supplied by the caller.If an artifact with the provided artifact ID already exists, the default behavioris for the server to reject the content with a 409 error. However, the caller cansupply the `ifExists` query parameter to alter this default behavior. The `ifExists`query parameter can have one of the following values:* `FAIL` (*default*) - server rejects the content with a 409 error* `UPDATE` - server updates the existing artifact and returns the new metadata* `RETURN` - server does not create or add content to the server, but instead returns the metadata for the existing artifact* `RETURN_OR_UPDATE` - server returns an existing **version** that matches the provided content if such a version exists, otherwise a new version is createdThis operation may fail for one of the following reasons:* An invalid `ArtifactType` was indicated (HTTP error `400`)* No `ArtifactType` was indicated and the server could not determine one from the content (HTTP error `400`)* Provided content (request body) was empty (HTTP error `400`)* An artifact with the provided ID already exists (HTTP error `409`)* The content violates one of the configured global rules (HTTP error `409`)* A server error occurred (HTTP error `500`)Note that if the `dryRun` query parameter is set to `true`, then this operationwill not actually make any changes. Instead it will succeed or fail based on whether it **would have worked**. Use this option to, for example, check if anartifact is valid or if a new version passes configured compatibility checks. +// ItemArtifactsRequestBuilderPostQueryParameters creates a new artifact. The body of the request should be a `CreateArtifact` object, which includes the metadata of the new artifact and, optionally, the metadata and content of the first version.If the artifact type is not provided, the registry attempts to figure out what kind of artifact is being added from thefollowing supported list:* Avro (`AVRO`)* Protobuf (`PROTOBUF`)* JSON Schema (`JSON`)* Kafka Connect (`KCONNECT`)* OpenAPI (`OPENAPI`)* AsyncAPI (`ASYNCAPI`)* GraphQL (`GRAPHQL`)* Web Services Description Language (`WSDL`)* XML Schema (`XSD`)An artifact will be created using the unique artifact ID that can optionally be provided in the request body. If not provided in the request, the server willgenerate a unique ID for the artifact. It is typically recommended that callersprovide the ID, because it is typically a meaningful identifier, and as suchfor most use cases should be supplied by the caller.If an artifact with the provided artifact ID already exists, the default behavioris for the server to reject the content with a 409 error. However, the caller cansupply the `ifExists` query parameter to alter this default behavior. The `ifExists`query parameter can have one of the following values:* `FAIL` (*default*) - server rejects the content with a 409 error* `UPDATE` - server updates the existing artifact and returns the new metadata* `RETURN` - server does not create or add content to the server, but instead returns the metadata for the existing artifact* `RETURN_OR_UPDATE` - server returns an existing **version** that matches the provided content if such a version exists, otherwise a new version is createdThis operation may fail for one of the following reasons:* An invalid `ArtifactType` was indicated (HTTP error `400`)* No `ArtifactType` was indicated and the server could not determine one from the content (HTTP error `400`)* Provided content (request body) was empty (HTTP error `400`)* An invalid version number was used for the optional included first version (HTTP error `400`)* An artifact with the provided ID already exists (HTTP error `409`)* The content violates one of the configured global rules (HTTP error `409`)* A server error occurred (HTTP error `500`)Note that if the `dryRun` query parameter is set to `true`, then this operationwill not actually make any changes. Instead it will succeed or fail based on whether it **would have worked**. Use this option to, for example, check if anartifact is valid or if a new version passes configured compatibility checks. type ItemArtifactsRequestBuilderPostQueryParameters struct { // Used only when the `ifExists` query parameter is set to `RETURN_OR_UPDATE`, this parameter can be set to `true` to indicate that the server should "canonicalize" the content when searching for a matching version. The canonicalization algorithm is unique to each artifact type, but typically involves removing extra whitespace and formatting the content in a consistent manner. Canonical *bool `uriparametername:"canonical"` @@ -132,7 +132,7 @@ func (m *ItemArtifactsRequestBuilder) Get(ctx context.Context, requestConfigurat return res.(i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.ArtifactSearchResultsable), nil } -// Post creates a new artifact. The body of the request should be a `CreateArtifact` object, which includes the metadata of the new artifact and, optionally, the metadata and content of the first version.If the artifact type is not provided, the registry attempts to figure out what kind of artifact is being added from thefollowing supported list:* Avro (`AVRO`)* Protobuf (`PROTOBUF`)* JSON Schema (`JSON`)* Kafka Connect (`KCONNECT`)* OpenAPI (`OPENAPI`)* AsyncAPI (`ASYNCAPI`)* GraphQL (`GRAPHQL`)* Web Services Description Language (`WSDL`)* XML Schema (`XSD`)An artifact will be created using the unique artifact ID that can optionally be provided in the request body. If not provided in the request, the server willgenerate a unique ID for the artifact. It is typically recommended that callersprovide the ID, because it is typically a meaningful identifier, and as suchfor most use cases should be supplied by the caller.If an artifact with the provided artifact ID already exists, the default behavioris for the server to reject the content with a 409 error. However, the caller cansupply the `ifExists` query parameter to alter this default behavior. The `ifExists`query parameter can have one of the following values:* `FAIL` (*default*) - server rejects the content with a 409 error* `UPDATE` - server updates the existing artifact and returns the new metadata* `RETURN` - server does not create or add content to the server, but instead returns the metadata for the existing artifact* `RETURN_OR_UPDATE` - server returns an existing **version** that matches the provided content if such a version exists, otherwise a new version is createdThis operation may fail for one of the following reasons:* An invalid `ArtifactType` was indicated (HTTP error `400`)* No `ArtifactType` was indicated and the server could not determine one from the content (HTTP error `400`)* Provided content (request body) was empty (HTTP error `400`)* An artifact with the provided ID already exists (HTTP error `409`)* The content violates one of the configured global rules (HTTP error `409`)* A server error occurred (HTTP error `500`)Note that if the `dryRun` query parameter is set to `true`, then this operationwill not actually make any changes. Instead it will succeed or fail based on whether it **would have worked**. Use this option to, for example, check if anartifact is valid or if a new version passes configured compatibility checks. +// Post creates a new artifact. The body of the request should be a `CreateArtifact` object, which includes the metadata of the new artifact and, optionally, the metadata and content of the first version.If the artifact type is not provided, the registry attempts to figure out what kind of artifact is being added from thefollowing supported list:* Avro (`AVRO`)* Protobuf (`PROTOBUF`)* JSON Schema (`JSON`)* Kafka Connect (`KCONNECT`)* OpenAPI (`OPENAPI`)* AsyncAPI (`ASYNCAPI`)* GraphQL (`GRAPHQL`)* Web Services Description Language (`WSDL`)* XML Schema (`XSD`)An artifact will be created using the unique artifact ID that can optionally be provided in the request body. If not provided in the request, the server willgenerate a unique ID for the artifact. It is typically recommended that callersprovide the ID, because it is typically a meaningful identifier, and as suchfor most use cases should be supplied by the caller.If an artifact with the provided artifact ID already exists, the default behavioris for the server to reject the content with a 409 error. However, the caller cansupply the `ifExists` query parameter to alter this default behavior. The `ifExists`query parameter can have one of the following values:* `FAIL` (*default*) - server rejects the content with a 409 error* `UPDATE` - server updates the existing artifact and returns the new metadata* `RETURN` - server does not create or add content to the server, but instead returns the metadata for the existing artifact* `RETURN_OR_UPDATE` - server returns an existing **version** that matches the provided content if such a version exists, otherwise a new version is createdThis operation may fail for one of the following reasons:* An invalid `ArtifactType` was indicated (HTTP error `400`)* No `ArtifactType` was indicated and the server could not determine one from the content (HTTP error `400`)* Provided content (request body) was empty (HTTP error `400`)* An invalid version number was used for the optional included first version (HTTP error `400`)* An artifact with the provided ID already exists (HTTP error `409`)* The content violates one of the configured global rules (HTTP error `409`)* A server error occurred (HTTP error `500`)Note that if the `dryRun` query parameter is set to `true`, then this operationwill not actually make any changes. Instead it will succeed or fail based on whether it **would have worked**. Use this option to, for example, check if anartifact is valid or if a new version passes configured compatibility checks. func (m *ItemArtifactsRequestBuilder) Post(ctx context.Context, body i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateArtifactable, requestConfiguration *ItemArtifactsRequestBuilderPostRequestConfiguration) (i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateArtifactResponseable, error) { requestInfo, err := m.ToPostRequestInformation(ctx, body, requestConfiguration) if err != nil { @@ -178,7 +178,7 @@ func (m *ItemArtifactsRequestBuilder) ToGetRequestInformation(ctx context.Contex return requestInfo, nil } -// ToPostRequestInformation creates a new artifact. The body of the request should be a `CreateArtifact` object, which includes the metadata of the new artifact and, optionally, the metadata and content of the first version.If the artifact type is not provided, the registry attempts to figure out what kind of artifact is being added from thefollowing supported list:* Avro (`AVRO`)* Protobuf (`PROTOBUF`)* JSON Schema (`JSON`)* Kafka Connect (`KCONNECT`)* OpenAPI (`OPENAPI`)* AsyncAPI (`ASYNCAPI`)* GraphQL (`GRAPHQL`)* Web Services Description Language (`WSDL`)* XML Schema (`XSD`)An artifact will be created using the unique artifact ID that can optionally be provided in the request body. If not provided in the request, the server willgenerate a unique ID for the artifact. It is typically recommended that callersprovide the ID, because it is typically a meaningful identifier, and as suchfor most use cases should be supplied by the caller.If an artifact with the provided artifact ID already exists, the default behavioris for the server to reject the content with a 409 error. However, the caller cansupply the `ifExists` query parameter to alter this default behavior. The `ifExists`query parameter can have one of the following values:* `FAIL` (*default*) - server rejects the content with a 409 error* `UPDATE` - server updates the existing artifact and returns the new metadata* `RETURN` - server does not create or add content to the server, but instead returns the metadata for the existing artifact* `RETURN_OR_UPDATE` - server returns an existing **version** that matches the provided content if such a version exists, otherwise a new version is createdThis operation may fail for one of the following reasons:* An invalid `ArtifactType` was indicated (HTTP error `400`)* No `ArtifactType` was indicated and the server could not determine one from the content (HTTP error `400`)* Provided content (request body) was empty (HTTP error `400`)* An artifact with the provided ID already exists (HTTP error `409`)* The content violates one of the configured global rules (HTTP error `409`)* A server error occurred (HTTP error `500`)Note that if the `dryRun` query parameter is set to `true`, then this operationwill not actually make any changes. Instead it will succeed or fail based on whether it **would have worked**. Use this option to, for example, check if anartifact is valid or if a new version passes configured compatibility checks. +// ToPostRequestInformation creates a new artifact. The body of the request should be a `CreateArtifact` object, which includes the metadata of the new artifact and, optionally, the metadata and content of the first version.If the artifact type is not provided, the registry attempts to figure out what kind of artifact is being added from thefollowing supported list:* Avro (`AVRO`)* Protobuf (`PROTOBUF`)* JSON Schema (`JSON`)* Kafka Connect (`KCONNECT`)* OpenAPI (`OPENAPI`)* AsyncAPI (`ASYNCAPI`)* GraphQL (`GRAPHQL`)* Web Services Description Language (`WSDL`)* XML Schema (`XSD`)An artifact will be created using the unique artifact ID that can optionally be provided in the request body. If not provided in the request, the server willgenerate a unique ID for the artifact. It is typically recommended that callersprovide the ID, because it is typically a meaningful identifier, and as suchfor most use cases should be supplied by the caller.If an artifact with the provided artifact ID already exists, the default behavioris for the server to reject the content with a 409 error. However, the caller cansupply the `ifExists` query parameter to alter this default behavior. The `ifExists`query parameter can have one of the following values:* `FAIL` (*default*) - server rejects the content with a 409 error* `UPDATE` - server updates the existing artifact and returns the new metadata* `RETURN` - server does not create or add content to the server, but instead returns the metadata for the existing artifact* `RETURN_OR_UPDATE` - server returns an existing **version** that matches the provided content if such a version exists, otherwise a new version is createdThis operation may fail for one of the following reasons:* An invalid `ArtifactType` was indicated (HTTP error `400`)* No `ArtifactType` was indicated and the server could not determine one from the content (HTTP error `400`)* Provided content (request body) was empty (HTTP error `400`)* An invalid version number was used for the optional included first version (HTTP error `400`)* An artifact with the provided ID already exists (HTTP error `409`)* The content violates one of the configured global rules (HTTP error `409`)* A server error occurred (HTTP error `500`)Note that if the `dryRun` query parameter is set to `true`, then this operationwill not actually make any changes. Instead it will succeed or fail based on whether it **would have worked**. Use this option to, for example, check if anartifact is valid or if a new version passes configured compatibility checks. func (m *ItemArtifactsRequestBuilder) ToPostRequestInformation(ctx context.Context, body i00eb2e63d156923d00d8e86fe16b5d74daf30e363c9f185a8165cb42aa2f2c71.CreateArtifactable, requestConfiguration *ItemArtifactsRequestBuilderPostRequestConfiguration) (*i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestInformation, error) { requestInfo := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewRequestInformationWithMethodAndUrlTemplateAndPathParameters(i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.POST, m.BaseRequestBuilder.UrlTemplate, m.BaseRequestBuilder.PathParameters) if requestConfiguration != nil { diff --git a/go-sdk/pkg/registryclient-v3/kiota-lock.json b/go-sdk/pkg/registryclient-v3/kiota-lock.json index 669ceaf061..82861a80d2 100644 --- a/go-sdk/pkg/registryclient-v3/kiota-lock.json +++ b/go-sdk/pkg/registryclient-v3/kiota-lock.json @@ -1,5 +1,5 @@ { - "descriptionHash": "59083E8DB7CCCE5B538FC8A8FFB0E5797F090B7BF67FB3B1EE9B4F4951AEE1EAF20DB4AC22EBC0DFC4DE5E40E0A35F1E6AF66703EF8A515BD3299DD0F6479BA9", + "descriptionHash": "B46056EBAC7BEBE57948B9F0255841628F4C6DC6F79CC57C6A2C964E2E2A7C36A2D8F58110E85785A37D0C304C9023C70B1AB1322AC2267D304EAA3C503DE5C7", "descriptionLocation": "../../v3.json", "lockFileVersion": "1.0.0", "kiotaVersion": "1.10.1", diff --git a/pom.xml b/pom.xml index 62cb498e88..0f25f34d67 100644 --- a/pom.xml +++ b/pom.xml @@ -198,6 +198,7 @@ 7.0.0 6.10.0.202406032230-r 4.2.2 + 5.2.2 3.7.0 @@ -586,6 +587,11 @@ org.eclipse.jgit ${jgit.version} + + org.semver4j + semver4j + ${semver4j.version} + diff --git a/ui/tests/specs/settings.spec.ts b/ui/tests/specs/settings.spec.ts index 45bc2a1720..56370ccd38 100644 --- a/ui/tests/specs/settings.spec.ts +++ b/ui/tests/specs/settings.spec.ts @@ -12,7 +12,7 @@ test.beforeEach(async ({ page }) => { test("Settings - Filter", async ({ page }) => { await expect(page.getByTestId("settings-search-widget").locator("input")).toBeEmpty(); expect(page.getByTestId("config-groups")).toBeDefined(); - await expect(page.getByTestId("config-groups").locator(".configuration-property")).toHaveCount(8); + await expect(page.getByTestId("config-groups").locator(".configuration-property")).toHaveCount(10); await page.getByTestId("settings-search-widget").locator("input").fill("legacy"); await expect(page.getByTestId("settings-search-widget").locator("input")).toHaveValue("legacy"); diff --git a/ui/ui-app/src/app/components/errorPage/index.ts b/ui/ui-app/src/app/components/errorPage/index.ts index 21b2d82a65..4a7a38915f 100644 --- a/ui/ui-app/src/app/components/errorPage/index.ts +++ b/ui/ui-app/src/app/components/errorPage/index.ts @@ -1,6 +1,6 @@ -export * from "./ErrorPage.tsx"; -export * from "./AccessErrorPage.tsx"; -export * from "./RateLimitErrorPage.tsx"; -export * from "./ConnectionFailedErrorPage.tsx"; -export * from "./NotFoundErrorPage.tsx"; +export * from "./ErrorPage"; +export * from "./AccessErrorPage"; +export * from "./RateLimitErrorPage"; +export * from "./ConnectionFailedErrorPage"; +export * from "./NotFoundErrorPage"; diff --git a/ui/ui-app/src/app/pages/PageErrorHandler.tsx b/ui/ui-app/src/app/pages/PageErrorHandler.tsx index dd174e6942..2a14e19cee 100644 --- a/ui/ui-app/src/app/pages/PageErrorHandler.tsx +++ b/ui/ui-app/src/app/pages/PageErrorHandler.tsx @@ -38,7 +38,9 @@ export const PageErrorHandler: FunctionComponent = (props const is419Error = (): boolean => { return props.error && props.error.error.error_code && (props.error.error.error_code == 419); }; - + + console.debug("Showing page error: ", props.error); + if (isError()) { if (is403Error()) { return ( diff --git a/ui/ui-app/src/app/pages/settings/SettingsPage.tsx b/ui/ui-app/src/app/pages/settings/SettingsPage.tsx index fd488127cc..d5e09391b9 100644 --- a/ui/ui-app/src/app/pages/settings/SettingsPage.tsx +++ b/ui/ui-app/src/app/pages/settings/SettingsPage.tsx @@ -59,6 +59,15 @@ const PROPERTY_GROUPS: PropertyGroup[] = [ "apicurio.ui.features.read-only.enabled" ] }, + { + id: "semver", + label: "Semantic versioning settings", + propertyNames: [ + "apicurio.semver.validation.enabled", + "apicurio.semver.branching.enabled", + "apicurio.semver.branching.coerce" + ] + }, ]; diff --git a/utils/tests/src/main/java/io/apicurio/registry/utils/tests/SemanticVersioningProfile.java b/utils/tests/src/main/java/io/apicurio/registry/utils/tests/SemanticVersioningProfile.java new file mode 100644 index 0000000000..c2c094df13 --- /dev/null +++ b/utils/tests/src/main/java/io/apicurio/registry/utils/tests/SemanticVersioningProfile.java @@ -0,0 +1,18 @@ +package io.apicurio.registry.utils.tests; + +import io.quarkus.test.junit.QuarkusTestProfile; + +import java.util.HashMap; +import java.util.Map; + +public class SemanticVersioningProfile implements QuarkusTestProfile { + + @Override + public Map getConfigOverrides() { + Map props = new HashMap<>(); + props.put("apicurio.semver.validation.enabled", "true"); + props.put("apicurio.semver.branching.enabled", "true"); + props.put("apicurio.semver.branching.coerce", "true"); + return props; + } +}