Serve version format that is backward compatible

This commit makes sure the metadata format uses a backward compatible
version format even if the new format is used. It also introduces a
new metadata version (2.2) that can be used by clients that support the
new version format.

See gh-1092
This commit is contained in:
Stephane Nicoll
2020-06-02 18:06:23 +02:00
parent bcc70551bb
commit 41f844a3ad
21 changed files with 523 additions and 54 deletions

View File

@@ -13,10 +13,16 @@ sent to the service. A good structure for a user agent is `clientId/clientVersio
== Service Capabilities
Any third party client can retrieve the capabilities of the service by issuing a
`GET` on the root URL using the following `Accept` header:
`application/vnd.initializr.v2.1+json`. Please note that the metadata may evolve in a
`application/vnd.initializr.v2.2+json`. Please note that the metadata may evolve in a
non backward compatible way in the future so adding this header ensures the service
returns the metadata format you expect.
The following versions are supported:
* `v2` initial version, with support of V1 version format only
* `v2.1` support compatibility range and dependencies links
* `v2.2` (current) support for V1 and V2 version formats.
This is an example output for a service running at `https://start.spring.io`:
.request

View File

@@ -110,7 +110,7 @@ include::{test-examples}/stub/ClientApplicationTests.java[tag=test]
Then you have a server that returns the stub of the JSON metadata
(`metadataWithCurrentAcceptHeader.json`) when you send it a header
`Accept:application/vnd.initializr.v2.1+json` (as recommended).
`Accept:application/vnd.initializr.v2.2+json` (as recommended).

View File

@@ -31,6 +31,7 @@ import io.spring.initializr.metadata.InvalidInitializrMetadataException;
import io.spring.initializr.web.mapper.DependencyMetadataV21JsonMapper;
import io.spring.initializr.web.mapper.InitializrMetadataJsonMapper;
import io.spring.initializr.web.mapper.InitializrMetadataV21JsonMapper;
import io.spring.initializr.web.mapper.InitializrMetadataV22JsonMapper;
import io.spring.initializr.web.mapper.InitializrMetadataV2JsonMapper;
import io.spring.initializr.web.mapper.InitializrMetadataVersion;
import io.spring.initializr.web.project.InvalidProjectRequestException;
@@ -77,6 +78,11 @@ public class ProjectMetadataController extends AbstractMetadataController {
return serviceCapabilitiesFor(InitializrMetadataVersion.V2_1, HAL_JSON_CONTENT_TYPE);
}
@RequestMapping(path = { "/", "/metadata/client" }, produces = { "application/vnd.initializr.v2.2+json" })
public ResponseEntity<String> serviceCapabilitiesV22() {
return serviceCapabilitiesFor(InitializrMetadataVersion.V2_2);
}
@RequestMapping(path = { "/", "/metadata/client" },
produces = { "application/vnd.initializr.v2.1+json", "application/json" })
public ResponseEntity<String> serviceCapabilitiesV21() {
@@ -147,8 +153,10 @@ public class ProjectMetadataController extends AbstractMetadataController {
switch (version) {
case V2:
return new InitializrMetadataV2JsonMapper();
default:
case V2_1:
return new InitializrMetadataV21JsonMapper();
default:
return new InitializrMetadataV22JsonMapper();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,8 @@ package io.spring.initializr.web.mapper;
import java.util.List;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.spring.initializr.generator.version.Version.Format;
import io.spring.initializr.generator.version.VersionRange;
import io.spring.initializr.metadata.Dependency;
import io.spring.initializr.metadata.Type;
@@ -55,8 +57,8 @@ public class InitializrMetadataV21JsonMapper extends InitializrMetadataV2JsonMap
@Override
protected ObjectNode mapDependency(Dependency dependency) {
ObjectNode content = mapValue(dependency);
if (dependency.getCompatibilityRange() != null) {
content.put("versionRange", dependency.getCompatibilityRange());
if (dependency.getRange() != null) {
content.put("versionRange", formatVersionRange(dependency.getRange()));
}
if (dependency.getLinks() != null && !dependency.getLinks().isEmpty()) {
content.set("_links", LinkMapper.mapLinks(dependency.getLinks()));
@@ -64,6 +66,10 @@ public class InitializrMetadataV21JsonMapper extends InitializrMetadataV2JsonMap
return content;
}
protected String formatVersionRange(VersionRange versionRange) {
return versionRange.format(Format.V1).toRangeString();
}
private ObjectNode dependenciesLink(String appUrl) {
String uri = (appUrl != null) ? appUrl + "/dependencies" : "/dependencies";
UriTemplate uriTemplate = UriTemplate.of(uri, this.dependenciesVariables);

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.web.mapper;
import io.spring.initializr.generator.version.Version;
import io.spring.initializr.generator.version.VersionRange;
/**
* A {@link InitializrMetadataJsonMapper} handling the metadata format for v2.2
* <p>
* Version 2.2 adds support for {@linkplain Version.Format#V2 SemVer version format}. Any
* previous version formats versions to {@link Version.Format#V1}.
*
* @author Stephane Nicoll
*/
public class InitializrMetadataV22JsonMapper extends InitializrMetadataV21JsonMapper {
@Override
protected String formatVersion(String versionId) {
return versionId;
}
@Override
protected String formatVersionRange(VersionRange versionRange) {
return versionRange.toRangeString();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,12 +17,16 @@
package io.spring.initializr.web.mapper;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.spring.initializr.generator.version.Version;
import io.spring.initializr.generator.version.Version.Format;
import io.spring.initializr.generator.version.VersionParser;
import io.spring.initializr.metadata.DefaultMetadataElement;
import io.spring.initializr.metadata.DependenciesCapability;
import io.spring.initializr.metadata.Dependency;
@@ -79,7 +83,7 @@ public class InitializrMetadataV2JsonMapper implements InitializrMetadataJsonMap
singleSelect(delegate, metadata.getPackagings());
singleSelect(delegate, metadata.getJavaVersions());
singleSelect(delegate, metadata.getLanguages());
singleSelect(delegate, metadata.getBootVersions());
singleSelect(delegate, metadata.getBootVersions(), this::mapVersionMetadata);
text(delegate, metadata.getGroupId());
text(delegate, metadata.getArtifactId());
text(delegate, metadata.getVersion());
@@ -133,6 +137,11 @@ public class InitializrMetadataV2JsonMapper implements InitializrMetadataJsonMap
}
protected void singleSelect(ObjectNode parent, SingleSelectCapability capability) {
singleSelect(parent, capability, this::mapValue);
}
protected void singleSelect(ObjectNode parent, SingleSelectCapability capability,
Function<MetadataElement, ObjectNode> valueMapper) {
ObjectNode single = nodeFactory.objectNode();
single.put("type", capability.getType().getName());
DefaultMetadataElement defaultType = capability.getDefault();
@@ -140,7 +149,7 @@ public class InitializrMetadataV2JsonMapper implements InitializrMetadataJsonMap
single.put("default", defaultType.getId());
}
ArrayNode values = nodeFactory.arrayNode();
values.addAll(capability.getContent().stream().map(this::mapValue).collect(Collectors.toList()));
values.addAll(capability.getContent().stream().map(valueMapper).collect(Collectors.toList()));
single.set("values", values);
parent.set(capability.getId(), single);
}
@@ -189,6 +198,18 @@ public class InitializrMetadataV2JsonMapper implements InitializrMetadataJsonMap
return result;
}
private ObjectNode mapVersionMetadata(MetadataElement value) {
ObjectNode result = nodeFactory.objectNode();
result.put("id", formatVersion(value.getId()));
result.put("name", value.getName());
return result;
}
protected String formatVersion(String versionId) {
Version version = VersionParser.DEFAULT.safeParse(versionId);
return (version != null) ? version.format(Format.V1).toString() : versionId;
}
protected ObjectNode mapValue(MetadataElement value) {
ObjectNode result = nodeFactory.objectNode();
result.put("id", value.getId());

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,12 @@ public enum InitializrMetadataVersion {
* versions are compatible with it. Also provide a separate "dependencies" endpoint to
* query dependencies metadata.
*/
V2_1("application/vnd.initializr.v2.1+json");
V2_1("application/vnd.initializr.v2.1+json"),
/**
* Add support for SemVer compliant version format.
*/
V2_2("application/vnd.initializr.v2.2+json");
private final MediaType mediaType;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -72,7 +72,9 @@ import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(classes = Config.class)
public abstract class AbstractInitializrIntegrationTests {
protected static final MediaType CURRENT_METADATA_MEDIA_TYPE = InitializrMetadataVersion.V2_1.getMediaType();
protected static final MediaType DEFAULT_METADATA_MEDIA_TYPE = InitializrMetadataVersion.V2_1.getMediaType();
protected static final MediaType CURRENT_METADATA_MEDIA_TYPE = InitializrMetadataVersion.V2_2.getMediaType();
private static final ObjectMapper objectMapper = new ObjectMapper();
@@ -125,14 +127,23 @@ public abstract class AbstractInitializrIntegrationTests {
}
}
protected void validateCurrentMetadata(ResponseEntity<String> response) {
validateContentType(response, CURRENT_METADATA_MEDIA_TYPE);
validateCurrentMetadata(response.getBody());
protected void validateDefaultMetadata(ResponseEntity<String> response) {
validateContentType(response, DEFAULT_METADATA_MEDIA_TYPE);
validateMetadata(response.getBody(), "2.1.0");
}
protected void validateCurrentMetadata(String json) {
protected void validateCurrentMetadata(ResponseEntity<String> response) {
validateContentType(response, CURRENT_METADATA_MEDIA_TYPE);
validateMetadata(response.getBody(), "2.2.0");
}
protected void validateDefaultMetadata(String json) {
validateMetadata(json, "2.1.0");
}
protected void validateMetadata(String json, String version) {
try {
JSONObject expected = readMetadataJson("2.1.0");
JSONObject expected = readMetadataJson(version);
JSONAssert.assertEquals(expected, new JSONObject(json), JSONCompareMode.STRICT);
}
catch (JSONException ex) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,8 +45,8 @@ public class CommandLineMetadataControllerIntegrationTests extends AbstractIniti
// make sure curl can still receive metadata with json
void curlWithAcceptHeaderJson() {
ResponseEntity<String> response = invokeHome("curl/1.2.4", "application/json");
validateContentType(response, AbstractInitializrIntegrationTests.CURRENT_METADATA_MEDIA_TYPE);
validateCurrentMetadata(response.getBody());
validateContentType(response, AbstractInitializrIntegrationTests.DEFAULT_METADATA_MEDIA_TYPE);
validateDefaultMetadata(response.getBody());
}
@Test
@@ -65,8 +65,8 @@ public class CommandLineMetadataControllerIntegrationTests extends AbstractIniti
// make sure curl can still receive metadata with json
void httpieWithAcceptHeaderJson() {
ResponseEntity<String> response = invokeHome("HTTPie/0.8.0", "application/json");
validateContentType(response, AbstractInitializrIntegrationTests.CURRENT_METADATA_MEDIA_TYPE);
validateCurrentMetadata(response.getBody());
validateContentType(response, AbstractInitializrIntegrationTests.DEFAULT_METADATA_MEDIA_TYPE);
validateDefaultMetadata(response.getBody());
}
@Test
@@ -84,8 +84,8 @@ public class CommandLineMetadataControllerIntegrationTests extends AbstractIniti
@Test
void springBootCliReceivesJsonByDefault() {
ResponseEntity<String> response = invokeHome("SpringBootCli/1.2.0", "*/*");
validateContentType(response, AbstractInitializrIntegrationTests.CURRENT_METADATA_MEDIA_TYPE);
validateCurrentMetadata(response.getBody());
validateContentType(response, AbstractInitializrIntegrationTests.DEFAULT_METADATA_MEDIA_TYPE);
validateDefaultMetadata(response.getBody());
}
@Test

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -84,24 +84,24 @@ class ProjectMetadataControllerCustomDefaultsIntegrationTests extends AbstractFu
@Test
void metadataClientEndpoint() {
ResponseEntity<String> response = execute("/metadata/client", String.class, null, "application/json");
validateCurrentMetadata(response);
validateDefaultMetadata(response);
}
@Test
void noBootVersion() throws JSONException {
ResponseEntity<String> response = execute("/dependencies", String.class, null, "application/json");
assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull();
validateContentType(response, CURRENT_METADATA_MEDIA_TYPE);
validateContentType(response, DEFAULT_METADATA_MEDIA_TYPE);
validateDependenciesOutput("2.1.4", response.getBody());
}
@Test
void filteredDependencies() throws JSONException {
ResponseEntity<String> response = execute("/dependencies?bootVersion=2.2.1.RELEASE", String.class, null,
ResponseEntity<String> response = execute("/dependencies?bootVersion=2.4.1.RELEASE", String.class, null,
"application/json");
assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull();
validateContentType(response, CURRENT_METADATA_MEDIA_TYPE);
validateDependenciesOutput("2.2.1", response.getBody());
validateContentType(response, DEFAULT_METADATA_MEDIA_TYPE);
validateDependenciesOutput("2.4.1", response.getBody());
}
protected void validateDependenciesOutput(String version, String actual) throws JSONException {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,7 +43,7 @@ public class ProjectMetadataControllerIntegrationTests extends AbstractInitializ
void metadataWithNoAcceptHeader() {
// rest template sets application/json by default
ResponseEntity<String> response = invokeHome(null, "*/*");
validateCurrentMetadata(response);
validateDefaultMetadata(response);
}
@Test
@@ -60,6 +60,18 @@ public class ProjectMetadataControllerIntegrationTests extends AbstractInitializ
validateMetadata(response, InitializrMetadataVersion.V2.getMediaType(), "2.0.0", JSONCompareMode.STRICT);
}
@Test
void metadataWithV21AcceptHeader() {
ResponseEntity<String> response = invokeHome(null, "application/vnd.initializr.v2.1+json");
validateMetadata(response, InitializrMetadataVersion.V2_1.getMediaType(), "2.1.0", JSONCompareMode.STRICT);
}
@Test
void metadataWithV22AcceptHeader() {
ResponseEntity<String> response = invokeHome(null, "application/vnd.initializr.v2.2+json");
validateMetadata(response, InitializrMetadataVersion.V2_2.getMediaType(), "2.2.0", JSONCompareMode.STRICT);
}
@Test
void metadataWithInvalidPlatformVersion() {
try {
@@ -76,18 +88,18 @@ public class ProjectMetadataControllerIntegrationTests extends AbstractInitializ
void metadataWithCurrentAcceptHeader() {
getRequests().setFields("_links.maven-project", "dependencies.values[0]", "type.values[0]",
"javaVersion.values[0]", "packaging.values[0]", "bootVersion.values[0]", "language.values[0]");
ResponseEntity<String> response = invokeHome(null, "application/vnd.initializr.v2.1+json");
ResponseEntity<String> response = invokeHome(null, "application/vnd.initializr.v2.2+json");
assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull();
validateContentType(response, AbstractInitializrIntegrationTests.CURRENT_METADATA_MEDIA_TYPE);
validateCurrentMetadata(response.getBody());
validateMetadata(response.getBody(), "2.2.0");
}
@Test
void metadataWithSeveralAcceptHeader() {
ResponseEntity<String> response = invokeHome(null, "application/vnd.initializr.v2.1+json",
ResponseEntity<String> response = invokeHome(null, "application/vnd.initializr.v2.2+json",
"application/vnd.initializr.v2+json");
validateContentType(response, AbstractInitializrIntegrationTests.CURRENT_METADATA_MEDIA_TYPE);
validateCurrentMetadata(response.getBody());
validateCurrentMetadata(response);
}
@Test
@@ -95,7 +107,7 @@ public class ProjectMetadataControllerIntegrationTests extends AbstractInitializ
ResponseEntity<String> response = invokeHome(null, "application/hal+json");
assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull();
validateContentType(response, ProjectMetadataController.HAL_JSON_CONTENT_TYPE);
validateCurrentMetadata(response.getBody());
validateDefaultMetadata(response.getBody());
}
@Test
@@ -117,13 +129,13 @@ public class ProjectMetadataControllerIntegrationTests extends AbstractInitializ
@Test
void unknownAgentReceivesJsonByDefault() {
ResponseEntity<String> response = invokeHome("foo/1.0", "*/*");
validateCurrentMetadata(response);
validateDefaultMetadata(response);
}
@Test
// Test that the current output is exactly what we expect
void validateCurrentProjectMetadata() {
validateCurrentMetadata(getMetadataJson());
validateDefaultMetadata(getMetadataJson());
}
private String getMetadataJson() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,8 +18,10 @@ package io.spring.initializr.web.mapper;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.spring.initializr.generator.test.InitializrMetadataTestBuilder;
import io.spring.initializr.metadata.Dependency;
import io.spring.initializr.metadata.InitializrMetadata;
@@ -29,13 +31,15 @@ import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link InitializrMetadataV21JsonMapper}.
*
* @author Stephane Nicoll
*/
class InitializrMetadataJsonMapperTests {
class InitializrMetadataV21JsonMapperTests {
private static final ObjectMapper objectMapper = new ObjectMapper();
private final InitializrMetadataJsonMapper jsonMapper = new InitializrMetadataV21JsonMapper();
private final InitializrMetadataV21JsonMapper jsonMapper = new InitializrMetadataV21JsonMapper();
@Test
void withNoAppUrl() throws IOException {
@@ -74,6 +78,42 @@ class InitializrMetadataJsonMapperTests {
assertThat(second).isGreaterThan(0);
}
@Test
void versionRangesUsingSemVerUseBackwardCompatibleFormat() {
Dependency dependency = Dependency.withId("test");
dependency.setCompatibilityRange("[1.1.1-RC1,1.2.0-M1)");
dependency.resolve();
ObjectNode node = this.jsonMapper.mapDependency(dependency);
assertThat(node.get("versionRange").textValue()).isEqualTo("[1.1.1.RC1,1.2.0.M1)");
}
@Test
void versionRangesUsingSemVerSnapshotReplacedByBackwardCompatibleSnapshotQualifier() {
Dependency dependency = Dependency.withId("test");
dependency.setCompatibilityRange("1.2.0-SNAPSHOT");
dependency.resolve();
ObjectNode node = this.jsonMapper.mapDependency(dependency);
assertThat(node.get("versionRange").textValue()).isEqualTo("1.2.0.BUILD-SNAPSHOT");
}
@Test
void platformVersionUsingSemVerUseBackwardCompatibleFormat() throws JsonProcessingException {
InitializrMetadata metadata = new InitializrMetadataTestBuilder().addBootVersion("2.5.0-SNAPSHOT", false)
.addBootVersion("2.5.0-M2", false).addBootVersion("2.4.2", true).build();
String json = this.jsonMapper.write(metadata, null);
JsonNode result = objectMapper.readTree(json);
JsonNode versions = result.get("bootVersion").get("values");
assertThat(versions).hasSize(3);
assertVersionMetadata(versions.get(0), "2.5.0.BUILD-SNAPSHOT", "2.5.0-SNAPSHOT");
assertVersionMetadata(versions.get(1), "2.5.0.M2", "2.5.0-M2");
assertVersionMetadata(versions.get(2), "2.4.2.RELEASE", "2.4.2");
}
private void assertVersionMetadata(JsonNode node, String id, String name) {
assertThat(node.get("id").textValue()).isEqualTo(id);
assertThat(node.get("name").textValue()).isEqualTo(name);
}
private Object get(JsonNode result, String path) {
String[] nodes = path.split("\\.");
for (int i = 0; i < nodes.length - 1; i++) {

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.web.mapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.spring.initializr.generator.test.InitializrMetadataTestBuilder;
import io.spring.initializr.metadata.Dependency;
import io.spring.initializr.metadata.InitializrMetadata;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link InitializrMetadataV22JsonMapper}.
*
* @author Stephane Nicoll
*/
class InitializrMetadataV22JsonMapperTests {
private static final ObjectMapper objectMapper = new ObjectMapper();
private final InitializrMetadataV22JsonMapper jsonMapper = new InitializrMetadataV22JsonMapper();
@Test
void versionRangesUsingSemVerIsNotChanged() {
Dependency dependency = Dependency.withId("test");
dependency.setCompatibilityRange("[1.1.1-RC1,1.2.0-M1)");
dependency.resolve();
ObjectNode node = this.jsonMapper.mapDependency(dependency);
assertThat(node.get("versionRange").textValue()).isEqualTo("[1.1.1-RC1,1.2.0-M1)");
}
@Test
void versionRangesUsingSemVerSnapshotIsNotChanged() {
Dependency dependency = Dependency.withId("test");
dependency.setCompatibilityRange("1.2.0-SNAPSHOT");
dependency.resolve();
ObjectNode node = this.jsonMapper.mapDependency(dependency);
assertThat(node.get("versionRange").textValue()).isEqualTo("1.2.0-SNAPSHOT");
}
@Test
void platformVersionUsingSemVerUIsNotChanged() throws JsonProcessingException {
InitializrMetadata metadata = new InitializrMetadataTestBuilder().addBootVersion("2.5.0-SNAPSHOT", false)
.addBootVersion("2.5.0-M2", false).addBootVersion("2.4.2", true).build();
String json = this.jsonMapper.write(metadata, null);
JsonNode result = objectMapper.readTree(json);
JsonNode versions = result.get("bootVersion").get("values");
assertThat(versions).hasSize(3);
assertVersionMetadata(versions.get(0), "2.5.0-SNAPSHOT", "2.5.0-SNAPSHOT");
assertVersionMetadata(versions.get(1), "2.5.0-M2", "2.5.0-M2");
assertVersionMetadata(versions.get(2), "2.4.2", "2.4.2");
}
private void assertVersionMetadata(JsonNode node, String id, String name) {
assertThat(node.get("id").textValue()).isEqualTo(id);
assertThat(node.get("name").textValue()).isEqualTo(name);
}
private Object get(JsonNode result, String path) {
String[] nodes = path.split("\\.");
for (int i = 0; i < nodes.length - 1; i++) {
String node = nodes[i];
result = result.path(node);
}
return result.get(nodes[nodes.length - 1]).textValue();
}
}

View File

@@ -90,7 +90,7 @@ initializr:
id: org.acme:bur
version: 2.1.0
scope: test
compatibilityRange: "[2.1.4.RELEASE,2.2.0.BUILD-SNAPSHOT)"
compatibilityRange: "[2.1.4.RELEASE,2.4.0-SNAPSHOT)"
- name: My API
id : my-api
groupId: org.acme
@@ -152,7 +152,7 @@ initializr:
default: false
bootVersions:
- name : Latest SNAPSHOT
id: 2.2.0.BUILD-SNAPSHOT
id: 2.4.0-SNAPSHOT
default: false
- name: 2.1.4
id: 2.1.4.RELEASE

View File

@@ -10,7 +10,7 @@
"content": [
{
"default": false,
"id": "2.2.0.BUILD-SNAPSHOT",
"id": "2.4.0-SNAPSHOT",
"name": "Latest SNAPSHOT"
},
{
@@ -230,7 +230,7 @@
"name": "Bur",
"scope": "test",
"version": "2.1.0",
"compatibilityRange": "[2.1.4.RELEASE,2.2.0.BUILD-SNAPSHOT)"
"compatibilityRange": "[2.1.4.RELEASE,2.4.0-SNAPSHOT)"
},
{
"starter": true,

View File

@@ -1,5 +1,5 @@
{
"bootVersion": "2.2.1.RELEASE",
"bootVersion": "2.4.1.RELEASE",
"repositories": {
"my-api-repo-2": {
"name": "repo2",

View File

@@ -154,7 +154,7 @@
"default": "2.1.4.RELEASE",
"values": [
{
"id": "2.2.0.BUILD-SNAPSHOT",
"id": "2.4.0.BUILD-SNAPSHOT",
"name": "Latest SNAPSHOT"
},
{

View File

@@ -154,7 +154,7 @@
"default": "2.1.4.RELEASE",
"values": [
{
"id": "2.2.0.BUILD-SNAPSHOT",
"id": "2.4.0.BUILD-SNAPSHOT",
"name": "Latest SNAPSHOT"
},
{

View File

@@ -85,7 +85,7 @@
{
"id": "org.acme:bur",
"name": "Bur",
"versionRange": "[2.1.4.RELEASE,2.2.0.BUILD-SNAPSHOT)"
"versionRange": "[2.1.4.RELEASE,2.4.0.BUILD-SNAPSHOT)"
},
{
"id": "my-api",
@@ -192,7 +192,7 @@
"default": "2.1.4.RELEASE",
"values": [
{
"id": "2.2.0.BUILD-SNAPSHOT",
"id": "2.4.0.BUILD-SNAPSHOT",
"name": "Latest SNAPSHOT"
},
{

View File

@@ -85,7 +85,7 @@
{
"id": "org.acme:bur",
"name": "Bur",
"versionRange": "[2.1.4.RELEASE,2.2.0.BUILD-SNAPSHOT)"
"versionRange": "[2.1.4.RELEASE,2.4.0.BUILD-SNAPSHOT)"
},
{
"id": "my-api",
@@ -192,7 +192,7 @@
"default": "2.1.4.RELEASE",
"values": [
{
"id": "2.2.0.BUILD-SNAPSHOT",
"id": "2.4.0.BUILD-SNAPSHOT",
"name": "Latest SNAPSHOT"
},
{

View File

@@ -0,0 +1,232 @@
{
"_links": {
"dependencies": {
"href": "http://@host@/dependencies{?bootVersion}",
"templated": true
},
"maven-build": {
"href": "http://@host@/pom.xml?type=maven-build{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}",
"templated": true
},
"maven-project": {
"href": "http://@host@/starter.zip?type=maven-project{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}",
"templated": true
},
"gradle-build": {
"href": "http://@host@/build.gradle?type=gradle-build{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}",
"templated": true
},
"gradle-project": {
"href": "http://@host@/starter.zip?type=gradle-project{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName}",
"templated": true
}
},
"dependencies": {
"type": "hierarchical-multi-select",
"values": [
{
"name": "Core",
"values": [
{
"id": "web",
"name": "Web",
"description": "Web dependency description",
"_links": {
"guide": {
"href": "https://example.com/guide",
"title": "Building a RESTful Web Service"
},
"reference": {
"href": "https://example.com/doc"
}
}
},
{
"id": "security",
"name": "Security"
},
{
"id": "data-jpa",
"name": "Data JPA"
}
]
},
{
"name": "Other",
"values": [
{
"id": "org.acme:foo",
"name": "Foo",
"_links": {
"guide": [
{
"href": "https://example.com/guide1"
},
{
"href": "https://example.com/guide2",
"title": "Some guide for foo"
}
],
"reference": {
"href": "https://example.com/{bootVersion}/doc",
"templated": true
}
}
},
{
"id": "org.acme:bar",
"name": "Bar"
},
{
"id": "org.acme:biz",
"name": "Biz",
"versionRange": "2.2.0.BUILD-SNAPSHOT"
},
{
"id": "org.acme:bur",
"name": "Bur",
"versionRange": "[2.1.4.RELEASE,2.4.0-SNAPSHOT)"
},
{
"id": "my-api",
"name": "My API"
}
]
}
]
},
"type": {
"type": "action",
"default": "maven-project",
"values": [
{
"id": "maven-build",
"name": "Maven POM",
"action": "/pom.xml",
"tags": {
"build": "maven",
"format": "build"
}
},
{
"id": "maven-project",
"name": "Maven Project",
"action": "/starter.zip",
"tags": {
"build": "maven",
"format": "project"
}
},
{
"id": "gradle-build",
"name": "Gradle Config",
"action": "/build.gradle",
"tags": {
"build": "gradle",
"format": "build"
}
},
{
"id": "gradle-project",
"name": "Gradle Project",
"action": "/starter.zip",
"tags": {
"build": "gradle",
"format": "project"
}
}
]
},
"packaging": {
"type": "single-select",
"default": "jar",
"values": [
{
"id": "jar",
"name": "Jar"
},
{
"id": "war",
"name": "War"
}
]
},
"javaVersion": {
"type": "single-select",
"default": "1.8",
"values": [
{
"id": "1.6",
"name": "1.6"
},
{
"id": "1.7",
"name": "1.7"
},
{
"id": "1.8",
"name": "1.8"
}
]
},
"language": {
"type": "single-select",
"default": "java",
"values": [
{
"id": "groovy",
"name": "Groovy"
},
{
"id": "java",
"name": "Java"
},
{
"id": "kotlin",
"name": "Kotlin"
}
]
},
"bootVersion": {
"type": "single-select",
"default": "2.1.4.RELEASE",
"values": [
{
"id": "2.4.0-SNAPSHOT",
"name": "Latest SNAPSHOT"
},
{
"id": "2.1.4.RELEASE",
"name": "2.1.4"
},
{
"id": "1.5.17.RELEASE",
"name": "1.5.17"
}
]
},
"groupId": {
"type": "text",
"default": "com.example"
},
"artifactId": {
"type": "text",
"default": "demo"
},
"version": {
"type": "text",
"default": "0.0.1-SNAPSHOT"
},
"name": {
"type": "text",
"default": "demo"
},
"description": {
"type": "text",
"default": "Demo project for Spring Boot"
},
"packageName": {
"type": "text",
"default": "com.example.demo"
}
}