From 7350cd165d01ab25e55bf4ed1115f6949ed06bb0 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Sat, 25 Oct 2014 15:36:45 +0200 Subject: [PATCH] Proper exception management This commit updates the service to throw dedicated exception types for common user error and associates proper HTTP status codes to them. Fixes gh-38 --- .../initializr/InitializrException.groovy | 29 ++++++++++++++++ .../InvalidInitializrMetadataException.groovy | 3 +- .../InvalidProjectRequestException.groovy | 33 +++++++++++++++++++ .../spring/initializr/ProjectRequest.groovy | 2 +- .../initializr/web/MainController.groovy | 4 +-- .../initializr/ProjectRequestTests.groovy | 2 +- .../web/MainControllerIntegrationTests.groovy | 31 ++++++++++++++--- 7 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 initializr/src/main/groovy/io/spring/initializr/InitializrException.groovy create mode 100644 initializr/src/main/groovy/io/spring/initializr/InvalidProjectRequestException.groovy diff --git a/initializr/src/main/groovy/io/spring/initializr/InitializrException.groovy b/initializr/src/main/groovy/io/spring/initializr/InitializrException.groovy new file mode 100644 index 00000000..d2577f1f --- /dev/null +++ b/initializr/src/main/groovy/io/spring/initializr/InitializrException.groovy @@ -0,0 +1,29 @@ +/* + * Copyright 2012-2014 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 + * + * http://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 + +import groovy.transform.InheritConstructors + +/** + * Base Initializr exception. + * + * @author Stephane Nicoll + * @since 1.0 + */ +@InheritConstructors +class InitializrException extends RuntimeException { +} diff --git a/initializr/src/main/groovy/io/spring/initializr/InvalidInitializrMetadataException.groovy b/initializr/src/main/groovy/io/spring/initializr/InvalidInitializrMetadataException.groovy index e11f842b..ca5b6b11 100644 --- a/initializr/src/main/groovy/io/spring/initializr/InvalidInitializrMetadataException.groovy +++ b/initializr/src/main/groovy/io/spring/initializr/InvalidInitializrMetadataException.groovy @@ -25,7 +25,6 @@ import groovy.transform.InheritConstructors * @since 1.0 */ @InheritConstructors -class InvalidInitializrMetadataException extends RuntimeException { - +class InvalidInitializrMetadataException extends InitializrException { } diff --git a/initializr/src/main/groovy/io/spring/initializr/InvalidProjectRequestException.groovy b/initializr/src/main/groovy/io/spring/initializr/InvalidProjectRequestException.groovy new file mode 100644 index 00000000..2c3d4370 --- /dev/null +++ b/initializr/src/main/groovy/io/spring/initializr/InvalidProjectRequestException.groovy @@ -0,0 +1,33 @@ +/* + * Copyright 2012-2014 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 + * + * http://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 + +import groovy.transform.InheritConstructors + +import org.springframework.http.HttpStatus +import org.springframework.web.bind.annotation.ResponseStatus + +/** + * Thrown when a {@link ProjectRequest} is invalid. + * + * @author Stephane Nicoll + * @since 1.0 + */ +@InheritConstructors +@ResponseStatus(HttpStatus.BAD_REQUEST) +class InvalidProjectRequestException extends InitializrException { +} diff --git a/initializr/src/main/groovy/io/spring/initializr/ProjectRequest.groovy b/initializr/src/main/groovy/io/spring/initializr/ProjectRequest.groovy index 9c780b92..cdcbe245 100644 --- a/initializr/src/main/groovy/io/spring/initializr/ProjectRequest.groovy +++ b/initializr/src/main/groovy/io/spring/initializr/ProjectRequest.groovy @@ -57,7 +57,7 @@ class ProjectRequest { def dependency = metadata.getDependency(it) if (dependency == null) { if (it.contains(':')) { - throw new IllegalArgumentException("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 InitializrMetadata.Dependency() diff --git a/initializr/src/main/groovy/io/spring/initializr/web/MainController.groovy b/initializr/src/main/groovy/io/spring/initializr/web/MainController.groovy index 86477770..d38af95e 100644 --- a/initializr/src/main/groovy/io/spring/initializr/web/MainController.groovy +++ b/initializr/src/main/groovy/io/spring/initializr/web/MainController.groovy @@ -104,7 +104,7 @@ class MainController extends AbstractInitializrController { upload(download, dir, generateFileName(request, 'zip'), 'application/zip') } - @RequestMapping(value='/starter.tgz', produces='application/x-compress') + @RequestMapping(value = '/starter.tgz', produces = 'application/x-compress') @ResponseBody ResponseEntity springTgz(ProjectRequest request) { def dir = projectGenerator.generateProjectStructure(request) @@ -112,7 +112,7 @@ class MainController extends AbstractInitializrController { def download = projectGenerator.createDistributionFile(dir, '.tgz') new AntBuilder().tar(destfile: download, compression: 'gzip') { - zipfileset(dir:dir, includes:'**') + zipfileset(dir: dir, includes: '**') } upload(download, dir, generateFileName(request, 'tgz'), 'application/x-compress') } diff --git a/initializr/src/test/groovy/io/spring/initializr/ProjectRequestTests.groovy b/initializr/src/test/groovy/io/spring/initializr/ProjectRequestTests.groovy index 6041dc23..1079239f 100644 --- a/initializr/src/test/groovy/io/spring/initializr/ProjectRequestTests.groovy +++ b/initializr/src/test/groovy/io/spring/initializr/ProjectRequestTests.groovy @@ -73,7 +73,7 @@ class ProjectRequestTests { request.style << 'org.foo:acme' // does not exist and - thrown.expect(IllegalArgumentException) + thrown.expect(InvalidProjectRequestException) thrown.expectMessage('org.foo:acme') request.resolve(metadata) } diff --git a/initializr/src/test/groovy/io/spring/initializr/web/MainControllerIntegrationTests.groovy b/initializr/src/test/groovy/io/spring/initializr/web/MainControllerIntegrationTests.groovy index 53154428..fc7dbafc 100644 --- a/initializr/src/test/groovy/io/spring/initializr/web/MainControllerIntegrationTests.groovy +++ b/initializr/src/test/groovy/io/spring/initializr/web/MainControllerIntegrationTests.groovy @@ -30,6 +30,7 @@ import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.test.context.ActiveProfiles import org.springframework.util.StreamUtils +import org.springframework.web.client.HttpClientErrorException import static org.junit.Assert.* @@ -39,6 +40,9 @@ import static org.junit.Assert.* @ActiveProfiles('test-default') class MainControllerIntegrationTests extends AbstractInitializrControllerIntegrationTests { + private final def slurper = new JsonSlurper() + + @Test void simpleZipProject() { downloadZip('/starter.zip?style=web&style=jpa').isJavaProject().isMavenProject() @@ -140,8 +144,23 @@ class MainControllerIntegrationTests extends AbstractInitializrControllerIntegra } private def metricsEndpoint() { - def slurper = new JsonSlurper() - slurper.parseText(restTemplate.getForObject(createUrl('/metrics'), String)) + parseJson(restTemplate.getForObject(createUrl('/metrics'), String)) + } + + @Test + void missingDependencyProperException() { + try { + invoke('/starter.zip?style=foo:bar') + } catch (HttpClientErrorException ex) { + def error = parseJson(ex.responseBodyAsString) + assertEquals HttpStatus.BAD_REQUEST, ex.getStatusCode() + assertTrue 'Dependency not in error message', error.message.contains('foo:bar') + } + + } + + private def parseJson(String content) { + slurper.parseText(content) } @Test @@ -212,13 +231,17 @@ class MainControllerIntegrationTests extends AbstractInitializrControllerIntegra } + private byte[] invoke(String context) { + restTemplate.getForObject(createUrl(context), byte[]) + } + private ProjectAssert downloadZip(String context) { - def body = restTemplate.getForObject(createUrl(context), byte[]) + def body = invoke(context) zipProjectAssert(body) } private ProjectAssert downloadTgz(String context) { - def body = restTemplate.getForObject(createUrl(context), byte[]) + def body = invoke(context) tgzProjectAssert(body) }