Skip to content

Commit 93d8e7d

Browse files
feat(support-for-error-templates): add the support of error templates (#53)
Exception from the server is significant. From server exception, we can take critical decisions. If the exception message from the server is not self-explanatory then it is hard for a client to respond accordingly. We noticed that we can improve the message thrown to the client from the server. Now, the client will register an error template against a particular code. On throwing an exception, the core library will replace the placeholder using JsonPointer. The user will get the proper message that he wanted. closes #52
1 parent 551a27d commit 93d8e7d

5 files changed

Lines changed: 555 additions & 34 deletions

File tree

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@
104104
<version>0.8.5</version>
105105
<scope>test</scope>
106106
</dependency>
107+
<dependency>
108+
<groupId>org.glassfish</groupId>
109+
<artifactId>javax.json</artifactId>
110+
<version>1.1.2</version>
111+
</dependency>
107112
</dependencies>
108113

109114
<build>

src/main/java/io/apimatic/core/ErrorCase.java

Lines changed: 134 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
package io.apimatic.core;
22

3+
import java.io.StringReader;
4+
import java.util.regex.Matcher;
5+
import java.util.regex.Pattern;
6+
import javax.json.Json;
7+
import javax.json.JsonException;
8+
import javax.json.JsonPointer;
9+
import javax.json.JsonReader;
10+
import javax.json.JsonStructure;
311
import io.apimatic.core.types.CoreApiException;
412
import io.apimatic.coreinterfaces.http.Context;
13+
import io.apimatic.coreinterfaces.http.HttpHeaders;
14+
import io.apimatic.coreinterfaces.http.response.Response;
515
import io.apimatic.coreinterfaces.type.functional.ExceptionCreator;
616

717
/**
@@ -19,6 +29,11 @@ public final class ErrorCase<ExceptionType extends CoreApiException> {
1929
*/
2030
private String reason;
2131

32+
/**
33+
* Error message a template or not.
34+
*/
35+
private boolean isErrorTemplate;
36+
2237
/**
2338
* An instance of {@link ExceptionCreator}.
2439
*/
@@ -28,10 +43,13 @@ public final class ErrorCase<ExceptionType extends CoreApiException> {
2843
* A private constructor.
2944
* @param reason the exception reason.
3045
* @param exceptionCreator the exceptionCreator.
46+
* @param isErrorTemplate error case for error template.
3147
*/
32-
private ErrorCase(final String reason, final ExceptionCreator<ExceptionType> exceptionCreator) {
48+
private ErrorCase(final String reason, final ExceptionCreator<ExceptionType> exceptionCreator,
49+
boolean isErrorTemplate) {
3350
this.reason = reason;
3451
this.exceptionCreator = exceptionCreator;
52+
this.isErrorTemplate = isErrorTemplate;
3553
}
3654

3755
/**
@@ -41,6 +59,9 @@ private ErrorCase(final String reason, final ExceptionCreator<ExceptionType> exc
4159
* @throws ExceptionType Represents error response from the server.
4260
*/
4361
public void throwException(Context httpContext) throws ExceptionType {
62+
if (isErrorTemplate) {
63+
replacePlaceHolder(httpContext.getResponse());
64+
}
4465
throw exceptionCreator.apply(reason, httpContext);
4566
}
4667

@@ -53,10 +74,120 @@ public void throwException(Context httpContext) throws ExceptionType {
5374
* thrown exception.
5475
* @return {@link ErrorCase}.
5576
*/
56-
public static <ExceptionType extends CoreApiException> ErrorCase<ExceptionType> create(
77+
public static <ExceptionType extends CoreApiException> ErrorCase<ExceptionType> setReason(
5778
String reason, ExceptionCreator<ExceptionType> exceptionCreator) {
58-
ErrorCase<ExceptionType> errorCase = new ErrorCase<ExceptionType>(reason, exceptionCreator);
79+
ErrorCase<ExceptionType> errorCase =
80+
new ErrorCase<ExceptionType>(reason, exceptionCreator, false);
5981
return errorCase;
6082
}
6183

84+
/**
85+
* Create the errorcase using the error reason and exception creator functional interface which
86+
* throws the respective exception while throwing.
87+
* @param <ExceptionType> Represents error response from the server.
88+
* @param reason the exception message.
89+
* @param exceptionCreator the functional interface which is responsible to create the server
90+
* thrown exception.
91+
* @return {@link ErrorCase}.
92+
*/
93+
public static <ExceptionType extends CoreApiException> ErrorCase<ExceptionType> setTemplate(
94+
String reason, ExceptionCreator<ExceptionType> exceptionCreator) {
95+
return new ErrorCase<ExceptionType>(reason, exceptionCreator, true);
96+
}
97+
98+
private void replacePlaceHolder(Response response) {
99+
replaceStatusCodeFromTemplate(response.getStatusCode());
100+
replaceHeadersFromTemplate(response.getHeaders());
101+
replaceBodyFromTemplate(response.getBody());
102+
}
103+
104+
private void replaceHeadersFromTemplate(HttpHeaders headers) {
105+
StringBuilder formatter = new StringBuilder(reason);
106+
Matcher matcher = Pattern.compile("\\{(.*?)\\}").matcher(reason);
107+
while (matcher.find()) {
108+
String key = matcher.group(1);
109+
String pointerKey = key;
110+
if (pointerKey.startsWith("$response.header.")) {
111+
pointerKey = pointerKey.replace("$response.header.", "");
112+
String formatKey = String.format("{%s}", key);
113+
int index = formatter.indexOf(formatKey);
114+
pointerKey = pointerKey.toLowerCase();
115+
if (index != -1) {
116+
formatter.replace(index, index + formatKey.length(),
117+
"" + (headers.has(pointerKey) ? headers.value(pointerKey) : ""));
118+
}
119+
}
120+
}
121+
reason = formatter.toString();
122+
}
123+
124+
private void replaceBodyFromTemplate(String responseBody) {
125+
StringBuilder formatter = new StringBuilder(reason);
126+
JsonReader jsonReader = Json.createReader(new StringReader(responseBody));
127+
JsonStructure jsonStructure = null;
128+
try {
129+
jsonStructure = jsonReader.read();
130+
} catch (Exception e) {
131+
// No need to do anything here
132+
}
133+
jsonReader.close();
134+
Matcher matcher = Pattern.compile("\\{(.*?)\\}").matcher(reason);
135+
while (matcher.find()) {
136+
String key = matcher.group(1);
137+
String pointerKey = key;
138+
replaceBodyString(responseBody, formatter, jsonStructure, key, pointerKey);
139+
}
140+
reason = formatter.toString().replaceAll("\"", "");
141+
}
142+
143+
private void replaceBodyString(String responseBody, StringBuilder formatter,
144+
JsonStructure jsonStructure, String key, String pointerKey) {
145+
if (pointerKey.startsWith("$response.body")) {
146+
String formatKey = String.format("{%s}", key);
147+
int index = formatter.indexOf(formatKey);
148+
String toReplaceString = "";
149+
toReplaceString = extractReplacementString(responseBody, jsonStructure, pointerKey,
150+
toReplaceString);
151+
if (index != -1) {
152+
try {
153+
154+
formatter.replace(index, index + formatKey.length(), toReplaceString);
155+
} catch (JsonException ex) {
156+
formatter.replace(index, index + formatKey.length(), "");
157+
}
158+
}
159+
}
160+
}
161+
162+
private String extractReplacementString(String responseBody, JsonStructure jsonStructure,
163+
String pointerKey, String toReplaceString) {
164+
if (pointerKey.contains("#")) {
165+
pointerKey = pointerKey.replace("$response.body#", "");
166+
JsonPointer jsonPointer = Json.createPointer(pointerKey);
167+
if (jsonStructure != null && jsonPointer.containsValue(jsonStructure)) {
168+
toReplaceString = jsonPointer.getValue(jsonStructure).toString();
169+
}
170+
} else {
171+
if (responseBody != null && !responseBody.isEmpty()) {
172+
toReplaceString = responseBody;
173+
}
174+
}
175+
return toReplaceString;
176+
}
177+
178+
private void replaceStatusCodeFromTemplate(int statusCode) {
179+
StringBuilder formatter = new StringBuilder(reason);
180+
Matcher matcher = Pattern.compile("\\{(.*?)\\}").matcher(reason);
181+
while (matcher.find()) {
182+
String key = matcher.group(1);
183+
if (key.equals("$statusCode")) {
184+
String formatKey = String.format("{%s}", key);
185+
int index = formatter.indexOf(formatKey);
186+
if (index != -1) {
187+
formatter.replace(index, index + formatKey.length(), "" + statusCode);
188+
}
189+
}
190+
}
191+
reason = formatter.toString();
192+
}
62193
}

src/main/java/io/apimatic/core/ResponseHandler.java

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import java.io.IOException;
44
import java.util.HashMap;
55
import java.util.Map;
6+
import java.util.regex.Matcher;
7+
import java.util.regex.Pattern;
68
import io.apimatic.core.types.CoreApiException;
79
import io.apimatic.coreinterfaces.compatibility.CompatibilityFactory;
810
import io.apimatic.coreinterfaces.http.Context;
@@ -105,13 +107,12 @@ private ResponseHandler(final Map<String, ErrorCase<ExceptionType>> localErrorCa
105107
* @throws ExceptionType Represents error response from the server.
106108
*/
107109
@SuppressWarnings("unchecked")
108-
public ResponseType handle(
109-
Request httpRequest, Response httpResponse, GlobalConfiguration globalConfiguration,
110+
public ResponseType handle(Request httpRequest, Response httpResponse,
111+
GlobalConfiguration globalConfiguration,
110112
CoreEndpointConfiguration endpointConfiguration) throws IOException, ExceptionType {
111113

112-
Context httpContext =
113-
globalConfiguration.getCompatibilityFactory().createHttpContext(httpRequest,
114-
httpResponse);
114+
Context httpContext = globalConfiguration.getCompatibilityFactory()
115+
.createHttpContext(httpRequest, httpResponse);
115116
// invoke the callback after response if its not null
116117
if (globalConfiguration.getHttpCallback() != null) {
117118
globalConfiguration.getHttpCallback().onAfterResponse(httpContext);
@@ -165,9 +166,8 @@ private <T> T applyDeserializer(Deserializer<T> deserializer, Response httpRespo
165166
}
166167

167168
@SuppressWarnings("unchecked")
168-
private <T> ResponseType createResponseClassType(
169-
Response httpResponse, GlobalConfiguration coreConfig, boolean hasBinaryResponse)
170-
throws IOException {
169+
private <T> ResponseType createResponseClassType(Response httpResponse,
170+
GlobalConfiguration coreConfig, boolean hasBinaryResponse) throws IOException {
171171
CompatibilityFactory compatibilityFactory = coreConfig.getCompatibilityFactory();
172172
switch (responseClassType) {
173173
case API_RESPONSE:
@@ -187,8 +187,8 @@ private <T> ResponseType createResponseClassType(
187187
}
188188

189189
@SuppressWarnings("unchecked")
190-
private ResponseType createDynamicResponse(
191-
Response httpResponse, CompatibilityFactory compatibilityFactory) {
190+
private ResponseType createDynamicResponse(Response httpResponse,
191+
CompatibilityFactory compatibilityFactory) {
192192
return (ResponseType) compatibilityFactory.createDynamicResponse(httpResponse);
193193
}
194194

@@ -203,19 +203,32 @@ private void validateResponse(Context httpContext) throws ExceptionType {
203203
int statusCode = response.getStatusCode();
204204
String errorCode = String.valueOf(statusCode);
205205

206-
if (localErrorCases != null && localErrorCases.containsKey(errorCode)) {
207-
localErrorCases.get(errorCode).throwException(httpContext);
208-
}
209206

210-
if (globalErrorCases != null && globalErrorCases.containsKey(errorCode)) {
211-
globalErrorCases.get(errorCode).throwException(httpContext);
212-
}
207+
throwConfiguredException(localErrorCases, errorCode, httpContext);
208+
throwConfiguredException(globalErrorCases, errorCode, httpContext);
213209

214210
if ((statusCode < MIN_SUCCESS_CODE) || (statusCode > MAX_SUCCESS_CODE)) {
215211
globalErrorCases.get(ErrorCase.DEFAULT).throwException(httpContext);
216212
}
217213
}
218214

215+
private void throwConfiguredException(Map<String, ErrorCase<ExceptionType>> errorCases,
216+
String errorCode, Context httpContext) throws ExceptionType {
217+
String defaultErrorCode = "";
218+
Matcher match = Pattern.compile("^[(4|5)[0-9]]{3}").matcher(errorCode);
219+
if (match.find()) {
220+
defaultErrorCode = errorCode.charAt(0) + "XX";
221+
}
222+
if (errorCases != null) {
223+
if (errorCases.containsKey(errorCode)) {
224+
errorCases.get(errorCode).throwException(httpContext);
225+
}
226+
if (errorCases.containsKey(defaultErrorCode)) {
227+
errorCases.get(defaultErrorCode).throwException(httpContext);
228+
}
229+
}
230+
}
231+
219232
public static class Builder<ResponseType, ExceptionType extends CoreApiException> {
220233
/**
221234
* A map of end point level errors.
@@ -258,8 +271,8 @@ public static class Builder<ResponseType, ExceptionType extends CoreApiException
258271
* @param errorCase to generate the SDK Exception.
259272
* @return {@link ResponseHandler.Builder}.
260273
*/
261-
public Builder<ResponseType, ExceptionType> localErrorCase(
262-
String statusCode, ErrorCase<ExceptionType> errorCase) {
274+
public Builder<ResponseType, ExceptionType> localErrorCase(String statusCode,
275+
ErrorCase<ExceptionType> errorCase) {
263276
if (this.localErrorCases == null) {
264277
this.localErrorCases = new HashMap<String, ErrorCase<ExceptionType>>();
265278
}

0 commit comments

Comments
 (0)