mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-10-29 17:46:16 +01:00
Committed-by: Eduard Heimbuch<eduard.heimbuch@cloudogu.com> Pushed-by: Rene Pfeuffer<rene.pfeuffer@cloudogu.com> Co-authored-by: René Pfeuffer<rene.pfeuffer@cloudogu.com> Pushed-by: Eduard Heimbuch<eduard.heimbuch@cloudogu.com> Committed-by: René Pfeuffer<rene.pfeuffer@cloudogu.com>
250 lines
11 KiB
Markdown
250 lines
11 KiB
Markdown
---
|
|
title: Error Handling
|
|
---
|
|
|
|
As a highly extensible product, SCM-Manager offers at least three ways to interact with:
|
|
|
|
- the GUI,
|
|
- the REST API, and
|
|
- the Java API
|
|
|
|
Having these three layers, the error handling should be consistent among these. That is, as a developer I would not like
|
|
to have custom made error codes in the REST layer that I cannot find in the Java API. Furthermore it is essential to get
|
|
precise error messages with hints how to find a way out (if possible), not only for a programmer making interactive
|
|
calls, but also for other programs. Last but not least it should be easy for plugin developers to adapt the error
|
|
handling.
|
|
|
|
On the GUI layer, these information have to be translated into messages that can be easily handled.
|
|
|
|
Here are some example error cases:
|
|
|
|
*Use case*: Read the metadata for a branch
|
|
|
|
*Possible errors:*
|
|
|
|
- The repository is missing
|
|
- There is no such branch
|
|
- The user is not authorized to read the metadata
|
|
- The repository is corrupt on file system level
|
|
|
|
---
|
|
|
|
*Use case:* Create a new user
|
|
|
|
*Possible errors:*
|
|
|
|
- Invalid characters in name
|
|
- Missing mandatory property
|
|
- Conflict with an existing user
|
|
- Insufficient priviliges
|
|
|
|
---
|
|
|
|
*Use case:* Update the e-mail of a repository
|
|
|
|
*Possible errors:*
|
|
|
|
- The repository does not exist
|
|
- The repository was modified concurrently
|
|
- Invalid e-mail address
|
|
|
|
## Java API
|
|
|
|
In SCM-Manager we make heavy use of Java `Exception`s, not only for technical exceptions in the program flow like
|
|
reading corrupt file systems, but also for "user errors" like illegal values or requests for missing data.
|
|
|
|
These exceptions are handled by
|
|
JEE [`ExceptionMapper`](https://docs.oracle.com/javaee/7/api/jakarta/ws/rs/ext/ExceptionMapper.html) s. Doing so, it is
|
|
possible to concentrate on implementing the "happy path" without the need to explicitly handle error cases everywhere (
|
|
for example you do not have to check whether got `null` as a result). Nonetheless we still had to decide whether to use
|
|
checked or unchecked exceptions. We have chosen to use unchecked exceptions due to the following reasons:
|
|
|
|
- Checked exceptions would have had to be declared everywhere.
|
|
- A checked exception can somehow trigger a "I have to handle this though I don't know how" feeling that would be wrong,
|
|
because we do have mappers for these exceptions.
|
|
|
|
Therefore handling such an exception has to be a concious decision.
|
|
|
|
In the following we will introduce the exceptions we are using:
|
|
|
|
### Used exceptions
|
|
|
|
#### `NotFoundException`
|
|
|
|
A `NotFoundException` is thrown, whenever "things" where requested but were not found.
|
|
|
|
#### `AlreadyExistsException`
|
|
|
|
This exception is thrown whenever an entity cannot be created, because another entity with the same key identifyer
|
|
exists.
|
|
|
|
#### `ConcurrentModificationException`
|
|
|
|
When you try to modify an entity based on an outdated version, this exception is thrown. For entities like user, group
|
|
or repository the "last modified" timestamp of `ModelObject` is used to check this.
|
|
|
|
#### `NativeRepositoryAccessException`
|
|
|
|
Failures while accessing native repositories (most of the time) result in `java.io.IOException`s. To distinguish these
|
|
exceptions from other I/O errors like network exceptions and to make them unchecked, we wrap them
|
|
in `NativeRepositoryAccessException`.
|
|
|
|
#### `ResteasyViolationException`
|
|
|
|
Input validation is handled
|
|
using [RESTEasy's validation support](https://docs.jboss.org/resteasy/docs/3.0.0.Final/userguide/html/Validation.html).
|
|
Constraint violations result in `ResteasyViolationException`s.
|
|
|
|
#### All other runtime exceptions
|
|
|
|
All other `java.lang.RuntimeException`s can be treated as unexpected errors, that either hint to severe problems (eg.
|
|
disk access failures) or implementation errors. It is unlikely that these can be handled by the program gracefully. They
|
|
will be caught by a generic exception handler which will wrap them in a new exception providing further SCM specific
|
|
information.
|
|
|
|
#### Checked exceptions
|
|
|
|
Above we mentioned, that we want to use unchecked exceptions only. Therefore we have to wrap checked exceptions for
|
|
example created by libraries or frarmeworks to make them unchecked. Normally it is sufficiant to wrap them in a
|
|
RuntimeException using a proper message, except you plan to handle them somewhere else than at resource level (then it
|
|
would be appropriate to introduce a new exception class extending `RuntimeException`).
|
|
|
|
### Enrichment of exceptions
|
|
|
|
#### Context
|
|
|
|
Most of these exceptions must provide information about _what_ could not have been found, updated, whatsoever. This is
|
|
necessary, because otherwise it may not be clear at what step of a potentionally complex process this exception occured.
|
|
Though this sounds easy, it has some complexity because for example the access of a file in a repository can fail on
|
|
many levels (the file is missing in the given revision, the revision is missing, or the repository itself is missing).
|
|
In these cases you have to know the complete access path (what file in what revision of what changeset in what
|
|
repository).
|
|
|
|
To ensure that this is done in a reproducable and consistent way, SCM-Manager will provide utility functions to creaate
|
|
such exceptions.
|
|
|
|
#### Transaction IDs
|
|
|
|
To be able to retrace the cause for exceptions, it is helpful to link log messages to these exceptions. To do so,
|
|
SCM-Manager introduces transaction ids that are generated for single requests or other related actions like the
|
|
processing of hooks or cron jobs. This transaction id will be part of every log message and every API response.
|
|
|
|
For http requests, this can be done using MDC filter.
|
|
|
|
#### Identification of exceptions
|
|
|
|
To be able to identify different types of exceptions even outside of the java ecosystem, each SCM-Manager exception
|
|
class will get a unique type id that will be created using the `DefaultKeyGenerator` during development. We chose to
|
|
generate these ids and not "human readable" ones to prevent collisions between plugins.
|
|
|
|
### Logging of exceptions
|
|
|
|
To be able to retrace errors a proper logging is indispensible. So we decided to use the following rules for logging
|
|
exceptions:
|
|
|
|
- Native SCM manager exceptions will be logged at log level `INFO` with their message, only. The complete stacktrace
|
|
will be logged at log level `DEBUG`.
|
|
- All other exceptions will be logged on level `WARN` with the complete stacktrace, because they indicate something
|
|
going wrong on a fundamental level.
|
|
|
|
## REST API
|
|
|
|
### Status codes
|
|
|
|
SCM-Manager uses [http status codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) to identify types of
|
|
errors (and successes, that is) and doing so provides a first hint, what may have gone wrong:
|
|
|
|
| Status code | Principal error cause |
|
|
|-------------|-----------------------|
|
|
| 200 | No error, everything is fine |
|
|
| 201 | The item has been created without an error |
|
|
| 204 | The request has been processed without an error |
|
|
| 400 | Something is not valid with the data provided |
|
|
| 401 | Missing authentication (not logged in?) |
|
|
| 403 | Missing authorization |
|
|
| 404 | The thing you are looking does not exist |
|
|
| 409 | Your update was rejected because you relate to an outdated version (maybe this item was changed in the meantime) _
|
|
or_ the item could not be created because the key already exists |
|
|
| 500 | The "You are not to blame" error; something unexpected went wrong while processing the request |
|
|
|
|
### Further information
|
|
|
|
Whenever possible, an error response contains useful details about the error in a simple json format. These information
|
|
are _not_ translated, so this is the responsibility of the frontend.
|
|
|
|
| key | content | availability |
|
|
|-----|---------|--------------|
|
|
| transactionId | A unique id to link your request to log messages | always |
|
|
| errorCode | A code that can be used for translated error messages. To prevent the usage of the same codes in different exceptions we decided to use generated ids. | always |
|
|
| context | (repo/key, branch/key, ...) | optional |
|
|
| message | An english error message (not necessarily for end users) | always |
|
|
| url | A URL to a site providing further information about the error | optional |
|
|
|
|
Error objects will contain no stack traces.
|
|
|
|
For SCM exceptions, the message will be created from the message of the java exception. For other exceptions this will
|
|
be a generic message in most cases.
|
|
|
|
Here is an example, how a concrete exception may look like in a json response:
|
|
|
|
```json
|
|
{
|
|
"transactionId": "7D82atGf3",
|
|
"errorCode": "H823fFAt",
|
|
"context": [
|
|
{
|
|
"type": "repository",
|
|
"id": "scmmanager/test"
|
|
},
|
|
{
|
|
"type": "branch",
|
|
"id": "master"
|
|
},
|
|
{
|
|
"type": "file",
|
|
"id": ".gitignore"
|
|
}
|
|
],
|
|
"message": "file not found",
|
|
"url": "https://www.scm-manager.org/errors/H823fFAt"
|
|
}
|
|
```
|
|
|
|
### Missing resources (404)
|
|
|
|
The http status code 404 is a special case, because it is a fundamental status code that can be created on a lot of
|
|
events:
|
|
|
|
- Your proxy has a misconfiguration and you are talking with a static website instead of SCM-Manager
|
|
- You are using a path without a valid endpoint in SCM-Manager
|
|
- You are requesting a entity that does not exists
|
|
|
|
Some say, that you should not try to interpret the body of a 404 response, because the origin of the response cannot be
|
|
taken for granted. Nonetheless we decided to use this http status code to indicate requests for missing resources,
|
|
because in our view this is what most people would expect.
|
|
|
|
### Internal errors (500)
|
|
|
|
Internal errors boil down to the following message: An error occured, that could not be handled in a reasonable way by
|
|
the program. In these cases often only an administrator can help. Examples are out-of-memory errors, failing disk I/O,
|
|
timeouts accessing other services, or (to be honest) simple programming errors that have to be fixed in further
|
|
releases. To be able to trace these errors in the logs one can use the transaction ids.
|
|
|
|
## GUI
|
|
|
|
As an end user of the SCM-Manager I would not like to see confusing internals, but rather have a meaningful message in
|
|
my language of choice. Therefore it is necessary to identify error types on a fine level. This can be done using the
|
|
errorCode provided in each error object.
|
|
|
|
Basically we have to differentiate between errors the user can handle ("user errors") and technical exceptions. For user
|
|
errors a meaningful message can be generated giving hints to what the user has done "wrong". All other exceptions can be
|
|
handled by displaying a "sorry, this did not work as expected" message with the transaction id.
|
|
|
|
## Resources / Best Practices
|
|
|
|
While creating this concepts we tried to adhere to best practices considering APIs of Twitter, Facebook, Bing, Spotify
|
|
and others, as summarized in the following articles:
|
|
|
|
* [RESTful API Design: What About Errors? (Apigee)](https://apigee.com/about/blog/technology/restful-api-design-what-about-errors)
|
|
* [Best Practices for API Error Handling (Nordic APIS)](https://nordicapis.com/best-practices-api-error-handling/)
|