Skip to content

Commit ecb5a3e

Browse files
authored
Add errors/ project with RemoteException and ServiceException (#9)
1 parent 318d687 commit ecb5a3e

File tree

16 files changed

+760
-2
lines changed

16 files changed

+760
-2
lines changed

errors/build.gradle

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
apply plugin: 'org.inferred.processors'
2+
3+
apply from: "${rootDir}/gradle/publish.gradle"
4+
5+
dependencies {
6+
compile "com.fasterxml.jackson.core:jackson-databind"
7+
compile "com.google.code.findbugs:jsr305"
8+
compile "com.palantir.safe-logging:safe-logging"
9+
compile "javax.ws.rs:javax.ws.rs-api"
10+
11+
testCompile project(":extras:jackson-support")
12+
testCompile "org.assertj:assertj-core"
13+
testCompile "junit:junit"
14+
testCompile "org.apache.commons:commons-lang3"
15+
16+
processor "org.immutables:value"
17+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright 2017 Palantir Technologies, Inc. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.palantir.remoting.api.errors;
18+
19+
import org.immutables.value.Value;
20+
21+
/**
22+
* Represents errors by code and name. {@link ErrorType} instance are meant to be compile-time constants in
23+
* the sense that the name should not contain information that is available at runtime only.
24+
*/
25+
@Value.Immutable
26+
@ImmutablesStyle
27+
public abstract class ErrorType {
28+
29+
public enum Code {
30+
UNKNOWN(500),
31+
PERMISSION_DENIED(403),
32+
INVALID_ARGUMENT(400),
33+
FAILED_PRECONDITION(400),
34+
INTERNAL(500),
35+
CUSTOM(null /* unused */);
36+
37+
private final Integer httpErrorCode; // boxed so that we fail loudly if someone accesses CUSTOM.httpErrorCode
38+
39+
Code(Integer httpErrorCode) {
40+
this.httpErrorCode = httpErrorCode;
41+
}
42+
}
43+
44+
public static final ErrorType UNKNOWN = create(Code.UNKNOWN);
45+
public static final ErrorType PERMISSION_DENIED = create(Code.PERMISSION_DENIED);
46+
public static final ErrorType INVALID_ARGUMENT = create(Code.INVALID_ARGUMENT);
47+
public static final ErrorType FAILED_PRECONDITION = create(Code.FAILED_PRECONDITION);
48+
public static final ErrorType INTERNAL = create(Code.INTERNAL);
49+
50+
/** The {@link Code} of this error. */
51+
public abstract Code code();
52+
53+
/**
54+
* The name of this error type. Names should be compile-time constants and are considered part of the API of a
55+
* service that produces this error.
56+
*/
57+
public abstract String name();
58+
59+
/** The HTTP error code used to convey this error to HTTP clients. */
60+
public abstract int httpErrorCode();
61+
62+
/**
63+
* Creates a new error type with the given name and HTTP error code, and error type{@link Code#CUSTOM}.
64+
* Allowed error codes are {@code 400 BAD REQUEST} and {@code 500 INTERNAL SERVER ERROR}.
65+
*/
66+
public static ErrorType custom(String name, int httpErrorCode) {
67+
if (httpErrorCode != 400 && httpErrorCode != 500) {
68+
throw new IllegalArgumentException("CUSTOM ErrorTypes must have HTTP error code 400 or 500");
69+
}
70+
return ImmutableErrorType.builder()
71+
.code(Code.CUSTOM)
72+
.name(name)
73+
.httpErrorCode(httpErrorCode)
74+
.build();
75+
}
76+
77+
/**
78+
* Constructs an {@link ErrorType} with the given error {@link Code} and name. Cannot use the {@link
79+
* Code#CUSTOM} error code, see {@link #custom} instead.
80+
*/
81+
public static ErrorType of(Code code, String name) {
82+
if (code == Code.CUSTOM) {
83+
throw new IllegalArgumentException("Use the custom() method to construct ErrorTypes with code CUSTOM");
84+
}
85+
return ImmutableErrorType.builder()
86+
.code(code)
87+
.name(name)
88+
.httpErrorCode(code.httpErrorCode)
89+
.build();
90+
}
91+
92+
private static ErrorType create(Code code) {
93+
return ImmutableErrorType.builder()
94+
.code(code)
95+
.name(code.name())
96+
.httpErrorCode(code.httpErrorCode)
97+
.build();
98+
}
99+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2017 Palantir Technologies, Inc. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.palantir.remoting.api.errors;
18+
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
23+
import org.immutables.value.Value;
24+
25+
@Target({ElementType.PACKAGE, ElementType.TYPE})
26+
@Retention(RetentionPolicy.CLASS)
27+
@Value.Style(
28+
visibility = Value.Style.ImplementationVisibility.PACKAGE,
29+
overshadowImplementation = true,
30+
jdkOnly = true)
31+
@interface ImmutablesStyle {}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2017 Palantir Technologies, Inc. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.palantir.remoting.api.errors;
18+
19+
/**
20+
* An exception thrown by an RPC client to indicate remote/server-side failure.
21+
*/
22+
public final class RemoteException extends RuntimeException {
23+
private static final long serialVersionUID = 1L;
24+
25+
private final SerializableError error;
26+
private final int status;
27+
28+
/** Returns the error thrown by a remote process which caused an RPC call to fail. */
29+
public SerializableError getError() {
30+
return error;
31+
}
32+
33+
/** The HTTP status code of the HTTP response conveying the remote exception. */
34+
public int getStatus() {
35+
return status;
36+
}
37+
38+
public RemoteException(SerializableError error, int status) {
39+
super(error.errorCode().equals(error.errorName())
40+
? String.format("RemoteException: %s", error.errorCode())
41+
: String.format("RemoteException: %s (%s)", error.errorCode(), error.errorName()));
42+
43+
this.error = error;
44+
this.status = status;
45+
}
46+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Copyright 2017 Palantir Technologies, Inc. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.palantir.remoting.api.errors;
18+
19+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
20+
import com.fasterxml.jackson.annotation.JsonProperty;
21+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
22+
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
23+
import com.palantir.logsafe.Arg;
24+
import java.io.Serializable;
25+
import java.util.Map;
26+
import org.immutables.value.Value;
27+
28+
/**
29+
* A JSON-serializable representation of an exception/error, represented by its error code, an error name identifying
30+
* the (specific sub-) type of error, an optional set of named parameters detailing the error condition. Intended to
31+
* transport errors through RPC channels such as HTTP responses.
32+
*/
33+
@JsonDeserialize(builder = SerializableError.Builder.class)
34+
@JsonSerialize(as = ImmutableSerializableError.class)
35+
@Value.Immutable
36+
@Value.Style(visibility = Value.Style.ImplementationVisibility.PACKAGE)
37+
@JsonIgnoreProperties(ignoreUnknown = true)
38+
public abstract class SerializableError implements Serializable {
39+
40+
/**
41+
* A fixed code word identifying the type of error. For errors generated from {@link ServiceException}, this
42+
* corresponds to the {@link ErrorType#code} and is part of the service's API surface. Clients are given access to
43+
* the server-side error code via {@link RemoteException#getError} and typically switch&dispatch on the error code
44+
* and/or name.
45+
*/
46+
@JsonProperty("errorCode")
47+
public abstract String errorCode();
48+
49+
/**
50+
* A fixed name identifying the error. For errors generated from {@link ServiceException}, this corresponding to the
51+
* {@link ErrorType#name} and is part of the service's API surface. Clients are given access to the service-side
52+
* error name via {@link RemoteException#getError} and typically switch&dispatch on the error code and/or name.
53+
*/
54+
@JsonProperty("errorName")
55+
public abstract String errorName();
56+
57+
/** A set of parameters that further explain the error. */
58+
public abstract Map<String, String> parameters();
59+
60+
61+
/**
62+
* @deprecated Used by the serialization-mechanism for back-compat only. Do not use.
63+
*/
64+
@Deprecated
65+
@Value.Derived
66+
@JsonProperty("exceptionClass")
67+
@SuppressWarnings("checkstyle:designforextension")
68+
// TODO(rfink): Remove once all error producers have switched to errorCode.
69+
String getExceptionClass() {
70+
return errorCode();
71+
}
72+
73+
/**
74+
* @deprecated Used by the serialization-mechanism for back-compat only. Do not use.
75+
*/
76+
@Deprecated
77+
@Value.Derived
78+
@JsonProperty("message")
79+
@SuppressWarnings("checkstyle:designforextension")
80+
// TODO(rfink): Remove once all error producers have switched to errorName.
81+
String getMessage() {
82+
return errorName();
83+
}
84+
85+
/**
86+
* Creates a {@link SerializableError} representation of this exception that derives from the error code and
87+
* message, as well as the {@link Arg#isSafeForLogging safe} {@link ServiceException#args parameters}.
88+
*/
89+
public static SerializableError forException(ServiceException exception) {
90+
Builder builder = new Builder()
91+
.errorCode(exception.getErrorType().code().name())
92+
.errorName(exception.getErrorType().name())
93+
.putParameters("errorId", exception.getErrorId());
94+
for (Arg<?> arg : exception.getArgs()) {
95+
if (arg.isSafeForLogging()) {
96+
builder.putParameters(arg.getName(), arg.getValue().toString());
97+
}
98+
}
99+
100+
return builder.build();
101+
}
102+
103+
// TODO(rfink): Remove once all error producers have switched to errorCode/errorName.
104+
public static final class Builder extends ImmutableSerializableError.Builder {
105+
@JsonProperty("exceptionClass")
106+
Builder exceptionClass(String exceptionClass) {
107+
return errorCode(exceptionClass);
108+
}
109+
110+
@JsonProperty("message")
111+
Builder message(String message) {
112+
return errorName(message);
113+
}
114+
}
115+
116+
public static Builder builder() {
117+
return new Builder();
118+
}
119+
}

0 commit comments

Comments
 (0)