Enable strict validation of dependencies

Previously, if one invokes the service asking for dependency `foo` and
`foo` does not exist we created a starter for it, that is
`spring-boot-starter-foo`. This mechanism was put in place because we
don't know all starters and to offer a nice fallback for users who know
what they're doing.

That statement proved to be wrong since users actually discover new
starters via the service and don't seem to attempt to create such starter
that are located in the `org.springframework.boot` groupId anyway. Most
if not all of those are pilot errors.

This commit enables strict validation of dependencies and generate an
appropriate exception if it isn't defined in the meta-data.

Closes gh-234
This commit is contained in:
Stephane Nicoll 2016-07-08 14:37:41 +02:00
parent a3d072f20a
commit 0ac320e5ae
5 changed files with 41 additions and 18 deletions

View File

@ -78,13 +78,8 @@ class ProjectRequest extends BasicProjectRequest {
resolvedDependencies = depIds.collect {
def dependency = metadata.dependencies.get(it)
if (dependency == null) {
if (it.contains(':')) {
throw new InvalidProjectRequestException("Unknown dependency '$it' check project metadata")
}
log.warn("No known dependency for style '$it' assuming spring-boot-starter")
dependency = new Dependency()
dependency.asSpringBootStarter(it)
}
dependency.resolve(requestedVersion)
}
resolvedDependencies.each {

View File

@ -550,6 +550,18 @@ class ProjectGeneratorTests extends AbstractProjectGeneratorTests {
.hasDependenciesCount(3)
}
@Test
void invalidDependency() {
def request = createProjectRequest('foo-bar')
try {
generateMavenPom(request)
fail("Should have failed to generate project")
} catch (InvalidProjectRequestException ex) {
assertThat ex.message, containsString('foo-bar')
verifyProjectFailedEventFor(request, ex)
}
}
@Test
void invalidType() {
def request = createProjectRequest('web')

View File

@ -100,15 +100,17 @@ class ProjectRequestTests {
}
@Test
void resolveUnknownSimpleIdAsSpringBootStarter() {
void resolveUnknownSimpleId() {
def request = new ProjectRequest()
def metadata = InitializrMetadataTestBuilder.withDefaults()
.addDependencyGroup('code', 'org.foo:bar').build()
request.style << 'org.foo:bar' << 'foo-bar'
thrown.expect(InvalidProjectRequestException)
thrown.expectMessage('foo-bar')
request.resolve(metadata)
assertDependency(request.resolvedDependencies[0], 'org.foo', 'bar', null)
assertBootStarter(request.resolvedDependencies[1], 'foo-bar')
assertEquals(1, request.resolvedDependencies.size())
}
@Test

View File

@ -17,6 +17,7 @@
package io.spring.initializr.web.project
import javax.annotation.PostConstruct
import javax.servlet.http.HttpServletResponse
import io.spring.initializr.generator.InvalidProjectRequestException
import io.spring.initializr.metadata.InitializrMetadataProvider
@ -24,7 +25,6 @@ import io.spring.initializr.metadata.InitializrMetadataProvider
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.servlet.support.ServletUriComponentsBuilder
import static io.spring.initializr.util.GroovyTemplate.template
@ -47,10 +47,9 @@ abstract class AbstractInitializrController {
forceSsl = metadataProvider.get().configuration.env.forceSsl
}
@ExceptionHandler(InvalidProjectRequestException)
@ResponseStatus(value= HttpStatus.BAD_REQUEST)
public void invalidProjectVersion() {
// Nothing to do
@ExceptionHandler
public void invalidProjectRequest(HttpServletResponse response, InvalidProjectRequestException ex) {
response.sendError(HttpStatus.BAD_REQUEST.value(), ex.getMessage());
}
/**

View File

@ -16,10 +16,10 @@
package io.spring.initializr.web.project
import groovy.json.JsonSlurper
import io.spring.initializr.metadata.Dependency
import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests
import io.spring.initializr.web.mapper.InitializrMetadataVersion
import io.spring.initializr.web.project.MainController
import org.json.JSONObject
import org.junit.Ignore
import org.junit.Test
@ -41,6 +41,7 @@ import static org.junit.Assert.assertFalse
import static org.junit.Assert.assertNotNull
import static org.junit.Assert.assertThat
import static org.junit.Assert.assertTrue
import static org.junit.Assert.fail
/**
* @author Stephane Nicoll
@ -326,15 +327,24 @@ class MainControllerIntegrationTests extends AbstractInitializrControllerIntegra
void missingDependencyProperException() {
try {
downloadArchive('/starter.zip?style=foo:bar')
fail("Should have failed")
} catch (HttpClientErrorException ex) {
assertEquals HttpStatus.BAD_REQUEST, ex.getStatusCode()
assertStandardErrorBody(ex.getResponseBodyAsString(),
"Unknown dependency 'foo:bar' check project metadata")
}
}
@Test
void downloadWithUnknownSpringBootStarter() { // Simple id are accepted as spring-boot-starter
downloadZip('/starter.zip?style=foo').pomAssert().hasSpringBootStarterDependency('foo')
void invalidDependencyProperException() {
try {
downloadArchive('/starter.zip?style=foo')
fail("Should have failed")
} catch (HttpClientErrorException ex) {
assertEquals HttpStatus.BAD_REQUEST, ex.getStatusCode()
assertStandardErrorBody(ex.getResponseBodyAsString(),
"Unknown dependency 'foo' check project metadata")
}
}
@Test
@ -412,5 +422,10 @@ class MainControllerIntegrationTests extends AbstractInitializrControllerIntegra
return new JSONObject(json)
}
private static void assertStandardErrorBody(String body, String message) {
assertNotNull "error body must be available", body
def model = new JsonSlurper().parseText(body)
assertEquals message, model.message
}
}