details = ex.toMap(stackstrace);
details.compute("message", escaper);
details.compute("reason", escaper);
rsp.send(
Results
.when(MediaType.html, () -> Results.html(VIEW).put("err", details))
.when(MediaType.all, () -> details));
}
}
/**
* Handle and render exceptions. Error handlers are executed in the order they were provided, the
* first err handler that send an output wins!
*
* The default err handler does content negotation on error, see {@link DefHandler}.
*
* @author edgar
* @since 0.1.0
*/
public interface Handler {
/**
* Handle a route exception by properly logging the error and sending a err response to the
* client.
*
* Please note you always get an {@link Err} whenever you throw it or not. For example if your
* application throws an {@link IllegalArgumentException} exception you will get an {@link Err}
* and you can retrieve the original exception by calling {@link Err#getCause()}.
*
* Jooby always give you an {@link Err} with an optional root cause and an associated status
* code.
*
* @param req HTTP request.
* @param rsp HTTP response.
* @param ex Error found and status code.
* @throws Throwable If something goes wrong.
*/
void handle(Request req, Response rsp, Err ex) throws Throwable;
}
/**
* The status code. Required.
*/
private int status;
/**
* Creates a new {@link Err}.
*
* @param status A HTTP status. Required.
* @param message A error message. Required.
* @param cause The cause of the problem.
*/
public Err(final Status status, final String message, final Throwable cause) {
super(message(status, message), cause);
this.status = status.value();
}
/**
* Creates a new {@link Err}.
*
* @param status A HTTP status. Required.
* @param message A error message. Required.
* @param cause The cause of the problem.
*/
public Err(final int status, final String message, final Throwable cause) {
super(message("", status, message), cause);
this.status = status;
}
/**
* Creates a new {@link Err}.
*
* @param status A web socket close status. Required.
* @param message Close message.
*/
public Err(final WebSocket.CloseStatus status, final String message) {
super(message(status.reason(), status.code(), message));
this.status = status.code();
}
/**
* Creates a new {@link Err}.
*
* @param status A HTTP status. Required.
* @param message A error message. Required.
*/
public Err(final Status status, final String message) {
super(message(status, message));
this.status = status.value();
}
/**
* Creates a new {@link Err}.
*
* @param status A HTTP status. Required.
* @param message A error message. Required.
*/
public Err(final int status, final String message) {
this(Status.valueOf(status), message);
}
/**
* Creates a new {@link Err}.
*
* @param status A HTTP status. Required.
* @param cause The cause of the problem.
*/
public Err(final Status status, final Throwable cause) {
super(message(status, null), cause);
this.status = status.value();
}
/**
* Creates a new {@link Err}.
*
* @param status A HTTP status. Required.
* @param cause The cause of the problem.
*/
public Err(final int status, final Throwable cause) {
this(Status.valueOf(status), cause);
}
/**
* Creates a new {@link Err}.
*
* @param status A HTTP status. Required.
*/
public Err(final Status status) {
super(message(status, null));
this.status = status.value();
}
/**
* Creates a new {@link Err}.
*
* @param status A HTTP status. Required.
*/
public Err(final int status) {
this(Status.valueOf(status));
}
/**
* @return The status code to send as response.
*/
public int statusCode() {
return status;
}
/**
* Produces a friendly view of the err, resulting map has these attributes:
*
*
* message: exception message (if present)
* status: status code
* reason: a status code reason
*
*
* @return A lightweight view of the err.
*/
public Map toMap() {
return toMap(false);
}
/**
* Produces a friendly view of the err, resulting map has these attributes:
*
*
* message: exception message (if present)
* stacktrace: array with the stacktrace
* status: status code
* reason: a status code reason
*
*
* @param stacktrace True for adding stacktrace.
* @return A lightweight view of the err.
*/
public Map toMap(boolean stacktrace) {
Status status = Status.valueOf(this.status);
Throwable cause = Optional.ofNullable(getCause()).orElse(this);
String message = Optional.ofNullable(cause.getMessage()).orElse(status.reason());
Map err = new LinkedHashMap<>();
err.put("message", message);
if (stacktrace) {
err.put("stacktrace", Throwables.getStackTraceAsString(cause).replace("\r", "").split("\\n"));
}
err.put("status", status.value());
err.put("reason", status.reason());
return err;
}
/**
* Build an error message using the HTTP status.
*
* @param status The HTTP Status.
* @param tail A message to append.
* @return An error message.
*/
private static String message(final Status status, @Nullable final String tail) {
return message(status.reason(), status.value(), tail);
}
/**
* Build an error message using the HTTP status.
*
* @param reason Reason.
* @param status The Status.
* @param tail A message to append.
* @return An error message.
*/
private static String message(final String reason, final int status, @Nullable final String tail) {
return reason + "(" + status + ")" + (tail == null ? "" : ": " + tail);
}
}