Browse Source

feat(api): Add helpful hint for missing trailing slashes (#44194)

Evan Purkhiser 2 years ago
parent
commit
543585cbb0
2 changed files with 51 additions and 3 deletions
  1. 20 3
      src/sentry/api/endpoints/catchall.py
  2. 31 0
      tests/sentry/api/endpoints/test_catchall.py

+ 20 - 3
src/sentry/api/endpoints/catchall.py

@@ -1,7 +1,6 @@
-from django.http import HttpResponse
+from django.http import HttpResponse, JsonResponse
 from django.views.decorators.csrf import csrf_exempt
 from rest_framework.request import Request
-from rest_framework.response import Response
 
 from sentry.api.base import Endpoint, pending_silo_endpoint
 
@@ -11,5 +10,23 @@ class CatchallEndpoint(Endpoint):
     permission_classes = ()
 
     @csrf_exempt
-    def dispatch(self, request: Request, *args, **kwargs) -> Response:
+    def dispatch(self, request: Request, *args, **kwargs) -> HttpResponse:
+        """
+        This endpoint handles routes that did not match
+        """
+        # Let the user know they may have forgotten a trailing slash
+        if not request.path.endswith("/"):
+            help = "Route not found, did you forget a trailing slash?"
+            suggestion = f"try: {request.path}/"
+
+            # Don't break JSON parsers
+            if request.META.get("CONTENT_TYPE", "").startswith("application/json"):
+                return JsonResponse(data={"info": f"{help} {suggestion}"}, status=404)
+
+            # Produce error message with a pointer to the trailing slash
+            arrow = f"{' ' * len(suggestion)}^"
+            message = f"{help}\n\n{suggestion}{request.path}/\n{arrow}\n"
+
+            return HttpResponse(message, status=404, content_type="text/plain")
+
         return HttpResponse(status=404)

+ 31 - 0
tests/sentry/api/endpoints/test_catchall.py

@@ -0,0 +1,31 @@
+from rest_framework import status
+
+from sentry.testutils import APITestCase
+from sentry.testutils.asserts import assert_status_code
+from sentry.testutils.silo import control_silo_test
+
+
+@control_silo_test
+class CatchallTestCase(APITestCase):
+    def setUp(self):
+        super().setUp()
+
+    def test_simple(self):
+        response = self.client.get("/api/0/bad_url/")
+        assert_status_code(response, status.HTTP_404_NOT_FOUND)
+
+        assert response.content == b""
+
+    def test_trailing_slash_help(self):
+        response = self.client.get("/api/0/bad_url")
+        assert_status_code(response, status.HTTP_404_NOT_FOUND)
+
+        assert b"Route not found, did you forget a trailing slash?" in response.content
+
+    def test_trailing_slash_help_json(self):
+        response = self.client.get("/api/0/bad_url", content_type="application/json")
+        assert_status_code(response, status.HTTP_404_NOT_FOUND)
+
+        assert response.json() == {
+            "info": "Route not found, did you forget a trailing slash? try: /api/0/bad_url/"
+        }