blob: 5566ba157191c38ed160027aa3aeb2ae8e87071b [file] [log] [blame]
/*
* Copyright 2014, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package io.grpc;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
/**
* Defines the status of an operation using the canonical error space.
*/
@Immutable
public final class Status {
/**
* The set of canonical error codes. If new codes are added over time they must choose
* a numerical value that does not collide with any previously defined code.
*/
public enum Code {
OK(0),
// The operation was cancelled (typically by the caller).
CANCELLED(1),
// Unknown error. An example of where this error may be returned is
// if a Status value received from another address space belongs to
// an error-space that is not known in this address space. Also
// errors raised by APIs that do not return enough error information
// may be converted to this error.
UNKNOWN(2),
// Client specified an invalid argument. Note that this differs
// from FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments
// that are problematic regardless of the state of the system
// (e.g., a malformed file name).
INVALID_ARGUMENT(3),
// Deadline expired before operation could complete. For operations
// that change the state of the system, this error may be returned
// even if the operation has completed successfully. For example, a
// successful response from a server could have been delayed long
// enough for the deadline to expire.
DEADLINE_EXCEEDED(4),
// Some requested entity (e.g., file or directory) was not found.
NOT_FOUND(5),
// Some entity that we attempted to create (e.g., file or directory)
// already exists.
ALREADY_EXISTS(6),
// The caller does not have permission to execute the specified
// operation. PERMISSION_DENIED must not be used for rejections
// caused by exhausting some resource (use RESOURCE_EXHAUSTED
// instead for those errors). PERMISSION_DENIED must not be
// used if the caller cannot be identified (use UNAUTHENTICATED
// instead for those errors).
PERMISSION_DENIED(7),
// Some resource has been exhausted, perhaps a per-user quota, or
// perhaps the entire file system is out of space.
RESOURCE_EXHAUSTED(8),
// Operation was rejected because the system is not in a state
// required for the operation's execution. For example, directory
// to be deleted may be non-empty, an rmdir operation is applied to
// a non-directory, etc.
//
// A litmus test that may help a service implementor in deciding
// between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE:
// (a) Use UNAVAILABLE if the client can retry just the failing call.
// (b) Use ABORTED if the client should retry at a higher-level
// (e.g., restarting a read-modify-write sequence).
// (c) Use FAILED_PRECONDITION if the client should not retry until
// the system state has been explicitly fixed. E.g., if an "rmdir"
// fails because the directory is non-empty, FAILED_PRECONDITION
// should be returned since the client should not retry unless
// they have first fixed up the directory by deleting files from it.
FAILED_PRECONDITION(9),
// The operation was aborted, typically due to a concurrency issue
// like sequencer check failures, transaction aborts, etc.
//
// See litmus test above for deciding between FAILED_PRECONDITION,
// ABORTED, and UNAVAILABLE.
ABORTED(10),
// Operation was attempted past the valid range. E.g., seeking or
// reading past end of file.
//
// Unlike INVALID_ARGUMENT, this error indicates a problem that may
// be fixed if the system state changes. For example, a 32-bit file
// system will generate INVALID_ARGUMENT if asked to read at an
// offset that is not in the range [0,2^32-1], but it will generate
// OUT_OF_RANGE if asked to read from an offset past the current
// file size.
//
// There is a fair bit of overlap between FAILED_PRECONDITION and
// OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific
// error) when it applies so that callers who are iterating through
// a space can easily look for an OUT_OF_RANGE error to detect when
// they are done.
OUT_OF_RANGE(11),
// Operation is not implemented or not supported/enabled in this service.
UNIMPLEMENTED(12),
// Internal errors. Means some invariants expected by underlying
// system has been broken. If you see one of these errors,
// something is very broken.
INTERNAL(13),
// The service is currently unavailable. This is a most likely a
// transient condition and may be corrected by retrying with
// a backoff.
//
// See litmus test above for deciding between FAILED_PRECONDITION,
// ABORTED, and UNAVAILABLE.
UNAVAILABLE(14),
// Unrecoverable data loss or corruption.
DATA_LOSS(15),
// The request does not have valid authentication credentials for the
// operation.
UNAUTHENTICATED(16);
private final int value;
private final String valueAscii;
private Code(int value) {
this.value = value;
this.valueAscii = Integer.toString(value);
}
public int value() {
return value;
}
private Status status() {
return STATUS_LIST.get(value);
}
private String valueAscii() {
return valueAscii;
}
}
// Create the canonical list of Status instances indexed by their code values.
private static List<Status> STATUS_LIST;
static {
TreeMap<Integer, Status> canonicalizer = new TreeMap<Integer, Status>();
for (Code code : Code.values()) {
Status replaced = canonicalizer.put(code.value(), new Status(code));
if (replaced != null) {
throw new IllegalStateException("Code value duplication between " +
replaced.getCode().name() + " & " + code.name());
}
}
STATUS_LIST = new ArrayList<Status>(canonicalizer.values());
}
// A pseudo-enum of Status instances mapped 1:1 with values in Code. This simplifies construction
// patterns for derived implementations of Status.
public static final Status OK = Code.OK.status();
public static final Status CANCELLED = Code.CANCELLED.status();
public static final Status UNKNOWN = Code.UNKNOWN.status();
public static final Status INVALID_ARGUMENT = Code.INVALID_ARGUMENT.status();
public static final Status DEADLINE_EXCEEDED = Code.DEADLINE_EXCEEDED.status();
public static final Status NOT_FOUND = Code.NOT_FOUND.status();
public static final Status ALREADY_EXISTS = Code.ALREADY_EXISTS.status();
public static final Status PERMISSION_DENIED = Code.PERMISSION_DENIED.status();
public static final Status UNAUTHENTICATED = Code.UNAUTHENTICATED.status();
public static final Status RESOURCE_EXHAUSTED = Code.RESOURCE_EXHAUSTED.status();
public static final Status FAILED_PRECONDITION =
Code.FAILED_PRECONDITION.status();
public static final Status ABORTED = Code.ABORTED.status();
public static final Status OUT_OF_RANGE = Code.OUT_OF_RANGE.status();
public static final Status UNIMPLEMENTED = Code.UNIMPLEMENTED.status();
public static final Status INTERNAL = Code.INTERNAL.status();
public static final Status UNAVAILABLE = Code.UNAVAILABLE.status();
public static final Status DATA_LOSS = Code.DATA_LOSS.status();
/**
* Return a {@link Status} given a canonical error {@link Code} value.
*/
public static Status fromCodeValue(int codeValue) {
if (codeValue < 0 || codeValue > STATUS_LIST.size()) {
return UNKNOWN.withDescription("Unknown code " + codeValue);
} else {
return STATUS_LIST.get(codeValue);
}
}
/**
* Key to bind status code to trailers.
*/
public static final Metadata.Key<Status> CODE_KEY
= Metadata.Key.of("grpc-status", new StatusCodeMarshaller());
/**
* Key to bind status message to trailers.
*/
public static final Metadata.Key<String> MESSAGE_KEY
= Metadata.Key.of("grpc-message", Metadata.ASCII_STRING_MARSHALLER);
/**
* Extract an error {@link Status} from the causal chain of a {@link Throwable}.
*/
public static Status fromThrowable(Throwable t) {
for (Throwable cause : Throwables.getCausalChain(t)) {
if (cause instanceof OperationException) {
return ((Status.OperationException) cause).getStatus();
} else if (cause instanceof OperationRuntimeException) {
return ((Status.OperationRuntimeException) cause).getStatus();
}
}
// Couldn't find a cause with a Status
return INTERNAL.withCause(t);
}
private final Code code;
private final String description;
private final Throwable cause;
private Status(Code code) {
this(code, null, null);
}
private Status(Code code, @Nullable String description, @Nullable Throwable cause) {
this.code = Preconditions.checkNotNull(code);
this.description = description;
this.cause = cause;
}
/**
* Create a derived instance of {@link Status} with the given cause.
*/
public Status withCause(Throwable cause) {
if (Objects.equal(this.cause, cause)) {
return this;
}
return new Status(this.code, this.description, cause);
}
/**
* Create a derived instance of {@link Status} with the given description.
*/
public Status withDescription(String description) {
if (Objects.equal(this.description, description)) {
return this;
}
return new Status(this.code, description, this.cause);
}
/**
* Create a derived instance of {@link Status} with the given description.
*/
public Status augmentDescription(String additionalDetail) {
if (additionalDetail == null) {
return this;
} else if (this.description == null) {
return new Status(this.code, additionalDetail, this.cause);
} else {
return new Status(this.code, this.description + "\n" + additionalDetail, this.cause);
}
}
public Code getCode() {
return code;
}
@Nullable
public String getDescription() {
return description;
}
@Nullable
public Throwable getCause() {
return cause;
}
/**
* Is this status OK, i.e. not an error.
*/
public boolean isOk() {
return Code.OK == code;
}
/**
* Convert this {@link Status} to a {@link RuntimeException}. Use {@code #fromThrowable}
* to recover this {@link Status} instance when the returned exception is in the causal chain.
*/
public RuntimeException asRuntimeException() {
return new OperationRuntimeException(this);
}
/**
* Convert this {@link Status} to an {@link Exception}. Use {@code #fromThrowable}
* to recover this {@link Status} instance when the returned exception is in the causal chain.
*/
public Exception asException() {
return new OperationException(this);
}
/**
* Exception thrown by implementations while managing an operation.
*/
public static class OperationException extends Exception {
private final Status status;
public OperationException(Status status) {
super(status.getCode() + ": " + status.getDescription(), status.getCause());
this.status = status;
}
public Status getStatus() {
return status;
}
}
/**
* Runtime exception thrown by implementations while managing an operation.
*/
public static class OperationRuntimeException extends RuntimeException {
private final Status status;
public OperationRuntimeException(Status status) {
super(status.getCode() + ": " + status.getDescription(), status.getCause());
this.status = status;
}
public Status getStatus() {
return status;
}
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("code", code.name())
.add("description", description)
.add("cause", cause)
.toString();
}
private static class StatusCodeMarshaller implements Metadata.AsciiMarshaller<Status> {
@Override
public String toAsciiString(Status status) {
return status.getCode().valueAscii();
}
@Override
public Status parseAsciiString(String serialized) {
return fromCodeValue(Integer.valueOf(serialized));
}
}
}