Better help messages

FIXED: save ApiClient.translator
CHANGED: ApiClient now respects more resource annotations for building help messages (success and error responses)
CHANGED: Added more detailed annotations to BlocksResource
This commit is contained in:
Kc 2018-09-24 23:36:57 +02:00
parent a85a558923
commit 75adc7453c
3 changed files with 109 additions and 30 deletions

View File

@ -3,6 +3,8 @@ package api;
import globalization.Translator;
import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
@ -30,16 +32,20 @@ import settings.Settings;
public class ApiClient {
private class HelpString {
private class HelpInfo {
public final Pattern pattern;
public final String fullPath;
public final String description;
public final List<String> success;
public final List<String> errors;
public HelpString(Pattern pattern, String fullPath, String description) {
public HelpInfo(Pattern pattern, String fullPath, String description, List<String> success, List<String> errors) {
this.pattern = pattern;
this.fullPath = fullPath;
this.description = description;
this.success = success;
this.errors = errors;
}
}
@ -55,11 +61,12 @@ public class ApiClient {
ApiService apiService;
private Translator translator;
List<HelpString> helpStrings;
List<HelpInfo> helpInfos;
public ApiClient(ApiService apiService, Translator translator) {
this.apiService = apiService;
this.helpStrings = getHelpStrings(apiService.getResources());
this.translator = translator;
this.helpInfos = getHelpInfos(apiService.getResources());
}
//XXX: replace singleton pattern by dependency injection?
@ -73,9 +80,11 @@ public class ApiClient {
return instance;
}
private List<HelpString> getHelpStrings(Iterable<Class<?>> resources) {
List<HelpString> result = new ArrayList<>();
private List<HelpInfo> getHelpInfos(Iterable<Class<?>> resources) {
List<HelpInfo> result = new ArrayList<>();
// TODO: need some way to realize translation from resource annotations
// scan each resource class
for (Class<?> resource : resources) {
if (OpenApiResource.class.isAssignableFrom(resource)) {
@ -96,6 +105,38 @@ public class ApiClient {
}
String description = operationAnnotation.description();
// extract responses
ArrayList success = new ArrayList();
ArrayList errors = new ArrayList();
for(ApiResponse response : operationAnnotation.responses()) {
String responseDescription = response.description();
if(StringUtils.isBlank(responseDescription))
continue; // ignore responses without description
try {
// try to identify response type by status code
int responseCode = Integer.parseInt(response.responseCode());
if(responseCode >= 400) {
errors.add(responseDescription);
} else {
success.add(responseDescription);
}
} catch (NumberFormatException e) {
// try to identify response type by content
if(response.content().length > 0) {
Content content = response.content()[0];
Class<?> implementation = content.schema().implementation();
if(implementation != null && ApiErrorMessage.class.isAssignableFrom(implementation)) {
errors.add(responseDescription);
} else {
success.add(responseDescription);
}
} else {
success.add(responseDescription);
}
}
}
Path methodPath = method.getDeclaredAnnotation(Path.class);
String methodPathString = (methodPath != null) ? methodPath.value() : "";
@ -110,9 +151,10 @@ public class ApiClient {
HttpMethod httpMethod = annotation.annotationType().getDeclaredAnnotation(HttpMethod.class);
String httpMethodString = httpMethod.value();
String fullPath = httpMethodString + " " + resourcePathString + methodPathString;
String fullPath = httpMethodString + " " + resourcePathString + methodPathString;
Pattern pattern = Pattern.compile("^ *(" + httpMethodString + " *)?" + getHelpPatternForPath(resourcePathString + methodPathString));
result.add(new HelpString(pattern, fullPath, description));
result.add(new HelpInfo(pattern, fullPath, description, success, errors));
}
}
}
@ -151,7 +193,7 @@ public class ApiClient {
StringBuilder result = new StringBuilder();
boolean showAll = command.trim().equalsIgnoreCase("all");
for (HelpString helpString : helpStrings) {
for (HelpInfo helpString : helpInfos) {
if (showAll || helpString.pattern.matcher(command).matches()) {
appendHelp(result, helpString);
}
@ -194,8 +236,20 @@ public class ApiClient {
return result.toString();
}
private void appendHelp(StringBuilder builder, HelpString helpString) {
builder.append(helpString.fullPath + "\n");
builder.append(helpString.description + "\n");
private void appendHelp(StringBuilder builder, HelpInfo help) {
builder.append(help.fullPath + "\n");
builder.append(" " + help.description + "\n");
if(help.success != null && help.success.size() > 0) {
builder.append(" On success returns:\n");
for(String content : help.success) {
builder.append(" " + content + "\n");
}
}
if(help.errors != null && help.errors.size() > 0) {
builder.append(" On failure returns:\n");
for(String content : help.errors) {
builder.append(" " + content + "\n");
}
}
}
}

View File

@ -150,14 +150,27 @@ public class ApiErrorFactory {
String templateKey = String.format("%s: ApiError.%s message", ApiErrorFactory.class.getSimpleName(), errorCode.name());
return new ErrorMessageEntry(templateKey, defaultTemplate, templateValues);
}
public String getErrorMessage(Locale locale, ApiError error) {
ErrorMessageEntry errorMessage = this.errorMessages.get(error);
String message = this.translator.translate(locale, errorMessage.templateKey, errorMessage.defaultTemplate, errorMessage.templateValues);
return message;
}
public ApiException createError(ApiError error) {
return createError(error, null);
}
public ApiException createError(Locale locale, ApiError error) {
return createError(locale, error, null);
}
public ApiException createError(ApiError error, Throwable throwable) {
Locale locale = Locale.ENGLISH; // XXX: should this be in local language?
Locale locale = Locale.ENGLISH; // default locale
return createError(locale, error, throwable);
}
public ApiException createError(Locale locale, ApiError error, Throwable throwable) {
// TODO: handle AT errors
// old AT error handling
// JSONObject jsonObject = new JSONObject();
@ -173,9 +186,8 @@ public class ApiErrorFactory {
//
//
// return new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity(jsonObject.toJSONString()).build());
ErrorMessageEntry errorMessage = this.errorMessages.get(error);
String message = this.translator.translate(locale, errorMessage.templateKey, errorMessage.defaultTemplate, errorMessage.templateValues);
String message = getErrorMessage(locale, error);
return new ApiException(error.getStatus(), error.getCode(), message, throwable);
}
}

View File

@ -40,11 +40,12 @@ public class BlocksResource {
responses = {
@ApiResponse(
description = "The blocks"
//content = @Content(schema = @Schema(implementation = ???))
//content = @Content(schema = @Schema(implementation = ???))
),
@ApiResponse(
responseCode = "422",
description = "Wallet does not exist"
description = "Error: 201 - Wallet does not exist",
content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
)
}
)
@ -65,15 +66,18 @@ public class BlocksResource {
),
@ApiResponse(
responseCode = "400",
description = "Invalid address"
description = "102 - Invalid address",
content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
),
@ApiResponse(
responseCode = "422",
description = "Wallet does not exist"
description = "201 - Wallet does not exist",
content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
),
@ApiResponse(
responseCode = "422",
description = "Address does not exist in wallet"
description = "202 - Address does not exist in wallet",
content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
)
}
)
@ -94,11 +98,13 @@ public class BlocksResource {
),
@ApiResponse(
responseCode = "400",
description = "Invalid signature"
description = "101 - Invalid signature",
content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
),
@ApiResponse(
responseCode = "422",
description = "Block does not exist"
description = "301 - Block does not exist",
content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
)
}
)
@ -153,11 +159,13 @@ public class BlocksResource {
),
@ApiResponse(
responseCode = "400",
description = "Invalid signature"
description = "101 - Invalid signature",
content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
),
@ApiResponse(
responseCode = "422",
description = "Block does not exist"
description = "301 - Block does not exist",
content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
)
}
)
@ -195,11 +203,13 @@ public class BlocksResource {
),
@ApiResponse(
responseCode = "400",
description = "Invalid signature"
description = "101 - Invalid signature",
content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
),
@ApiResponse(
responseCode = "422",
description = "Block does not exist"
description = "301 - Block does not exist",
content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
)
}
)
@ -275,11 +285,13 @@ public class BlocksResource {
),
@ApiResponse(
responseCode = "400",
description = "Invalid signature"
description = "101 - Invalid signature",
content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
),
@ApiResponse(
responseCode = "422",
description = "Block does not exist"
description = "301 - Block does not exist",
content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
)
}
)
@ -300,7 +312,8 @@ public class BlocksResource {
),
@ApiResponse(
responseCode = "422",
description = "Block does not exist"
description = "301 - Block does not exist",
content = @Content(schema = @Schema(implementation = ApiErrorMessage.class))
)
}
)