blob: dd990a82c50e8aaf02011cfe0e8d47540b454e34 [file] [log] [blame] [raw]
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gitiles;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_GONE;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import java.util.Optional;
import javax.annotation.Nullable;
/**
* Indicates the request should be failed.
*
* <p>When an HTTP request should be failed, throw this exception instead of directly setting an
* HTTP status code. The exception is caught by an error handler in {@link GitilesFilter}. By
* default, {@link DefaultErrorHandlingFilter} handles this exception and set an appropriate HTTP
* status code. If you want to customize how the error is surfaced, like changing the error page
* rendering, replace this error handler from {@link GitilesServlet}.
*
* <h2>Extending the error space</h2>
*
* <p>{@link GitilesServlet} lets you customize some parts of Gitiles, and sometimes you would like
* to create a new {@link FailureReason}. For example, a customized {@code RepositoryResolver} might
* check a request quota and reject a request if a client sends too many requests. In that case, you
* can define your own {@link RuntimeException} and an error handler.
*
* <pre><code>
* public final class MyRequestFailureException extends RuntimeException {
* private final FailureReason reason;
*
* public MyRequestFailureException(FailureReason reason) {
* super();
* this.reason = reason;
* }
*
* public FailureReason getReason() {
* return reason;
* }
*
* enum FailureReason {
* QUOTA_EXCEEDED(429);
* }
*
* private final int httpStatusCode;
*
* FailureReason(int httpStatusCode) {
* this.httpStatusCode = httpStatusCode;
* }
*
* public int getHttpStatusCode() {
* return httpStatusCode;
* }
* }
*
* public class MyErrorHandlingFilter extends AbstractHttpFilter {
* private static final DefaultErrorHandlingFilter delegate =
* new DefaultErrorHandlingFilter();
*
* {@literal @}Override
* public void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
* throws IOException, ServletException {
* try {
* delegate.doFilter(req, res, chain);
* } catch (MyRequestFailureException e) {
* res.setHeader(DefaultErrorHandlingFilter.GITILES_ERROR, e.getReason().toString());
* res.sendError(e.getReason().getHttpStatusCode());
* }
* }
* }
* </code></pre>
*
* <p>{@code RepositoryResolver} can throw {@code MyRequestFailureException} and {@code
* MyErrorHandlingFilter} will handle that. You can control how the error should be surfaced.
*/
public final class GitilesRequestFailureException extends RuntimeException {
private static final long serialVersionUID = 1L;
private final FailureReason reason;
private String publicErrorMessage;
public GitilesRequestFailureException(FailureReason reason) {
super();
this.reason = reason;
}
public GitilesRequestFailureException(FailureReason reason, Throwable cause) {
super(cause);
this.reason = reason;
}
public GitilesRequestFailureException withPublicErrorMessage(String format, Object... params) {
this.publicErrorMessage = String.format(format, params);
return this;
}
public FailureReason getReason() {
return reason;
}
@Nullable
public String getPublicErrorMessage() {
return Optional.ofNullable(publicErrorMessage).orElse(reason.getMessage());
}
/** The request failure reason. */
public enum FailureReason {
/** The object specified by the URL is ambiguous and Gitiles cannot identify one object. */
AMBIGUOUS_OBJECT(
SC_BAD_REQUEST,
"The object specified by the URL is ambiguous and Gitiles cannot identify one object"),
/** There's nothing to show for blame (e.g. the file is empty). */
BLAME_REGION_NOT_FOUND(SC_NOT_FOUND, "There's nothing to show for blame"),
/** Cannot parse URL as a Gitiles URL. */
CANNOT_PARSE_GITILES_VIEW(SC_NOT_FOUND, "Cannot parse URL as a Gitiles URL"),
/** URL parameters are not valid. */
INCORECT_PARAMETER(SC_BAD_REQUEST, "URL parameters are not valid"),
/**
* The object specified by the URL is not suitable for the view (e.g. trying to show a blob as a
* tree).
*/
INCORRECT_OBJECT_TYPE(
SC_BAD_REQUEST, "The object specified by the URL is not suitable for the view"),
/** Markdown rendering is not enabled. */
MARKDOWN_NOT_ENABLED(SC_NOT_FOUND, "Markdown rendering is not enabled"),
/** Request is not authorized. */
NOT_AUTHORIZED(SC_UNAUTHORIZED, "Request is not authorized"),
/** Object is not found. */
OBJECT_NOT_FOUND(SC_NOT_FOUND, "Object is not found"),
/** Object is too large to show. */
OBJECT_TOO_LARGE(SC_INTERNAL_SERVER_ERROR, "Object is too large to show"),
/** Repository is not found. */
REPOSITORY_NOT_FOUND(SC_NOT_FOUND, "Repository is not found"),
/** Gitiles is not enabled for the repository. */
SERVICE_NOT_ENABLED(SC_FORBIDDEN, "Gitiles is not enabled for the repository"),
/** GitWeb URL cannot be converted to Gitiles URL. */
UNSUPPORTED_GITWEB_URL(SC_GONE, "GitWeb URL cannot be converted to Gitiles URL"),
/** The specified object's type is not supported. */
UNSUPPORTED_OBJECT_TYPE(SC_NOT_FOUND, "The specified object's type is not supported"),
/** The specified format type is not supported. */
UNSUPPORTED_RESPONSE_FORMAT(SC_BAD_REQUEST, "The specified format type is not supported"),
/** The specified revision names are not supported. */
UNSUPPORTED_REVISION_NAMES(SC_BAD_REQUEST, "The specified revision names are not supported"),
/** Internal server error. */
INTERNAL_SERVER_ERROR(SC_INTERNAL_SERVER_ERROR, "Internal server error");
private final int httpStatusCode;
private final String message;
FailureReason(int httpStatusCode, String message) {
this.httpStatusCode = httpStatusCode;
this.message = message;
}
public int getHttpStatusCode() {
return httpStatusCode;
}
public String getMessage() {
return message;
}
}
}