Wrap all HTTP errors from SVNMan API in RemoteErrors
authorSybren A. Stüvel <sybren@stuvel.eu>
Fri, 3 Nov 2017 13:35:37 +0000 (14:35 +0100)
committerSybren A. Stüvel <sybren@stuvel.eu>
Fri, 3 Nov 2017 13:36:33 +0000 (14:36 +0100)
This ensures that errors on the API side aren't sent as-is to the
Pillar web client. For example, a 404 indicating that a repository
cannot be found shouldn't automatically result in a 404 sent to the
browser.

svnman/exceptions.py
svnman/remote.py

index 217a214..3a5f8dc 100644 (file)
@@ -1,5 +1,7 @@
 """SVNMan-specific exceptions."""
 
+import collections
+
 
 class SVNManException(Exception):
     """Base exception for all SVNMan-specific exceptions."""
@@ -10,10 +12,18 @@ class RemoteError(SVNManException):
 
     Note that not all errors that have anything to do with the remote server
     communication are wrapped in this error; it's very possible you can get
-    exceptions from the Requests library, for example.
+    IO exceptions when the network fails, for example.
     """
 
 
+class InternalAPIServerError(RemoteError):
+    """Raised when we received a 500 Internal Server Error from the API."""
+
+
+class BadAPIRequest(RemoteError):
+    """Raised when we received a 400 Bad Request response from the API"""
+
+
 class RepoAlreadyExists(RemoteError):
     def __init__(self, repo_id: str):
         self.repo_id = repo_id
@@ -21,3 +31,17 @@ class RepoAlreadyExists(RemoteError):
     def __repr__(self):
         return f'RepoAlreadyExists({self.repo_id!r})'
 
+
+class RepoNotFound(RemoteError):
+    def __init__(self, repo_id: str):
+        self.repo_id = repo_id
+
+    def __repr__(self):
+        return f'RepoNotFound({self.repo_id!r})'
+
+
+http_error_map = collections.defaultdict(RemoteError)
+http_error_map.update({
+    400: BadAPIRequest,
+    500: InternalAPIServerError,
+})
index 57d32e2..c0caa77 100644 (file)
@@ -47,14 +47,20 @@ class API:
         auth = (self.username, self.password) if self.username or self.password else None
         return self._session.request(method, abs_url, auth=auth, **kwargs)
 
+    def _raise_for_status(self, resp: requests.Response):
+        """Raises the appropriate exception for the given response."""
+
+        if resp.status_code < 400:
+            return
+
+        exc_class = exceptions.http_error_map[resp.status_code]
+        raise exc_class(resp.text)
+
     def fetch_repo(self, repo_id: str) -> RepoDescription:
         """Fetches repository information from the remote."""
 
         resp = self._request('GET', f'repo/{repo_id}')
-
-        if resp.status_code == requests.codes.conflict:
-            raise exceptions.RepoAlreadyExists(repo_id)
-        resp.raise_for_status()
+        self._raise_for_status(resp)
 
         return RepoDescription(**resp.json())
 
@@ -77,4 +83,4 @@ class API:
             'grant': grants,
             'revoke': revoke,
         })
-        resp.raise_for_status()
+        self._raise_for_status(resp)