Skip to content

[R] Added handling exception with ApiException class and better documentation #3217

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/windows/r-petstore.bat
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ If Not Exist %executable% (
)

REM set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M -DloggerPath=conf/log4j.properties
set ags=generate -i modules\openapi-generator\src\test\resources\2_0\petstore.yaml -g r -o samples\client\petstore\R --additional-properties packageName=petstore
set ags=generate -i modules\openapi-generator\src\test\resources\2_0\petstore.yaml -g r -o samples\client\petstore\R --additional-properties packageName=petstore,returnExceptionOnFailure=false,exceptionPackage=default

java %JAVA_OPTS% -jar %executable% %ags%
2 changes: 2 additions & 0 deletions docs/generators/r.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ sidebar_label: r
|packageName|R package name (convention: lowercase).| |openapi|
|packageVersion|R package version.| |1.0.0|
|hideGenerationTimestamp|Hides the generation timestamp when files are generated.| |true|
|returnExceptionOnFailure|Throw an exception on non success response codes| |false|
|exceptionPackage|Specify the exception handling package|<dl><dt>**default**</dt><dd>Use stop() for raising exceptions.</dd><dt>**rlang**</dt><dd>Use rlang package for exceptions.</dd><dl>|default|
Original file line number Diff line number Diff line change
Expand Up @@ -306,4 +306,6 @@ public static enum ENUM_PROPERTY_NAMING_TYPE {camelCase, PascalCase, snake_case,
public static final String SNAPSHOT_VERSION = "snapshotVersion";
public static final String SNAPSHOT_VERSION_DESC = "Uses a SNAPSHOT version.";

public static final String EXCEPTION_ON_FAILURE = "returnExceptionOnFailure";
public static final String EXCEPTION_ON_FAILURE_DESC = "Throw an exception on non success response codes";
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ public class RClientCodegen extends DefaultCodegen implements CodegenConfig {
protected String apiDocPath = "docs/";
protected String modelDocPath = "docs/";
protected String testFolder = "tests/testthat";
protected boolean returnExceptionOnFailure = false;
protected String exceptionPackage = "default";
protected Map<String, String> exceptionPackages = new LinkedHashMap<String, String>();

public static final String EXCEPTION_PACKAGE = "exceptionPackage";
public static final String USE_DEFAULT_EXCEPTION = "useDefaultExceptionHandling";
public static final String USE_RLANG_EXCEPTION = "useRlangExceptionHandling";
public static final String DEFAULT = "default";
public static final String RLANG = "rlang";

protected boolean useDefaultExceptionHandling = false;
protected boolean useRlangExceptionHandling = false;

public CodegenType getTag() {
return CodegenType.CLIENT;
Expand Down Expand Up @@ -114,7 +126,16 @@ public RClientCodegen() {
.defaultValue("1.0.0"));
cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, CodegenConstants.HIDE_GENERATION_TIMESTAMP_DESC)
.defaultValue(Boolean.TRUE.toString()));
cliOptions.add(new CliOption(CodegenConstants.EXCEPTION_ON_FAILURE, CodegenConstants.EXCEPTION_ON_FAILURE_DESC)
.defaultValue(Boolean.FALSE.toString()));

exceptionPackages.put(DEFAULT, "Use stop() for raising exceptions.");
exceptionPackages.put(RLANG, "Use rlang package for exceptions.");

CliOption exceptionPackage = new CliOption(EXCEPTION_PACKAGE, "Specify the exception handling package");
exceptionPackage.setEnum(exceptionPackages);
exceptionPackage.setDefault(DEFAULT);
cliOptions.add(exceptionPackage);
}

@Override
Expand All @@ -133,8 +154,27 @@ public void processOpts() {
setPackageVersion("1.0.0");
}

if (additionalProperties.containsKey(CodegenConstants.EXCEPTION_ON_FAILURE)) {
boolean booleanValue = Boolean.valueOf(additionalProperties.get(CodegenConstants.EXCEPTION_ON_FAILURE).toString());
setReturnExceptionOnFailure(booleanValue);
} else {
setReturnExceptionOnFailure(false);
}

if (additionalProperties.containsKey(EXCEPTION_PACKAGE)) {
String exceptionPackage = additionalProperties.get(EXCEPTION_PACKAGE).toString();
setExceptionPackageToUse(exceptionPackage);
} else {
setExceptionPackageToUse(DEFAULT);
}

additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName);
additionalProperties.put(CodegenConstants.PACKAGE_VERSION, packageVersion);
additionalProperties.put(CodegenConstants.EXCEPTION_ON_FAILURE, returnExceptionOnFailure);

additionalProperties.put(USE_DEFAULT_EXCEPTION, this.useDefaultExceptionHandling);
additionalProperties.put(USE_RLANG_EXCEPTION, this.useRlangExceptionHandling);


additionalProperties.put("apiDocPath", apiDocPath);
additionalProperties.put("modelDocPath", modelDocPath);
Expand Down Expand Up @@ -384,6 +424,19 @@ public void setPackageVersion(String packageVersion) {
this.packageVersion = packageVersion;
}

public void setReturnExceptionOnFailure(boolean returnExceptionOnFailure) {
this.returnExceptionOnFailure = returnExceptionOnFailure;
}

public void setExceptionPackageToUse(String exceptionPackage) {
if(DEFAULT.equals(exceptionPackage))
this.useDefaultExceptionHandling = true;
if(RLANG.equals(exceptionPackage)){
supportingFiles.add(new SupportingFile("api_exception.mustache", File.separator + "R", "api_exception.R"));
this.useRlangExceptionHandling = true;
}
}

@Override
public String escapeQuotationMark(String input) {
// remove " to avoid code injection
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#' ApiResponse Class
#'
#' ApiResponse Class
#' @docType class
#' @title ApiResponse
#' @description ApiResponse Class
#' @format An \code{R6Class} generator object
#' @field content The deserialized response body.
#' @field response The raw response from the endpoint.
#' @export
ApiResponse <- R6::R6Class(
'ApiResponse',
Expand Down
182 changes: 176 additions & 6 deletions modules/openapi-generator/src/main/resources/r/api.mustache
Original file line number Diff line number Diff line change
@@ -1,23 +1,150 @@
{{>partial_header}}
{{#operations}}
#' @docType class
#' @title {{baseName}} operations
#' @description {{importPath}}
#'
#' @field path Stores url path of the request.
#' @format An \code{R6Class} generator object
#' @field apiClient Handles the client-server communication.
#'
#' @importFrom R6 R6Class
#'
#' @section Methods:
{{! Adding the below changes for generating documentation for the api methods. }}
#' \describe{
{{#operation}}
#' \strong{ {{operationId}} } \emph{ {{summary}} }
#' {{notes}}
#'
#' \itemize{
{{#allParams}}
{{#isEnum}}
#' \item \emph{ @param } {{paramName}} Enum < {{#allowableValues}}{{values}}{{/allowableValues}} >
{{/isEnum}}
{{^isEnum}}
{{#isContainer}}
{{#isListContainer}}
{{#items}}
{{#isPrimitiveType}}
#' \item \emph{ @param } {{paramName}} list( {{dataType}} )
{{/isPrimitiveType}}
{{^isPrimitiveType}}
#' \item \emph{ @param } {{paramName}} list( \link[{{packageName}}:{{baseType}}]{ {{dataType}} } )
{{/isPrimitiveType}}
{{/items}}
{{/isListContainer}}
{{#isMapContainer}}
{{#isPrimitiveType}}
#' \item \emph{ @param } {{paramName}} named list( {{dataType}} )
{{/isPrimitiveType}}
{{^isPrimitiveType}}
#' \item \emph{ @param } {{paramName}} named list( \link[{{packageName}}:{{baseType}}]{ {{dataType}} } )
{{/isPrimitiveType}}
{{/isMapContainer}}
{{/isContainer}}
{{^isContainer}}
{{#isPrimitiveType}}
#' \item \emph{ @param } {{paramName}} {{dataType}}
{{/isPrimitiveType}}
{{^isPrimitiveType}}
#' \item \emph{ @param } {{paramName}} \link[{{packageName}}:{{baseType}}]{ {{dataType}} }
{{/isPrimitiveType}}
{{/isContainer}}
{{/isEnum}}
{{/allParams}}
{{#returnType}}
{{^returnTypeIsPrimitive}}
#' \item \emph{ @returnType } \link[{{packageName}}:{{returnBaseType}}]{ {{#returnContainer}}{{#isListContainer}}list({{returnBaseType}}){{/isListContainer}}{{#isMapContainer}}named list({{returnBaseType}}){{/isMapContainer}}{{/returnContainer}}{{^returnContainer}}{{returnType}}{{/returnContainer}} } \cr
{{/returnTypeIsPrimitive}}
{{/returnType}}
#'
{{#useRlangExceptionHandling}}
#' \item On encountering errors, an error of subclass ApiException will be thrown.
{{/useRlangExceptionHandling}}
#'
{{#responses}}
#' \item status code : {{code}} | {{message}}
#'
#' {{operationId}} {{summary}}
#'{{#dataType}} \item return type : {{dataType}} {{/dataType}}
#' \item response headers :
#'
#' \tabular{ll}{
{{#headers}}
#' {{name}} \tab {{description}} \cr
{{/headers}}
#' }
{{/responses}}
#' }
#'
{{/operation}}
#' }
#'
#'
#' @examples
#' \donttest{
{{#operation}}
#' #################### {{operationId}} ####################
#'
#' library({{{packageName}}})
{{#allParams}}
#' var.{{{paramName}}} <- {{{example}}} # {{{dataType}}} | {{{description}}}
{{/allParams}}
#'
{{#summary}}
#' #{{{.}}}
{{/summary}}
#' api.instance <- {{{classname}}}$new()
{{#hasAuthMethods}}
{{#authMethods}}
#'
{{#isBasic}}
#' #Configure HTTP basic authorization: {{{name}}}
#' # provide your username in the user-serial format
#' api.instance$apiClient$username <- '<user-serial>';
#' # provide your api key generated using the developer portal
#' api.instance$apiClient$password <- '<api_key>';
{{/isBasic}}
{{#isApiKey}}
#' #Configure API key authorization: {{{name}}}
#' api.instance$apiClient$apiKeys['{{{keyParamName}}}'] <- 'TODO_YOUR_API_KEY';
{{/isApiKey}}
{{#isOAuth}}
#' # Configure OAuth2 access token for authorization: {{{name}}}
#' api.instance$apiClient$accessToken <- 'TODO_YOUR_ACCESS_TOKEN';
{{/isOAuth}}
{{/authMethods}}
{{/hasAuthMethods}}
#'
{{#returnExceptionOnFailure}}
{{#useRlangExceptionHandling}}
#'result <- tryCatch(
#' api.instance${{{operationId}}}({{#requiredParams}}var.{{{paramName}}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#optionalParams}}{{#-first}}{{#requiredParams.0}}, {{/requiredParams.0}}{{/-first}}{{{paramName}}}=var.{{{paramName}}}{{^-last}}, {{/-last}}{{/optionalParams}}),
#' ApiException = function(ex) ex
#' )
#' # In case of error, print the error object
#' if(!is.null(result$ApiException)) {
#' cat(result$ApiException$toString())
#' } else {
{{#returnType}}
#' # deserialized response object
#' response.object <- result$content
{{/returnType}}
#' # response headers
#' response.headers <- result$response$headers
#' # response status code
#' response.status.code <- result$response$status_code
#' }
{{/useRlangExceptionHandling}}
{{/returnExceptionOnFailure}}
{{^useRlangExceptionHandling}}
#' result <- api.instance${{{operationId}}}({{#requiredParams}}var.{{{paramName}}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#optionalParams}}{{#-first}}{{#requiredParams.0}}, {{/requiredParams.0}}{{/-first}}{{{paramName}}}=var.{{{paramName}}}{{^-last}}, {{/-last}}{{/optionalParams}})
{{/useRlangExceptionHandling}}
#'
#'
{{/operation}}
#' }
#' @importFrom R6 R6Class
#' @importFrom caTools base64encode
{{#useRlangExceptionHandling}}
#' @importFrom rlang abort
{{/useRlangExceptionHandling}}
#' @export
{{classname}} <- R6::R6Class(
'{{classname}}',
Expand Down Expand Up @@ -51,7 +178,12 @@

{{#requiredParams}}
if (missing(`{{paramName}}`)) {
{{#useDefaultExceptionHandling}}
stop("Missing required parameter `{{{paramName}}}`.")
{{/useDefaultExceptionHandling}}
{{#useRlangExceptionHandling}}
rlang::abort(message = "Missing required parameter `{{{paramName}}}`.", .subclass = "ApiException", ApiException = ApiException$new(status = 0, reason = "Missing required parameter `{{{paramName}}}`."))
{{/useRlangExceptionHandling}}
}

{{/requiredParams}}
Expand Down Expand Up @@ -141,7 +273,17 @@
ApiResponse$new(content,resp)
{{/isPrimitiveType}}
{{^isPrimitiveType}}
deserializedRespObj <- self$apiClient$deserialize(resp, "{{returnType}}", "package:{{packageName}}")
deserializedRespObj <- tryCatch(
self$apiClient$deserialize(resp, "{{returnType}}", "package:{{packageName}}"),
error = function(e){
{{#useDefaultExceptionHandling}}
stop("Failed to deserialize response")
{{/useDefaultExceptionHandling}}
{{#useRlangExceptionHandling}}
rlang::abort(message = "Failed to deserialize response", .subclass = "ApiException", ApiException = ApiException$new(http_response = resp))
{{/useRlangExceptionHandling}}
}
)
ApiResponse$new(deserializedRespObj, resp)
{{/isPrimitiveType}}
{{/returnType}}
Expand All @@ -150,9 +292,37 @@
ApiResponse$new(NULL, resp)
{{/returnType}}
} else if (httr::status_code(resp) >= 400 && httr::status_code(resp) <= 499) {
{{#returnExceptionOnFailure}}
errorMsg <- toString(content(resp))
if(errorMsg == ""){
errorMsg <- "Api client exception encountered."
}
{{#useDefaultExceptionHandling}}
stop(errorMsg)
{{/useDefaultExceptionHandling}}
{{#useRlangExceptionHandling}}
rlang::abort(message = errorMsg, .subclass = "ApiException", ApiException = ApiException$new(http_response = resp))
{{/useRlangExceptionHandling}}
{{/returnExceptionOnFailure}}
{{^returnExceptionOnFailure}}
ApiResponse$new("API client error", resp)
{{/returnExceptionOnFailure}}
} else if (httr::status_code(resp) >= 500 && httr::status_code(resp) <= 599) {
{{#returnExceptionOnFailure}}
errorMsg <- toString(content(resp))
if(errorMsg == ""){
errorMsg <- "Api server exception encountered."
}
{{#useDefaultExceptionHandling}}
stop(errorMsg)
{{/useDefaultExceptionHandling}}
{{#useRlangExceptionHandling}}
rlang::abort(message = errorMsg, .subclass = "ApiException", ApiException = ApiException$new(http_response = resp))
{{/useRlangExceptionHandling}}
{{/returnExceptionOnFailure}}
{{^returnExceptionOnFailure}}
ApiResponse$new("API server error", resp)
{{/returnExceptionOnFailure}}
}
}{{#hasMore}},{{/hasMore}}
{{/operation}}
Expand Down
Loading