Skip to content
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

R client refactoring #2215

Merged
merged 17 commits into from Feb 23, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -193,6 +193,8 @@ samples/client/petstore/haskell-http-client/docs/quick-jump.css

# R
.Rproj.user
samples/client/petstore/R/**/petstore.Rcheck/
samples/client/petstore/R/**/*.tar.gz

# elixir
samples/client/petstore/elixir/_build/
Expand Down
Expand Up @@ -17,8 +17,10 @@

package org.openapitools.codegen.languages;

import io.swagger.v3.oas.models.examples.Example;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.Parameter;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.*;
import org.openapitools.codegen.utils.ModelUtils;
Expand All @@ -27,6 +29,7 @@

import java.io.File;
import java.util.*;
import java.util.regex.Pattern;

import static org.openapitools.codegen.utils.StringUtils.camelize;
import static org.openapitools.codegen.utils.StringUtils.underscore;
Expand All @@ -38,6 +41,7 @@ public class RClientCodegen extends DefaultCodegen implements CodegenConfig {
protected String packageVersion = "1.0.0";
protected String apiDocPath = "docs/";
protected String modelDocPath = "docs/";
protected String testFolder = "tests/testthat";

public CodegenType getTag() {
return CodegenType.CLIENT;
Expand All @@ -54,8 +58,8 @@ public String getHelp() {
public RClientCodegen() {
super();
outputFolder = "generated-code/r";
modelTemplateFiles.put("model.mustache", ".r");
apiTemplateFiles.put("api.mustache", ".r");
modelTemplateFiles.put("model.mustache", ".R");
apiTemplateFiles.put("api.mustache", ".R");

modelDocTemplateFiles.put("model_doc.mustache", ".md");
apiDocTemplateFiles.put("api_doc.mustache", ".md");
Expand All @@ -70,7 +74,9 @@ public RClientCodegen() {
// reserved words: https://stat.ethz.ch/R-manual/R-devel/library/base/html/Reserved.html
"if", "else", "repeat", "while", "function", "for", "in",
"next", "break", "TRUE", "FALSE", "NULL", "Inf", "NaN",
"NA", "NA_integer_", "NA_real_", "NA_complex_", "NA_character_"
"NA", "NA_integer_", "NA_real_", "NA_complex_", "NA_character_",
// reserved words in API client
"ApiResponse"
)
);

Expand Down Expand Up @@ -130,11 +136,11 @@ public void processOpts() {
additionalProperties.put("apiDocPath", apiDocPath);
additionalProperties.put("modelDocPath", modelDocPath);

apiTestTemplateFiles.clear(); // TODO: add api test template
modelTestTemplateFiles.clear(); // TODO: add model test template
modelTestTemplateFiles.put("model_test.mustache", ".R");
apiTestTemplateFiles.put("api_test.mustache", ".R");

apiDocTemplateFiles.clear(); // TODO: add api doc template
modelDocTemplateFiles.clear(); // TODO: add model doc template
modelDocTemplateFiles.put("model_doc.mustache", ".md");
apiDocTemplateFiles.put("api_doc.mustache", ".md");

modelPackage = packageName;
apiPackage = packageName;
Expand All @@ -145,10 +151,11 @@ public void processOpts() {
supportingFiles.add(new SupportingFile("description.mustache", "", "DESCRIPTION"));
supportingFiles.add(new SupportingFile("Rbuildignore.mustache", "", ".Rbuildignore"));
supportingFiles.add(new SupportingFile(".travis.yml", "", ".travis.yml"));
supportingFiles.add(new SupportingFile("response.mustache", "/R", "Response.r"));
supportingFiles.add(new SupportingFile("element.mustache", "/R", "Element.r"));
supportingFiles.add(new SupportingFile("api_client.mustache", "/R", "ApiClient.r"));
supportingFiles.add(new SupportingFile("ApiResponse.mustache", File.separator + "R", "api_response.R"));
//supportingFiles.add(new SupportingFile("element.mustache", File.separator + "R", "Element.R"));
supportingFiles.add(new SupportingFile("api_client.mustache", File.separator + "R", "api_client.R"));
supportingFiles.add(new SupportingFile("NAMESPACE.mustache", "", "NAMESPACE"));
supportingFiles.add(new SupportingFile("testthat.mustache", File.separator + "tests", "testthat.R"));
}

@Override
Expand Down Expand Up @@ -180,7 +187,7 @@ public String modelFileFolder() {
}

@Override
public String toVarName(String name) {
public String toParamName(String name) {
// replace - with _ e.g. created-at => created_at
name = sanitizeName(name.replaceAll("-", "_"));

Expand All @@ -200,21 +207,22 @@ public String toVarName(String name) {
if (name.matches("^\\d.*"))
name = "Var" + name;

return name;
return name.replace("_", ".");
}

@Override
public String toParamName(String name) {
return toVarName(name);
public String toVarName(String name) {
// don't do anything as we'll put property name inside ` `, e.g. `date-time`
return name;
}

@Override
public String toModelName(String name) {
return toModelFilename(name);
public String toModelFilename(String name) {
return underscore(toModelName(name));
}

@Override
public String toModelFilename(String name) {
public String toModelName(String name) {
if (!StringUtils.isEmpty(modelNamePrefix)) {
name = modelNamePrefix + "_" + name;
}
Expand Down Expand Up @@ -246,7 +254,7 @@ public String toApiFilename(String name) {
name = name.replaceAll("-", "_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.

// e.g. PetApi.r => pet_api.r
return camelize(name + "_api");
return underscore(name + "_api");
}

@Override
Expand Down Expand Up @@ -327,7 +335,7 @@ public String toOperationId(String operationId) {
sanitizedOperationId = "call_" + sanitizedOperationId;
}

return underscore(sanitizedOperationId);
return camelize(sanitizedOperationId);
}

@Override
Expand Down Expand Up @@ -450,4 +458,179 @@ public String toEnumName(CodegenProperty property) {
return enumName;
}
}

@Override
public void setParameterExampleValue(CodegenParameter p) {
String example;

if (p.defaultValue == null) {
example = p.example;
} else {
p.example = p.defaultValue;
return;
}

String type = p.baseType;
if (type == null) {
type = p.dataType;
}

if ("character".equals(type)) {
if (example == null) {
example = p.paramName + "_example";
}
example = "'" + escapeText(example) + "'";
} else if ("integer".equals(type)) {
if (example == null) {
example = "56";
}
} else if ("numeric".equals(type)) {
if (example == null) {
example = "3.4";
}
} else if ("data.frame".equals(type)) {
if (example == null) {
example = "/path/to/file";
}
example = "File.new('" + escapeText(example) + "')";
} else if (!languageSpecificPrimitives.contains(type)) {
// type is a model class, e.g. User
example = type + "$new()";
}

if (example == null) {
example = "NULL";
} else if (Boolean.TRUE.equals(p.isListContainer)) {
example = "[" + example + "]";
} else if (Boolean.TRUE.equals(p.isMapContainer)) {
example = "{'key' => " + example + "}";
}

p.example = example;
}

/**
* Return the example value of the parameter. Overrides the
* setParameterExampleValue(CodegenParameter, Parameter) method in
* DefaultCodegen to always call setParameterExampleValue(CodegenParameter)
* in this class, which adds single quotes around strings from the
* x-example property.
*
* @param codegenParameter Codegen parameter
* @param parameter Parameter
*/
public void setParameterExampleValue(CodegenParameter codegenParameter, Parameter parameter) {
if (parameter.getExample() != null) {
codegenParameter.example = parameter.getExample().toString();
} else if (parameter.getExamples() != null && !parameter.getExamples().isEmpty()) {
Example example = parameter.getExamples().values().iterator().next();
if (example.getValue() != null) {
codegenParameter.example = example.getValue().toString();
}
} else {
Schema schema = parameter.getSchema();
if (schema != null && schema.getExample() != null) {
codegenParameter.example = schema.getExample().toString();
}
}

setParameterExampleValue(codegenParameter);
}

/**
* Return the default value of the property
* @param p OpenAPI property object
* @return string presentation of the default value of the property
*/
@Override
public String toDefaultValue(Schema p) {
if (ModelUtils.isBooleanSchema(p)) {
if (p.getDefault() != null) {
if (Boolean.valueOf(p.getDefault().toString()) == false)
return "FALSE";
else
return "TRUE";
}
// include fallback to example, default defined as server only
// example is not defined as server only
if (p.getExample() != null) {
if (Boolean.valueOf(p.getExample().toString()) == false)
return "FALSE";
else
return "TRUE";
}
} else if (ModelUtils.isDateSchema(p)) {
// TODO
} else if (ModelUtils.isDateTimeSchema(p)) {
// TODO
} else if (ModelUtils.isNumberSchema(p)) {
if (p.getDefault() != null) {
return p.getDefault().toString();
}
// default numbers are not yet returned by v2 spec openAPI results
// https://github.com/swagger-api/swagger-parser/issues/971
// include fallback to example, default defined as server only
// example is not defined as server only
if (p.getExample() != null) {
return p.getExample().toString();
}
} else if (ModelUtils.isIntegerSchema(p)) {
if (p.getDefault() != null) {
return p.getDefault().toString();
}
// default integers are not yet returned by v2 spec openAPI results
// https://github.com/swagger-api/swagger-parser/issues/971
// include fallback to example, default defined as server only
// example is not defined as server only
if (p.getExample() != null) {
return p.getExample().toString();
}
} else if (ModelUtils.isStringSchema(p)) {
if (p.getDefault() != null) {
if (Pattern.compile("\r\n|\r|\n").matcher((String) p.getDefault()).find())
return "'''" + p.getDefault() + "'''";
else
return "'" + p.getDefault() + "'";
}
// include fallback to example, default defined as server only
// example is not defined as server only
if (p.getExample() != null) {
if (Pattern.compile("\r\n|\r|\n").matcher((String) p.getExample()).find())
return "'''" + p.getExample() + "'''";
else
return "'" + p.getExample() + "'";
}
} else if (ModelUtils.isArraySchema(p)) {
if (p.getDefault() != null) {
return p.getDefault().toString();
}
// include fallback to example, default defined as server only
// example is not defined as server only
if (p.getExample() != null) {
return p.getExample().toString();
}
}

return null;
}

@Override
public String apiTestFileFolder() {
return outputFolder + File.separator + testFolder;
}

@Override
public String modelTestFileFolder() {
return outputFolder + File.separator + testFolder;
}

@Override
public String toApiTestFilename(String name) {
return "test_" + toApiFilename(name);
}

@Override
public String toModelTestFilename(String name) {
return "test_" + toModelFilename(name);
}
}
14 changes: 13 additions & 1 deletion modules/openapi-generator/src/main/resources/r/.travis.yml
@@ -1,3 +1,15 @@
# ref: https://docs.travis-ci.com/user/languages/r/
language: r
cache: packages
cache:
directories:
- /home/travis/R/Library
r_packages:
- jsonlite
- httr
# uncomment below to install deps with devtools
#install:
#- R -e 'devtools::install_deps(dep = T)'
script:
- R CMD build .
- R CMD check *tar.gz
- R CMD INSTALL *tar.gz
@@ -1,9 +1,9 @@
#' Response Class
#' ApiResponse Class
#'
#' Response Class
#' ApiResponse Class
#' @export
Response <- R6::R6Class(
'Response',
ApiResponse <- R6::R6Class(
'ApiResponse',
public = list(
content = NULL,
response = NULL,
Expand All @@ -12,4 +12,4 @@ Response <- R6::R6Class(
self$response <- response
}
)
)
)
18 changes: 18 additions & 0 deletions modules/openapi-generator/src/main/resources/r/NAMESPACE.mustache
@@ -1,8 +1,26 @@
# Generated by openapi-generator: https://openapi-generator.tech
# Do not edit by hand

# Core
export(ApiClient)
export(ApiResponse)

# Models
{{#models}}
{{#model}}
export({{{classname}}})
{{/model}}
{{/models}}

# APIs
{{#apiInfo}}
{{#apis}}
{{#operations}}
{{#operation}}
{{#-first}}
export({{{classname}}})
{{/-first}}
{{/operation}}
{{/operations}}
{{/apis}}
{{/apiInfo}}