Repository: aws/chalice
Issue: Unintuitive routes

I spent a couple of evenings trying to set up a chalice with two routes, always getting the same error:

```
botocore.exceptions.ClientError: An error occurred (BadRequestException) when calling the CreateResource operation: Resource's path part must be specified
```

After some trial and error, I discovered the following:
1. The '/' route is a lie

If you ask for {hostname}/dev/ you will be rudely rejected.
If you ask for {hostname}/dev it will access route('/')
1. Trailing / makes chalice cry.

Originally I had two routes:

```
    @app.route('/')

    @app.route('/login/')
```

And got the error above.

However, if I change the second route to `@app.route('/login')` it works.

Can anyone explain this?  Or is this really a bug?


Changed files:
tests/unit/test_deployer.py, chalice/cli/__init__.py, chalice/deployer.py

diff --git a/chalice/cli/__init__.py b/chalice/cli/__init__.py
index 1b83db9..07bfae1 100644
--- a/chalice/cli/__init__.py
+++ b/chalice/cli/__init__.py
@@ -131,6 +131,10 @@ def deploy(ctx, project_dir, autogen_policy, stage):
                                  "region value in our ~/.aws/config file.")
         e.exit_code = 2
         raise e
+    except Exception as e:
+        e = click.ClickException("Error when deploying: %s" % e)
+        e.exit_code = 1
+        raise e
 
 
 @cli.command()
diff --git a/chalice/deployer.py b/chalice/deployer.py
index b796593..c8a780c 100644
--- a/chalice/deployer.py
+++ b/chalice/deployer.py
@@ -115,6 +115,7 @@ def build_url_trie(routes):
     :return: A prefix trie of URL patterns.
 
     """
+    _validate_routes(routes)
     root = node('', '/')
     for route in routes:
         if route == '/':
@@ -140,6 +141,33 @@ def build_url_trie(routes):
     return root['children'].values()[0]
 
 
+def validate_configuration(config):
+    # type: (Dict[str, Any]) -> None
+    """Validate app configuration.
+
+    The purpose of this method is to provide a fail fast mechanism
+    for anything we know is going to fail deployment.
+    We can detect common error cases and provide the user with helpful
+    error messages.
+
+    """
+    routes = config['chalice_app'].routes
+    _validate_routes(routes)
+
+
+def _validate_routes(routes):
+    # type: (Dict[str, Any]) -> None
+    # We're trying to validate any kind of route that will fail
+    # when we send the request to API gateway.
+    # We check for:
+    #
+    # * any routes that end with a trailing slash.
+    for route in routes:
+        if route != '/' and route.endswith('/'):
+            raise ValueError("Route cannot end with a trailing slash: %s"
+                             % route)
+
+
 def node(name, uri_path, is_route=False):
     # type: (str, str, bool) -> Dict[str, Any]
     return {
@@ -204,6 +232,7 @@ class Deployer(object):
                 project config file.
 
         """
+        validate_configuration(config)
         self._deploy_lambda(config)
         rest_api_id, region_name, stage = self._deploy_api_gateway(config)
         print (
diff --git a/tests/unit/test_deployer.py b/tests/unit/test_deployer.py
index f534f6b..1f308f7 100644
--- a/tests/unit/test_deployer.py
+++ b/tests/unit/test_deployer.py
@@ -4,8 +4,9 @@ from pytest import fixture
 from chalice.deployer import build_url_trie
 from chalice.deployer import APIGatewayResourceCreator
 from chalice.deployer import FULL_PASSTHROUGH, ERROR_MAPPING
-from chalice.deployer import ResourceQuery
+from chalice.deployer import ResourceQuery, validate_configuration
 from chalice.app import RouteEntry, ALL_ERRORS
+from chalice.app import Chalice
 
 from botocore.stub import Stubber
 import botocore.session
@@ -93,6 +94,16 @@ def test_multiple_routes_on_single_spine():
     )
 
 
+def test_trailing_slash_routes_result_in_error():
+    app = Chalice('appname')
+    app.routes = {'/trailing-slash/': None}
+    config = {
+        'chalice_app':  app,
+    }
+    with pytest.raises(ValueError):
+        validate_configuration(config)
+
+
 def add_expected_calls_to_map_error(error_cls, gateway_stub):
     gateway_stub.add_response(
         'put_integration_response',