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 { resolvedDependencies = depIds.collect {
def dependency = metadata.dependencies.get(it) def dependency = metadata.dependencies.get(it)
if (dependency == null) { if (dependency == null) {
if (it.contains(':')) {
throw new InvalidProjectRequestException("Unknown dependency '$it' check project metadata") 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) dependency.resolve(requestedVersion)
} }
resolvedDependencies.each { resolvedDependencies.each {

View File

@ -550,6 +550,18 @@ class ProjectGeneratorTests extends AbstractProjectGeneratorTests {
.hasDependenciesCount(3) .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 @Test
void invalidType() { void invalidType() {
def request = createProjectRequest('web') def request = createProjectRequest('web')

View File

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

View File

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

View File

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