blob: c4fd48a931af2032518faae22e51d2d0c555e24e [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
Matthias Sohnd5ce8212023-09-30 21:49:14 +020023import com.google.errorprone.annotations.FormatMethod;
24import com.google.errorprone.annotations.FormatString;
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +020025import java.util.Optional;
Masaya Suzukib53fc062019-03-25 13:35:07 -070026import javax.annotation.Nullable;
27
28/**
29 * Indicates the request should be failed.
30 *
31 * <p>When an HTTP request should be failed, throw this exception instead of directly setting an
32 * HTTP status code. The exception is caught by an error handler in {@link GitilesFilter}. By
33 * default, {@link DefaultErrorHandlingFilter} handles this exception and set an appropriate HTTP
34 * status code. If you want to customize how the error is surfaced, like changing the error page
35 * rendering, replace this error handler from {@link GitilesServlet}.
36 *
37 * <h2>Extending the error space</h2>
38 *
39 * <p>{@link GitilesServlet} lets you customize some parts of Gitiles, and sometimes you would like
40 * to create a new {@link FailureReason}. For example, a customized {@code RepositoryResolver} might
41 * check a request quota and reject a request if a client sends too many requests. In that case, you
42 * can define your own {@link RuntimeException} and an error handler.
43 *
44 * <pre><code>
45 * public final class MyRequestFailureException extends RuntimeException {
46 * private final FailureReason reason;
47 *
48 * public MyRequestFailureException(FailureReason reason) {
49 * super();
50 * this.reason = reason;
51 * }
52 *
53 * public FailureReason getReason() {
54 * return reason;
55 * }
56 *
57 * enum FailureReason {
58 * QUOTA_EXCEEDED(429);
59 * }
60 *
61 * private final int httpStatusCode;
62 *
63 * FailureReason(int httpStatusCode) {
64 * this.httpStatusCode = httpStatusCode;
65 * }
66 *
67 * public int getHttpStatusCode() {
68 * return httpStatusCode;
69 * }
70 * }
71 *
72 * public class MyErrorHandlingFilter extends AbstractHttpFilter {
73 * private static final DefaultErrorHandlingFilter delegate =
74 * new DefaultErrorHandlingFilter();
75 *
76 * {@literal @}Override
77 * public void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
78 * throws IOException, ServletException {
79 * try {
80 * delegate.doFilter(req, res, chain);
81 * } catch (MyRequestFailureException e) {
82 * res.setHeader(DefaultErrorHandlingFilter.GITILES_ERROR, e.getReason().toString());
83 * res.sendError(e.getReason().getHttpStatusCode());
84 * }
85 * }
86 * }
87 * </code></pre>
88 *
89 * <p>{@code RepositoryResolver} can throw {@code MyRequestFailureException} and {@code
90 * MyErrorHandlingFilter} will handle that. You can control how the error should be surfaced.
91 */
92public final class GitilesRequestFailureException extends RuntimeException {
David Pursehouse2d058662019-05-13 06:50:08 +020093 private static final long serialVersionUID = 1L;
Masaya Suzukib53fc062019-03-25 13:35:07 -070094 private final FailureReason reason;
95 private String publicErrorMessage;
96
97 public GitilesRequestFailureException(FailureReason reason) {
98 super();
99 this.reason = reason;
100 }
101
102 public GitilesRequestFailureException(FailureReason reason, Throwable cause) {
103 super(cause);
104 this.reason = reason;
105 }
106
Matthias Sohnd5ce8212023-09-30 21:49:14 +0200107 @FormatMethod
108 public GitilesRequestFailureException withPublicErrorMessage(
109 @FormatString String format, Object... params) {
Masaya Suzukib53fc062019-03-25 13:35:07 -0700110 this.publicErrorMessage = String.format(format, params);
111 return this;
112 }
113
114 public FailureReason getReason() {
115 return reason;
116 }
117
118 @Nullable
119 public String getPublicErrorMessage() {
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200120 return Optional.ofNullable(publicErrorMessage).orElse(reason.getMessage());
Masaya Suzukib53fc062019-03-25 13:35:07 -0700121 }
122
123 /** The request failure reason. */
124 public enum FailureReason {
125 /** The object specified by the URL is ambiguous and Gitiles cannot identify one object. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200126 AMBIGUOUS_OBJECT(
127 SC_BAD_REQUEST,
128 "The object specified by the URL is ambiguous and Gitiles cannot identify one object"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700129 /** There's nothing to show for blame (e.g. the file is empty). */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200130 BLAME_REGION_NOT_FOUND(SC_NOT_FOUND, "There's nothing to show for blame"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700131 /** Cannot parse URL as a Gitiles URL. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200132 CANNOT_PARSE_GITILES_VIEW(SC_NOT_FOUND, "Cannot parse URL as a Gitiles URL"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700133 /** URL parameters are not valid. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200134 INCORECT_PARAMETER(SC_BAD_REQUEST, "URL parameters are not valid"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700135 /**
136 * The object specified by the URL is not suitable for the view (e.g. trying to show a blob as a
137 * tree).
138 */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200139 INCORRECT_OBJECT_TYPE(
140 SC_BAD_REQUEST, "The object specified by the URL is not suitable for the view"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700141 /** Markdown rendering is not enabled. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200142 MARKDOWN_NOT_ENABLED(SC_NOT_FOUND, "Markdown rendering is not enabled"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700143 /** Request is not authorized. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200144 NOT_AUTHORIZED(SC_UNAUTHORIZED, "Request is not authorized"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700145 /** Object is not found. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200146 OBJECT_NOT_FOUND(SC_NOT_FOUND, "Object is not found"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700147 /** Object is too large to show. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200148 OBJECT_TOO_LARGE(SC_INTERNAL_SERVER_ERROR, "Object is too large to show"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700149 /** Repository is not found. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200150 REPOSITORY_NOT_FOUND(SC_NOT_FOUND, "Repository is not found"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700151 /** Gitiles is not enabled for the repository. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200152 SERVICE_NOT_ENABLED(SC_FORBIDDEN, "Gitiles is not enabled for the repository"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700153 /** GitWeb URL cannot be converted to Gitiles URL. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200154 UNSUPPORTED_GITWEB_URL(SC_GONE, "GitWeb URL cannot be converted to Gitiles URL"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700155 /** The specified object's type is not supported. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200156 UNSUPPORTED_OBJECT_TYPE(SC_NOT_FOUND, "The specified object's type is not supported"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700157 /** The specified format type is not supported. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200158 UNSUPPORTED_RESPONSE_FORMAT(SC_BAD_REQUEST, "The specified format type is not supported"),
Masaya Suzukib53fc062019-03-25 13:35:07 -0700159 /** The specified revision names are not supported. */
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200160 UNSUPPORTED_REVISION_NAMES(SC_BAD_REQUEST, "The specified revision names are not supported"),
161 /** Internal server error. */
162 INTERNAL_SERVER_ERROR(SC_INTERNAL_SERVER_ERROR, "Internal server error");
Masaya Suzukib53fc062019-03-25 13:35:07 -0700163
164 private final int httpStatusCode;
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200165 private final String message;
Masaya Suzukib53fc062019-03-25 13:35:07 -0700166
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200167 FailureReason(int httpStatusCode, String message) {
Masaya Suzukib53fc062019-03-25 13:35:07 -0700168 this.httpStatusCode = httpStatusCode;
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200169 this.message = message;
Masaya Suzukib53fc062019-03-25 13:35:07 -0700170 }
171
172 public int getHttpStatusCode() {
173 return httpStatusCode;
174 }
Alon Bar-Levcf9e71d2019-01-23 15:23:19 +0200175
176 public String getMessage() {
177 return message;
178 }
Masaya Suzukib53fc062019-03-25 13:35:07 -0700179 }
180}