mirror of
https://github.com/OpenAPITools/openapi-generator.git
synced 2026-03-14 13:29:06 +00:00
[R] Add httr2 support (work in progress) (#13005)
* add httr2 support to r client gen * fix headers * add accepts, content-types * update samples * fix req * update samples * various fixes * add data file test * fix streaming, add tests
This commit is contained in:
@@ -64,6 +64,8 @@ public class RClientCodegen extends DefaultCodegen implements CodegenConfig {
|
||||
public static final String USE_RLANG_EXCEPTION = "useRlangExceptionHandling";
|
||||
public static final String DEFAULT = "default";
|
||||
public static final String RLANG = "rlang";
|
||||
public static final String HTTR = "httr";
|
||||
public static final String HTTR2 = "httr2";
|
||||
|
||||
// naming convention for operationId (function names in the API)
|
||||
public static final String OPERATIONID_NAMING = "operationIdNaming";
|
||||
@@ -199,6 +201,16 @@ public class RClientCodegen extends DefaultCodegen implements CodegenConfig {
|
||||
cliOptions.add(exceptionPackage);
|
||||
|
||||
cliOptions.add(CliOption.newString(CodegenConstants.ERROR_OBJECT_TYPE, "Error object type."));
|
||||
|
||||
supportedLibraries.put(HTTR2, "httr2 (https://httr2.r-lib.org/)");
|
||||
supportedLibraries.put(HTTR, "httr (https://cran.r-project.org/web/packages/httr/index.html)");
|
||||
|
||||
CliOption libraryOption = new CliOption(CodegenConstants.LIBRARY, "HTTP library template (sub-template) to use");
|
||||
libraryOption.setEnum(supportedLibraries);
|
||||
// set httr as the default
|
||||
libraryOption.setDefault(HTTR);
|
||||
cliOptions.add(libraryOption);
|
||||
setLibrary(HTTR);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -274,6 +286,17 @@ public class RClientCodegen extends DefaultCodegen implements CodegenConfig {
|
||||
supportingFiles.add(new SupportingFile("r-client.mustache", File.separator + ".github" + File.separator + "workflows", "r-client.yaml"));
|
||||
supportingFiles.add(new SupportingFile("lintr.mustache", "", ".lintr"));
|
||||
|
||||
if (HTTR.equals(getLibrary())) {
|
||||
// for httr
|
||||
setLibrary(HTTR);
|
||||
} else if (HTTR2.equals(getLibrary())) {
|
||||
// for httr2
|
||||
setLibrary(HTTR2);
|
||||
additionalProperties.put("isHttr2", Boolean.TRUE);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid HTTP library " + getLibrary() + ". Only httr, httr2 are supported.");
|
||||
}
|
||||
|
||||
// add lambda for mustache templates to fix license field
|
||||
additionalProperties.put("lambdaLicense", new Mustache.Lambda() {
|
||||
@Override
|
||||
|
||||
@@ -3,7 +3,12 @@
|
||||
|
||||
import(R6)
|
||||
import(jsonlite)
|
||||
{{^isHttr2}}
|
||||
import(httr)
|
||||
{{/isHttr2}}
|
||||
{{#isHttr2}}
|
||||
import(httr2)
|
||||
{{/isHttr2}}
|
||||
import(base64enc)
|
||||
import(stringr)
|
||||
|
||||
|
||||
@@ -233,7 +233,7 @@
|
||||
#' @export
|
||||
{{{operationId}}}{{WithHttpInfo}} = function({{#requiredParams}}{{paramName}}, {{/requiredParams}}{{#optionalParams}}{{paramName}} = {{^defaultValue}}NULL{{/defaultValue}}{{{defaultValue}}}, {{/optionalParams}}{{#vendorExtensions.x-streaming}}stream_callback = NULL, {{/vendorExtensions.x-streaming}}{{#returnType}}data_file = NULL, {{/returnType}}...) {
|
||||
args <- list(...)
|
||||
query_params <- list()
|
||||
query_params <- c()
|
||||
header_params <- c()
|
||||
|
||||
{{#requiredParams}}
|
||||
@@ -265,7 +265,12 @@
|
||||
"{{baseName}}" = {{paramName}}{{^-last}},{{/-last}}
|
||||
{{/isFile}}
|
||||
{{#isFile}}
|
||||
{{^isHttr2}}
|
||||
"{{baseName}}" = httr::upload_file({{paramName}}){{^-last}},{{/-last}}
|
||||
{{/isHttr2}}
|
||||
{{#isHttr2}}
|
||||
"{{baseName}}" = {{paramName}}{{^-last}},{{/-last}}
|
||||
{{/isHttr2}}
|
||||
{{/isFile}}
|
||||
{{/formParams}}
|
||||
)
|
||||
|
||||
@@ -11,5 +11,5 @@ Encoding: UTF-8
|
||||
License: {{#lambdaLicense}}{{licenseInfo}}{{/lambdaLicense}}{{^licenseInfo}}Unlicense{{/licenseInfo}}
|
||||
LazyData: true
|
||||
Suggests: testthat
|
||||
Imports: jsonlite, httr, R6, base64enc, stringr
|
||||
Imports: jsonlite, httr{{#isHttr2}}2{{/isHttr2}}, R6, base64enc, stringr
|
||||
RoxygenNote: 7.2.0
|
||||
|
||||
360
modules/openapi-generator/src/main/resources/r/libraries/httr2/api_client.mustache
vendored
Normal file
360
modules/openapi-generator/src/main/resources/r/libraries/httr2/api_client.mustache
vendored
Normal file
@@ -0,0 +1,360 @@
|
||||
{{>partial_header}}
|
||||
#' ApiClient Class
|
||||
#'
|
||||
#' Generic API client for OpenAPI client library builds.
|
||||
#' OpenAPI generic API client. This client handles the client-
|
||||
#' server communication, and is invariant across implementations. Specifics of
|
||||
#' the methods and models for each application are generated from the OpenAPI Generator
|
||||
#' templates.
|
||||
#'
|
||||
#' NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
#' Ref: https://openapi-generator.tech
|
||||
#' Do not edit the class manually.
|
||||
#'
|
||||
#' @docType class
|
||||
#' @title ApiClient
|
||||
#' @description ApiClient Class
|
||||
#' @format An \code{R6Class} generator object
|
||||
#' @field base_path Base url
|
||||
#' @field user_agent Default user agent
|
||||
#' @field default_headers Default headers
|
||||
#' @field username Username for HTTP basic authentication
|
||||
#' @field password Password for HTTP basic authentication
|
||||
#' @field api_keys API keys
|
||||
#' @field access_token Access token
|
||||
#' @field bearer_token Bearer token
|
||||
#' @field timeout Default timeout in seconds
|
||||
#' @field retry_status_codes vector of status codes to retry
|
||||
#' @field max_retry_attempts maximum number of retries for the status codes
|
||||
#' @importFrom httr add_headers accept timeout content
|
||||
{{#useRlangExceptionHandling}}
|
||||
#' @importFrom rlang abort
|
||||
{{/useRlangExceptionHandling}}
|
||||
#' @export
|
||||
ApiClient <- R6::R6Class(
|
||||
"ApiClient",
|
||||
public = list(
|
||||
# base path of all requests
|
||||
base_path = "{{{basePath}}}",
|
||||
# user agent in the HTTP request
|
||||
user_agent = "{{{httpUserAgent}}}{{^httpUserAgent}}OpenAPI-Generator/{{{packageVersion}}}/r{{/httpUserAgent}}",
|
||||
# default headers in the HTTP request
|
||||
default_headers = NULL,
|
||||
# username (HTTP basic authentication)
|
||||
username = NULL,
|
||||
# password (HTTP basic authentication)
|
||||
password = NULL,
|
||||
# API keys
|
||||
api_keys = NULL,
|
||||
# Access token
|
||||
access_token = NULL,
|
||||
# Bearer token
|
||||
bearer_token = NULL,
|
||||
# Time Out (seconds)
|
||||
timeout = NULL,
|
||||
# Vector of status codes to retry
|
||||
retry_status_codes = NULL,
|
||||
# Maximum number of retry attempts for the retry status codes
|
||||
max_retry_attempts = NULL,
|
||||
#' Initialize a new ApiClient.
|
||||
#'
|
||||
#' @description
|
||||
#' Initialize a new ApiClient.
|
||||
#'
|
||||
#' @param base_path Base path.
|
||||
#' @param user_agent User agent.
|
||||
#' @param default_headers Default headers.
|
||||
#' @param username User name.
|
||||
#' @param password Password.
|
||||
#' @param api_keys API keys.
|
||||
#' @param access_token Access token.
|
||||
#' @param bearer_token Bearer token.
|
||||
#' @param timeout Timeout.
|
||||
#' @param retry_status_codes Status codes for retry.
|
||||
#' @param max_retry_attempts Maxmium number of retry.
|
||||
#' @export
|
||||
initialize = function(base_path = NULL, user_agent = NULL,
|
||||
default_headers = NULL,
|
||||
username = NULL, password = NULL, api_keys = NULL,
|
||||
access_token = NULL, bearer_token = NULL, timeout = NULL,
|
||||
retry_status_codes = NULL, max_retry_attempts = NULL) {
|
||||
if (!is.null(base_path)) {
|
||||
self$base_path <- base_path
|
||||
}
|
||||
|
||||
if (!is.null(default_headers)) {
|
||||
self$default_headers <- default_headers
|
||||
}
|
||||
|
||||
if (!is.null(username)) {
|
||||
self$username <- username
|
||||
}
|
||||
|
||||
if (!is.null(password)) {
|
||||
self$password <- password
|
||||
}
|
||||
|
||||
if (!is.null(access_token)) {
|
||||
self$access_token <- access_token
|
||||
}
|
||||
|
||||
if (!is.null(bearer_token)) {
|
||||
self$bearer_token <- bearer_token
|
||||
}
|
||||
|
||||
if (!is.null(api_keys)) {
|
||||
self$api_keys <- api_keys
|
||||
} else {
|
||||
self$api_keys <- list()
|
||||
}
|
||||
|
||||
if (!is.null(user_agent)) {
|
||||
self$`user_agent` <- user_agent
|
||||
}
|
||||
|
||||
if (!is.null(timeout)) {
|
||||
self$timeout <- timeout
|
||||
}
|
||||
|
||||
if (!is.null(retry_status_codes)) {
|
||||
self$retry_status_codes <- retry_status_codes
|
||||
}
|
||||
|
||||
if (!is.null(max_retry_attempts)) {
|
||||
self$max_retry_attempts <- max_retry_attempts
|
||||
}
|
||||
},
|
||||
#' Prepare to make an API call with the retry logic.
|
||||
#'
|
||||
#' @description
|
||||
#' Prepare to make an API call with the retry logic.
|
||||
#'
|
||||
#' @param url URL.
|
||||
#' @param method HTTP method.
|
||||
#' @param query_params The query parameters.
|
||||
#' @param header_params The header parameters.
|
||||
#' @param body The HTTP request body.
|
||||
#' @param stream_callback Callback function to process the data stream
|
||||
#' @param ... Other optional arguments.
|
||||
#' @return HTTP response
|
||||
#' @export
|
||||
CallApi = function(url, method, query_params, header_params, accepts,
|
||||
content_types, body, stream_callback = NULL, ...) {
|
||||
|
||||
resp <- self$Execute(url, method, query_params, header_params,
|
||||
accepts, content_types,
|
||||
body, stream_callback = stream_callback, ...)
|
||||
#status_code <- httr::status_code(resp)
|
||||
|
||||
#if (is.null(self$max_retry_attempts)) {
|
||||
# self$req_retry(max_tries <- max_retry_attempts)
|
||||
#}
|
||||
|
||||
#if (!is.null(self$retry_status_codes)) {
|
||||
|
||||
# for (i in 1 : self$max_retry_attempts) {
|
||||
# if (status_code %in% self$retry_status_codes) {
|
||||
# Sys.sleep((2 ^ i) + stats::runif(n = 1, min = 0, max = 1))
|
||||
# resp <- self$Execute(url, method, query_params, header_params, body, stream_callback = stream_callback, ...)
|
||||
# status_code <- httr::status_code(resp)
|
||||
# } else {
|
||||
# break
|
||||
# }
|
||||
# }
|
||||
#}
|
||||
|
||||
resp
|
||||
},
|
||||
#' Make an API call
|
||||
#'
|
||||
#' @description
|
||||
#' Make an API call
|
||||
#'
|
||||
#' @param url URL.
|
||||
#' @param method HTTP method.
|
||||
#' @param query_params The query parameters.
|
||||
#' @param header_params The header parameters.
|
||||
#' @param body The HTTP request body.
|
||||
#' @param stream_callback Callback function to process data stream
|
||||
#' @param ... Other optional arguments.
|
||||
#' @return HTTP response
|
||||
#' @export
|
||||
Execute = function(url, method, query_params, header_params, accepts, content_types,
|
||||
body, stream_callback = NULL, ...) {
|
||||
req <- request(url)
|
||||
|
||||
## add headers and default headers
|
||||
if (!is.null(header_params) && length(header_params) != 0) {
|
||||
for (http_header in names(header_params)) {
|
||||
req <- req %>% req_headers(http_header = header_params[http_header])
|
||||
}
|
||||
}
|
||||
if (!is.null(self$default_headers) && length(self$default_headers) != 0) {
|
||||
for (http_header in names(header_params)) {
|
||||
req <- req %>% req_headers(http_header = self$default_headers[http_header])
|
||||
}
|
||||
}
|
||||
|
||||
# set HTTP accept header
|
||||
accept = self$select_header(accepts)
|
||||
if (!is.null(accept)) {
|
||||
req <- req %>% req_headers("Accept" = accept)
|
||||
}
|
||||
|
||||
# set HTTP content-type header
|
||||
content_type = self$select_header(content_types)
|
||||
if (!is.null(content_type)) {
|
||||
req <- req %>% req_headers("Content-Type" = content_type)
|
||||
}
|
||||
|
||||
## add query parameters
|
||||
if (length(query_params) != 0) {
|
||||
for (query_param in names(query_params)) {
|
||||
req <- req %>% req_url_query(query_param = query_params[query_param])
|
||||
}
|
||||
}
|
||||
|
||||
# add body parameters
|
||||
if (!is.null(body)) {
|
||||
req <- req %>% req_body_raw(body)
|
||||
}
|
||||
|
||||
# set timeout
|
||||
{{! Adding timeout that can be set at the apiClient object level}}
|
||||
if (!is.null(self$timeout)) {
|
||||
req <- req %>% req_timeout(self$timeout)
|
||||
}
|
||||
|
||||
# set retry
|
||||
if (!is.null(self$max_retry_attempts)) {
|
||||
req <- req %>% retry_max_tries(self$timeout)
|
||||
req <- req %>% retry_max_seconds(self$timeout)
|
||||
}
|
||||
|
||||
# set user agent
|
||||
if (!is.null(self$user_agent)) {
|
||||
req <- req %>% req_user_agent(self$user_agent)
|
||||
}
|
||||
|
||||
# set HTTP verb
|
||||
req <- req %>% req_method(method)
|
||||
|
||||
# stream data
|
||||
if (typeof(stream_callback) == "closure") {
|
||||
req %>% req_stream(stream_callback)
|
||||
} else {
|
||||
# perform the HTTP request
|
||||
resp <- req %>%
|
||||
req_error(is_error = function(resp) FALSE) %>%
|
||||
req_perform()
|
||||
|
||||
# return ApiResponse
|
||||
api_response <- ApiResponse$new()
|
||||
api_response$status_code <- resp %>% resp_status()
|
||||
api_response$status_code_desc <- resp %>% resp_status_desc()
|
||||
api_response$response <- resp %>% resp_body_string()
|
||||
api_response$headers <- resp %>% resp_headers()
|
||||
|
||||
api_response
|
||||
}
|
||||
},
|
||||
#' Deserialize the content of API response to the given type.
|
||||
#'
|
||||
#' @description
|
||||
#' Deserialize the content of API response to the given type.
|
||||
#'
|
||||
#' @param raw_response Raw response.
|
||||
#' @param return_type R return type.
|
||||
#' @param pkg_env Package environment.
|
||||
#' @return Deserialized object.
|
||||
#' @export
|
||||
deserialize = function(raw_response, return_type, pkg_env) {
|
||||
resp_obj <- jsonlite::fromJSON(raw_response)
|
||||
self$deserializeObj(resp_obj, return_type, pkg_env)
|
||||
},
|
||||
#' Deserialize the response from jsonlite object based on the given type
|
||||
#'
|
||||
#' @description
|
||||
#' Deserialize the response from jsonlite object based on the given type
|
||||
#' by handling complex and nested types by iterating recursively
|
||||
#' Example return_types will be like "array[integer]", "map(Pet)", "array[map(Tag)]", etc.,
|
||||
#'
|
||||
#' @param obj Response object.
|
||||
#' @param return_type R return type.
|
||||
#' @param pkg_env Package environment.
|
||||
#' @return Deserialized object.
|
||||
#' @export
|
||||
deserializeObj = function(obj, return_type, pkg_env) {
|
||||
return_obj <- NULL
|
||||
primitive_types <- c("character", "numeric", "integer", "logical", "complex")
|
||||
|
||||
# To handle the "map" type
|
||||
if (startsWith(return_type, "map(")) {
|
||||
inner_return_type <- regmatches(return_type,
|
||||
regexec(pattern = "map\\((.*)\\)", return_type))[[1]][2]
|
||||
return_obj <- lapply(names(obj), function(name) {
|
||||
self$deserializeObj(obj[[name]], inner_return_type, pkg_env)
|
||||
})
|
||||
names(return_obj) <- names(obj)
|
||||
} else if (startsWith(return_type, "array[")) {
|
||||
# To handle the "array" type
|
||||
inner_return_type <- regmatches(return_type,
|
||||
regexec(pattern = "array\\[(.*)\\]", return_type))[[1]][2]
|
||||
if (c(inner_return_type) %in% primitive_types) {
|
||||
return_obj <- vector("list", length = length(obj))
|
||||
if (length(obj) > 0) {
|
||||
for (row in 1:length(obj)) {
|
||||
return_obj[[row]] <- self$deserializeObj(obj[row], inner_return_type, pkg_env)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!is.null(nrow(obj))) {
|
||||
return_obj <- vector("list", length = nrow(obj))
|
||||
if (nrow(obj) > 0) {
|
||||
for (row in 1:nrow(obj)) {
|
||||
return_obj[[row]] <- self$deserializeObj(obj[row, , drop = FALSE],
|
||||
inner_return_type, pkg_env)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (exists(return_type, pkg_env) && !(c(return_type) %in% primitive_types)) {
|
||||
# To handle model objects which are not array or map containers. Ex:"Pet"
|
||||
return_type <- get(return_type, envir = as.environment(pkg_env))
|
||||
return_obj <- return_type$new()
|
||||
return_obj$fromJSON(
|
||||
jsonlite::toJSON(obj, digits = NA, auto_unbox = TRUE)
|
||||
)
|
||||
} else {
|
||||
# To handle primitive type
|
||||
return_obj <- obj
|
||||
}
|
||||
return_obj
|
||||
},
|
||||
#' Return a propery header (for accept or content-type). If JSON-related MIME is found,
|
||||
#' return it. Otherwise, return the first one, if any.
|
||||
#'
|
||||
#' @description
|
||||
#' Return a propery header (for accept or content-type). If JSON-related MIME is found,
|
||||
#' return it. Otherwise, return the first one, if any.
|
||||
#'
|
||||
#' @param headers A list of headers
|
||||
#' @return A header (e.g. 'application/json')
|
||||
#' @export
|
||||
select_header = function(headers) {
|
||||
if (length(headers) == 0) {
|
||||
return(invisible(NULL))
|
||||
} else {
|
||||
for (header in headers) {
|
||||
if (str_detect(header, "(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$")) {
|
||||
# return JSON-related MIME
|
||||
return(header)
|
||||
}
|
||||
}
|
||||
|
||||
# not json mime type, simply return the first one
|
||||
return(headers[1])
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
Reference in New Issue
Block a user