blob: dd990a82c50e8aaf02011cfe0e8d47540b454e34 [file] [log] [blame]
Masaya Suzukib53fc062019-03-25 13:35:07 -07001// Copyright 2019 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14package com.google.gitiles;
15
16import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
17import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
18import static javax.servlet.http.HttpServletResponse.SC_GONE;
19import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
20import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
21import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
22
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +020023import java.util.Optional;
Masaya Suzukib53fc062019-03-25 13:35:07 -070024import javax.annotation.Nullable;
25
26/**
27 * Indicates the request should be failed.
28 *
29 * <p>When an HTTP request should be failed, throw this exception instead of directly setting an
30 * HTTP status code. The exception is caught by an error handler in {@link GitilesFilter}. By
31 * default, {@link DefaultErrorHandlingFilter} handles this exception and set an appropriate HTTP
32 * status code. If you want to customize how the error is surfaced, like changing the error page
33 * rendering, replace this error handler from {@link GitilesServlet}.
34 *
35 * <h2>Extending the error space</h2>
36 *
37 * <p>{@link GitilesServlet} lets you customize some parts of Gitiles, and sometimes you would like
38 * to create a new {@link FailureReason}. For example, a customized {@code RepositoryResolver} might
39 * check a request quota and reject a request if a client sends too many requests. In that case, you
40 * can define your own {@link RuntimeException} and an error handler.
41 *
42 * <pre><code>
43 * public final class MyRequestFailureException extends RuntimeException {
44 * private final FailureReason reason;
45 *
46 * public MyRequestFailureException(FailureReason reason) {
47 * super();
48 * this.reason = reason;
49 * }
50 *
51 * public FailureReason getReason() {
52 * return reason;
53 * }
54 *
55 * enum FailureReason {
56 * QUOTA_EXCEEDED(429);
57 * }
58 *
59 * private final int httpStatusCode;
60 *
61 * FailureReason(int httpStatusCode) {
62 * this.httpStatusCode = httpStatusCode;
63 * }
64 *
65 * public int getHttpStatusCode() {
66 * return httpStatusCode;
67 * }
68 * }
69 *
70 * public class MyErrorHandlingFilter extends AbstractHttpFilter {
71 * private static final DefaultErrorHandlingFilter delegate =
72 * new DefaultErrorHandlingFilter();
73 *
74 * {@literal @}Override
75 * public void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
76 * throws IOException, ServletException {
77 * try {
78 * delegate.doFilter(req, res, chain);
79 * } catch (MyRequestFailureException e) {
80 * res.setHeader(DefaultErrorHandlingFilter.GITILES_ERROR, e.getReason().toString());
81 * res.sendError(e.getReason().getHttpStatusCode());
82 * }
83 * }
84 * }
85 * </code></pre>
86 *
87 * <p>{@code RepositoryResolver} can throw {@code MyRequestFailureException} and {@code
88 * MyErrorHandlingFilter} will handle that. You can control how the error should be surfaced.
89 */
90public final class GitilesRequestFailureException extends RuntimeException {
David Pursehouse2d058662019-05-13 06:50:08 +020091 private static final long serialVersionUID = 1L;
Masaya Suzukib53fc062019-03-25 13:35:07 -070092 private final FailureReason reason;
93 private String publicErrorMessage;
94
95 public GitilesRequestFailureException(FailureReason reason) {
96 super();
97 this.reason = reason;
98 }
99
100 public GitilesRequestFailureException(FailureReason reason, Throwable cause) {
101 super(cause);
102 this.reason = reason;
103 }
104
105 public GitilesRequestFailureException withPublicErrorMessage(String format, Object... params) {
106 this.publicErrorMessage = String.format(format, params);
107 return this;
108 }
109
110 public FailureReason getReason() {
111 return reason;
112 }
113
114 @Nullable
115 public String getPublicErrorMessage() {
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200116 return Optional.ofNullable(publicErrorMessage).orElse(reason.getMessage());
Masaya Suzukib53fc062019-03-25 13:35:07 -0700117 }
118
119 /** The request failure reason. */
120 public enum FailureReason {
121 /** The object specified by the URL is ambiguous and Gitiles cannot identify one object. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200122 AMBIGUOUS_OBJECT(
123 SC_BAD_REQUEST,
124 "The object specified by the URL is ambiguous and Gitiles cannot identify one object"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700125 /** There's nothing to show for blame (e.g. the file is empty). */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200126 BLAME_REGION_NOT_FOUND(SC_NOT_FOUND, "There's nothing to show for blame"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700127 /** Cannot parse URL as a Gitiles URL. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200128 CANNOT_PARSE_GITILES_VIEW(SC_NOT_FOUND, "Cannot parse URL as a Gitiles URL"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700129 /** URL parameters are not valid. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200130 INCORECT_PARAMETER(SC_BAD_REQUEST, "URL parameters are not valid"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700131 /**
132 * The object specified by the URL is not suitable for the view (e.g. trying to show a blob as a
133 * tree).
134 */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200135 INCORRECT_OBJECT_TYPE(
136 SC_BAD_REQUEST, "The object specified by the URL is not suitable for the view"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700137 /** Markdown rendering is not enabled. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200138 MARKDOWN_NOT_ENABLED(SC_NOT_FOUND, "Markdown rendering is not enabled"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700139 /** Request is not authorized. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200140 NOT_AUTHORIZED(SC_UNAUTHORIZED, "Request is not authorized"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700141 /** Object is not found. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200142 OBJECT_NOT_FOUND(SC_NOT_FOUND, "Object is not found"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700143 /** Object is too large to show. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200144 OBJECT_TOO_LARGE(SC_INTERNAL_SERVER_ERROR, "Object is too large to show"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700145 /** Repository is not found. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200146 REPOSITORY_NOT_FOUND(SC_NOT_FOUND, "Repository is not found"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700147 /** Gitiles is not enabled for the repository. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200148 SERVICE_NOT_ENABLED(SC_FORBIDDEN, "Gitiles is not enabled for the repository"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700149 /** GitWeb URL cannot be converted to Gitiles URL. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200150 UNSUPPORTED_GITWEB_URL(SC_GONE, "GitWeb URL cannot be converted to Gitiles URL"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700151 /** The specified object's type is not supported. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200152 UNSUPPORTED_OBJECT_TYPE(SC_NOT_FOUND, "The specified object's type is not supported"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700153 /** The specified format type is not supported. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200154 UNSUPPORTED_RESPONSE_FORMAT(SC_BAD_REQUEST, "The specified format type is not supported"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700155 /** The specified revision names are not supported. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200156 UNSUPPORTED_REVISION_NAMES(SC_BAD_REQUEST, "The specified revision names are not supported"),
157 /** Internal server error. */
158 INTERNAL_SERVER_ERROR(SC_INTERNAL_SERVER_ERROR, "Internal server error");
Masaya Suzukib53fc062019-03-25 13:35:07 -0700159
160 private final int httpStatusCode;
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200161 private final String message;
Masaya Suzukib53fc062019-03-25 13:35:07 -0700162
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200163 FailureReason(int httpStatusCode, String message) {
Masaya Suzukib53fc062019-03-25 13:35:07 -0700164 this.httpStatusCode = httpStatusCode;
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200165 this.message = message;
Masaya Suzukib53fc062019-03-25 13:35:07 -0700166 }
167
168 public int getHttpStatusCode() {
169 return httpStatusCode;
170 }
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200171
172 public String getMessage() {
173 return message;
174 }
Masaya Suzukib53fc062019-03-25 13:35:07 -0700175 }
176}