blob: fe5318ecb3ef22a68319da120e14887f31e4f737 [file] [log] [blame]
ejona07d3f6a2014-05-14 11:26:57 -07001package com.google.net.stubby;
2
ejonadd7ba532014-10-08 09:59:24 -07003import static com.google.common.base.Charsets.US_ASCII;
4
lryanc5e70c22014-11-24 16:41:02 -08005import com.google.common.base.MoreObjects;
ejona8c76c8a2014-10-09 09:53:41 -07006import com.google.common.base.Objects;
ejona07d3f6a2014-05-14 11:26:57 -07007import com.google.common.base.Preconditions;
lryan2ce84462014-06-02 14:43:36 -07008import com.google.common.base.Throwables;
lryan71e4a922014-09-25 18:25:54 -07009import com.google.common.collect.Lists;
ejona07d3f6a2014-05-14 11:26:57 -070010
lryan71e4a922014-09-25 18:25:54 -070011import java.util.List;
lryan71e4a922014-09-25 18:25:54 -070012import java.util.TreeMap;
ejona9d502992014-09-22 12:23:19 -070013
ejona07d3f6a2014-05-14 11:26:57 -070014import javax.annotation.Nullable;
15import javax.annotation.concurrent.Immutable;
16
17/**
18 * Defines the status of an operation using the canonical error space.
19 */
20@Immutable
lryan71e4a922014-09-25 18:25:54 -070021public final class Status {
22
23 /**
24 * The set of canonical error codes. If new codes are added over time they must choose
25 * a numerical value that does not collide with any previously defined code.
26 */
27 public enum Code {
28 OK(0),
29
30 // The operation was cancelled (typically by the caller).
31 CANCELLED(1),
32
33 // Unknown error. An example of where this error may be returned is
34 // if a Status value received from another address space belongs to
35 // an error-space that is not known in this address space. Also
36 // errors raised by APIs that do not return enough error information
37 // may be converted to this error.
38 UNKNOWN(2),
39
40 // Client specified an invalid argument. Note that this differs
41 // from FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments
42 // that are problematic regardless of the state of the system
43 // (e.g., a malformed file name).
44 INVALID_ARGUMENT(3),
45
46 // Deadline expired before operation could complete. For operations
47 // that change the state of the system, this error may be returned
48 // even if the operation has completed successfully. For example, a
49 // successful response from a server could have been delayed long
50 // enough for the deadline to expire.
51 DEADLINE_EXCEEDED(4),
52
53 // Some requested entity (e.g., file or directory) was not found.
54 NOT_FOUND(5),
55
56 // Some entity that we attempted to create (e.g., file or directory)
57 // already exists.
58 ALREADY_EXISTS(6),
59
60 // The caller does not have permission to execute the specified
61 // operation. PERMISSION_DENIED must not be used for rejections
62 // caused by exhausting some resource (use RESOURCE_EXHAUSTED
63 // instead for those errors). PERMISSION_DENIED must not be
64 // used if the caller cannot be identified (use UNAUTHENTICATED
65 // instead for those errors).
66 PERMISSION_DENIED(7),
67
68 // Some resource has been exhausted, perhaps a per-user quota, or
69 // perhaps the entire file system is out of space.
70 RESOURCE_EXHAUSTED(8),
71
72 // Operation was rejected because the system is not in a state
73 // required for the operation's execution. For example, directory
74 // to be deleted may be non-empty, an rmdir operation is applied to
75 // a non-directory, etc.
76 //
77 // A litmus test that may help a service implementor in deciding
78 // between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE:
79 // (a) Use UNAVAILABLE if the client can retry just the failing call.
80 // (b) Use ABORTED if the client should retry at a higher-level
81 // (e.g., restarting a read-modify-write sequence).
82 // (c) Use FAILED_PRECONDITION if the client should not retry until
83 // the system state has been explicitly fixed. E.g., if an "rmdir"
84 // fails because the directory is non-empty, FAILED_PRECONDITION
85 // should be returned since the client should not retry unless
86 // they have first fixed up the directory by deleting files from it.
87 FAILED_PRECONDITION(9),
88
89 // The operation was aborted, typically due to a concurrency issue
90 // like sequencer check failures, transaction aborts, etc.
91 //
92 // See litmus test above for deciding between FAILED_PRECONDITION,
93 // ABORTED, and UNAVAILABLE.
94 ABORTED(10),
95
96 // Operation was attempted past the valid range. E.g., seeking or
97 // reading past end of file.
98 //
99 // Unlike INVALID_ARGUMENT, this error indicates a problem that may
100 // be fixed if the system state changes. For example, a 32-bit file
101 // system will generate INVALID_ARGUMENT if asked to read at an
102 // offset that is not in the range [0,2^32-1], but it will generate
103 // OUT_OF_RANGE if asked to read from an offset past the current
104 // file size.
105 //
106 // There is a fair bit of overlap between FAILED_PRECONDITION and
107 // OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific
108 // error) when it applies so that callers who are iterating through
109 // a space can easily look for an OUT_OF_RANGE error to detect when
110 // they are done.
111 OUT_OF_RANGE(11),
112
113 // Operation is not implemented or not supported/enabled in this service.
114 UNIMPLEMENTED(12),
115
116 // Internal errors. Means some invariants expected by underlying
117 // system has been broken. If you see one of these errors,
118 // something is very broken.
119 INTERNAL(13),
120
121 // The service is currently unavailable. This is a most likely a
122 // transient condition and may be corrected by retrying with
123 // a backoff.
124 //
125 // See litmus test above for deciding between FAILED_PRECONDITION,
126 // ABORTED, and UNAVAILABLE.
127 UNAVAILABLE(14),
128
129 // Unrecoverable data loss or corruption.
130 DATA_LOSS(15),
131
132 // The request does not have valid authentication credentials for the
133 // operation.
134 UNAUTHENTICATED(16);
135
136 private final int value;
ejona0a585332014-10-23 18:47:40 -0700137 private final String valueAscii;
lryan71e4a922014-09-25 18:25:54 -0700138
139 private Code(int value) {
140 this.value = value;
ejona0a585332014-10-23 18:47:40 -0700141 this.valueAscii = Integer.toString(value);
lryan71e4a922014-09-25 18:25:54 -0700142 }
143
144 public int value() {
145 return value;
146 }
scottpeterson8f6e2c22014-09-26 17:09:39 -0700147
lryan71e4a922014-09-25 18:25:54 -0700148 private Status status() {
149 return STATUS_LIST.get(value);
150 }
ejona0a585332014-10-23 18:47:40 -0700151
152 private String valueAscii() {
153 return valueAscii;
154 }
lryan71e4a922014-09-25 18:25:54 -0700155 }
156
157 // Create the canonical list of Status instances indexed by their code values.
158 private static List<Status> STATUS_LIST;
159 static {
ejona8c76c8a2014-10-09 09:53:41 -0700160 TreeMap<Integer, Status> canonicalizer = new TreeMap<Integer, Status>();
lryan71e4a922014-09-25 18:25:54 -0700161 for (Code code : Code.values()) {
162 Status replaced = canonicalizer.put(code.value(), new Status(code));
163 if (replaced != null) {
164 throw new IllegalStateException("Code value duplication between " +
165 replaced.getCode().name() + " & " + code.name());
166 }
167 }
168 STATUS_LIST = Lists.newArrayList(canonicalizer.values());
169 }
170
171 // A pseudo-enum of Status instances mapped 1:1 with values in Code. This simplifies construction
172 // patterns for derived implementations of Status.
173 public static final Status OK = Code.OK.status();
174 public static final Status CANCELLED = Code.CANCELLED.status();
175 public static final Status UNKNOWN = Code.UNKNOWN.status();
176 public static final Status INVALID_ARGUMENT = Code.INVALID_ARGUMENT.status();
177 public static final Status DEADLINE_EXCEEDED = Code.DEADLINE_EXCEEDED.status();
178 public static final Status NOT_FOUND = Code.NOT_FOUND.status();
179 public static final Status ALREADY_EXISTS = Code.ALREADY_EXISTS.status();
180 public static final Status PERMISSION_DENIED = Code.PERMISSION_DENIED.status();
scottpeterson8f6e2c22014-09-26 17:09:39 -0700181 public static final Status UNAUTHENTICATED = Code.UNAUTHENTICATED.status();
lryan71e4a922014-09-25 18:25:54 -0700182 public static final Status RESOURCE_EXHAUSTED = Code.RESOURCE_EXHAUSTED.status();
scottpeterson8f6e2c22014-09-26 17:09:39 -0700183 public static final Status FAILED_PRECONDITION =
lryan71e4a922014-09-25 18:25:54 -0700184 Code.FAILED_PRECONDITION.status();
185 public static final Status ABORTED = Code.ABORTED.status();
186 public static final Status OUT_OF_RANGE = Code.OUT_OF_RANGE.status();
187 public static final Status UNIMPLEMENTED = Code.UNIMPLEMENTED.status();
188 public static final Status INTERNAL = Code.INTERNAL.status();
189 public static final Status UNAVAILABLE = Code.UNAVAILABLE.status();
190 public static final Status DATA_LOSS = Code.DATA_LOSS.status();
191
192 /**
193 * Return a {@link Status} given a canonical error {@link Code} value.
194 */
195 public static Status fromCodeValue(int codeValue) {
lryan71e4a922014-09-25 18:25:54 -0700196 if (codeValue < 0 || codeValue > STATUS_LIST.size()) {
197 return UNKNOWN.withDescription("Unknown code " + codeValue);
198 } else {
nathanmittler0304b3d2014-10-24 13:39:13 -0700199 return STATUS_LIST.get(codeValue);
lryan71e4a922014-09-25 18:25:54 -0700200 }
201 }
202
203 /**
204 * Key to bind status code to trailers.
205 */
206 public static final Metadata.Key<Status> CODE_KEY
207 = Metadata.Key.of("grpc-status", new StatusCodeMarshaller());
208
209 /**
210 * Key to bind status message to trailers.
211 */
ejona9d502992014-09-22 12:23:19 -0700212 public static final Metadata.Key<String> MESSAGE_KEY
213 = Metadata.Key.of("grpc-message", Metadata.STRING_MARSHALLER);
214
lryan71e4a922014-09-25 18:25:54 -0700215 /**
216 * Extract an error {@link Status} from the causal chain of a {@link Throwable}.
217 */
lryan2ce84462014-06-02 14:43:36 -0700218 public static Status fromThrowable(Throwable t) {
219 for (Throwable cause : Throwables.getCausalChain(t)) {
220 if (cause instanceof OperationException) {
221 return ((Status.OperationException) cause).getStatus();
222 } else if (cause instanceof OperationRuntimeException) {
223 return ((Status.OperationRuntimeException) cause).getStatus();
224 }
225 }
226 // Couldn't find a cause with a Status
lryan71e4a922014-09-25 18:25:54 -0700227 return INTERNAL.withCause(t);
lryan2ce84462014-06-02 14:43:36 -0700228 }
229
lryan71e4a922014-09-25 18:25:54 -0700230 private final Code code;
ejona07d3f6a2014-05-14 11:26:57 -0700231 private final String description;
232 private final Throwable cause;
233
lryan71e4a922014-09-25 18:25:54 -0700234 private Status(Code code) {
ejona07d3f6a2014-05-14 11:26:57 -0700235 this(code, null, null);
236 }
237
lryan71e4a922014-09-25 18:25:54 -0700238 private Status(Code code, @Nullable String description, @Nullable Throwable cause) {
ejona07d3f6a2014-05-14 11:26:57 -0700239 this.code = Preconditions.checkNotNull(code);
240 this.description = description;
241 this.cause = cause;
242 }
243
lryan71e4a922014-09-25 18:25:54 -0700244 /**
245 * Create a derived instance of {@link Status} with the given cause.
246 */
247 public Status withCause(Throwable cause) {
ejona8c76c8a2014-10-09 09:53:41 -0700248 if (Objects.equal(this.cause, cause)) {
lryan71e4a922014-09-25 18:25:54 -0700249 return this;
250 }
251 return new Status(this.code, this.description, cause);
252 }
253
254 /**
255 * Create a derived instance of {@link Status} with the given description.
256 */
257 public Status withDescription(String description) {
ejona8c76c8a2014-10-09 09:53:41 -0700258 if (Objects.equal(this.description, description)) {
lryan71e4a922014-09-25 18:25:54 -0700259 return this;
260 }
261 return new Status(this.code, description, this.cause);
262 }
263
lryan669724a2014-11-10 10:21:45 -0800264 /**
265 * Create a derived instance of {@link Status} with the given description.
266 */
267 public Status augmentDescription(String additionalDetail) {
268 if (additionalDetail == null) {
269 return this;
270 } else if (this.description == null) {
271 return new Status(this.code, additionalDetail, this.cause);
272 } else {
273 return new Status(this.code, this.description + "\n" + additionalDetail, this.cause);
274 }
275 }
276
lryan71e4a922014-09-25 18:25:54 -0700277 public Code getCode() {
ejona07d3f6a2014-05-14 11:26:57 -0700278 return code;
279 }
280
281 @Nullable
282 public String getDescription() {
283 return description;
284 }
285
286 @Nullable
287 public Throwable getCause() {
288 return cause;
289 }
290
lryan71e4a922014-09-25 18:25:54 -0700291 /**
292 * Is this status OK, i.e. not an error.
293 */
ejona35cabd02014-06-11 14:46:25 -0700294 public boolean isOk() {
lryan71e4a922014-09-25 18:25:54 -0700295 return Code.OK == code;
ejona35cabd02014-06-11 14:46:25 -0700296 }
297
ejona07d3f6a2014-05-14 11:26:57 -0700298 /**
lryan71e4a922014-09-25 18:25:54 -0700299 * Convert this {@link Status} to a {@link RuntimeException}. Use {@code #fromThrowable}
300 * to recover this {@link Status} instance when the returned exception is in the causal chain.
ejona07d3f6a2014-05-14 11:26:57 -0700301 */
ejona07d3f6a2014-05-14 11:26:57 -0700302 public RuntimeException asRuntimeException() {
303 return new OperationRuntimeException(this);
304 }
305
lryan71e4a922014-09-25 18:25:54 -0700306 /**
307 * Convert this {@link Status} to an {@link Exception}. Use {@code #fromThrowable}
308 * to recover this {@link Status} instance when the returned exception is in the causal chain.
309 */
ejona07d3f6a2014-05-14 11:26:57 -0700310 public Exception asException() {
311 return new OperationException(this);
312 }
313
314 /**
315 * Exception thrown by implementations while managing an operation.
316 */
317 public static class OperationException extends Exception {
318
319 private final Status status;
320
321 public OperationException(Status status) {
zhangkune1ae25c2014-08-08 22:39:39 -0700322 super(status.getCode() + ": " + status.getDescription(), status.getCause());
ejona07d3f6a2014-05-14 11:26:57 -0700323 this.status = status;
324 }
325
326 public Status getStatus() {
327 return status;
328 }
329 }
330
331 /**
332 * Runtime exception thrown by implementations while managing an operation.
333 */
334 public static class OperationRuntimeException extends RuntimeException {
335
336 private final Status status;
337
338 public OperationRuntimeException(Status status) {
zhangkune1ae25c2014-08-08 22:39:39 -0700339 super(status.getCode() + ": " + status.getDescription(), status.getCause());
ejona07d3f6a2014-05-14 11:26:57 -0700340 this.status = status;
341 }
342
343 public Status getStatus() {
344 return status;
345 }
346 }
zhangkun347a22d2014-05-21 16:44:20 -0700347
348 @Override
349 public String toString() {
lryanc5e70c22014-11-24 16:41:02 -0800350 return MoreObjects.toStringHelper(this)
351 .add("code", code.name())
352 .add("description", description)
353 .add("cause", cause)
354 .toString();
zhangkun347a22d2014-05-21 16:44:20 -0700355 }
ejona9d502992014-09-22 12:23:19 -0700356
lryan71e4a922014-09-25 18:25:54 -0700357 private static class StatusCodeMarshaller implements Metadata.Marshaller<Status> {
ejona9d502992014-09-22 12:23:19 -0700358 @Override
lryan71e4a922014-09-25 18:25:54 -0700359 public byte[] toBytes(Status status) {
ejona0a585332014-10-23 18:47:40 -0700360 return toAscii(status).getBytes(US_ASCII);
ejona9d502992014-09-22 12:23:19 -0700361 }
362
363 @Override
lryan71e4a922014-09-25 18:25:54 -0700364 public String toAscii(Status status) {
ejona0a585332014-10-23 18:47:40 -0700365 return status.getCode().valueAscii();
ejona9d502992014-09-22 12:23:19 -0700366 }
367
368 @Override
lryan71e4a922014-09-25 18:25:54 -0700369 public Status parseBytes(byte[] serialized) {
ejona0a585332014-10-23 18:47:40 -0700370 return parseAscii(new String(serialized, US_ASCII));
ejona9d502992014-09-22 12:23:19 -0700371 }
372
373 @Override
lryan71e4a922014-09-25 18:25:54 -0700374 public Status parseAscii(String ascii) {
ejona0a585332014-10-23 18:47:40 -0700375 return fromCodeValue(Integer.valueOf(ascii));
ejona9d502992014-09-22 12:23:19 -0700376 }
377 }
ejona07d3f6a2014-05-14 11:26:57 -0700378}