Created by: robertpyke
PR is just a suggestion of how I think you can fix it. I'm not setup to actually run the build system at the moment. i.e. I'm not claiming this is ready to merge as is, it's just what I expect will address the issue based on my observation. I.e. You can think of this more as a bug report with attached code pointer if you like.
Description
Our model has a POST that has no request modeled. i.e. no body. The code generated normally for a PUT with a request generates something akin to:
if (!missing(`athena.create.table.request`)) {
body <- `athena.create.table.request`$toJSONString()
} else {
body <- NULL
}
...
resp <- self$apiClient$CallApi(url = paste0(self$apiClient$basePath, urlPath),
method = "PUT",
queryParams = queryParams,
headerParams = headerParams,
body = body,
...)
In the case where no request object is modeled, this initial code block (where body is set) isn't generated, so you just end up with something like:
resp <- self$apiClient$CallApi(url = paste0(self$apiClient$basePath, urlPath),
method = "POST",
queryParams = queryParams,
headerParams = headerParams,
body = body,
...)
This in turn means self$apiClient$CallApi is called with body set as the vale of the built in function base::body (as body isn't a local variable).
base::body
==>
function (fun = sys.function(sys.parent()))
{
if (is.character(fun))
fun <- get(fun, mode = "function", envir = parent.frame())
.Internal(body(fun))
}
This results in a strange error indicating that body was unexpected:
api.instance <- DefaultApi$new()
result <- api.instance$TokenExchangeStsPost()
where TokenExchangeStsPost is a POST with no request body modeled.
Error: Unknown type of `body`: must be NULL, FALSE, character, raw or list
Traceback:
1. api.instance$TokenExchangeStsPost()
2. self$TokenExchangeStsPostWithHttpInfo(...)
3. self$apiClient$CallApi(url = paste0(self$apiClient$basePath,
. urlPath), method = "POST", queryParams = queryParams, headerParams = headerParams,
. body = body, ...)
4. httr::POST(url, query = queryParams, headers, body = body, httr::content_type("application/json"),
. httpTimeout, httr::user_agent(self$userAgent), ...)
5. request_build("POST", hu$url, body_config(body, match.arg(encode)),
. as.request(config), ...)
6. body_config(body, match.arg(encode))
7. stop("Unknown type of `body`: must be NULL, FALSE, character, raw or list",
. call. = FALSE)
Swagger-codegen version
Build package: org.openapitools.codegen.languages.RClientCodegen
<dependency>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-cli</artifactId>
<version>4.2.3</version>
</dependency>
Swagger declaration file content or url
Note: I removed some of our model from what does the repro (to not expose certain info). The important bit is that the token_exchange_sts is a post, and has no request defined.
openapi: "3.0.1"
info:
title: "${title}"
version: "1.0"
description: "${description}"
x-amazon-apigateway-policy: ${policy}
servers:
-
url: "${endpoint}"
x-amazon-apigateway-endpoint-configuration:
vpcEndpointIds:
- "${vpc_ep_id}"
paths:
/token_exchange_sts:
post:
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/tokenExchangeSTSResponse'
x-amazon-apigateway-integration:
httpMethod: POST
type: aws_proxy
uri: arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/${service_function_arn}:${alias}/invocations
credentials: ${service_invoker_role_arn}
security:
- CustomAuthorizer: []
components:
securitySchemes:
CustomAuthorizer:
in: header
type: apiKey
name: Authorization
x-amazon-apigateway-authorizer:
type: token
authorizerResultTtlInSeconds: 300
authorizerUri: arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/${authorizer_function_arn}:${alias}/invocations
identityValidationExpression: Bearer .+
authorizerCredentials: ${auth_invoker_role_arn}
x-amazon-apigateway-authtype: custom
schemas:
# Response Models
tokenExchangeSTSResponse:
type: object
properties:
token:
$ref: '#/components/schemas/stsCredentials'
required:
- token
# Object Models
stsCredentials:
description: Represents STS credentials for a Session (An assumed role)
type: object
properties:
access_key_id:
type: string
readOnly: true
secret_access_key:
format: password # Hint that this shouldn't be exposed.
type: string
readOnly: true
session_token:
type: string
readOnly: true
required:
- access_key_id
- secret_access_key
- session_token
Command line used for generation
"-J-Dmodels",
"-J-Dapis",
"-J-DsupportingFiles",
"-J-DmodelTests=false",
"-J-DapiTests=false",
"generate",
"-i",
"MY FILE.yaml",
"-g",
"r",
"-o",
"MY OUTPUT PATH",
"-p",
"packageName=rproxyserviceclient"
Steps to reproduce
- Generate the client in R for a POST model with no request body (as shown in the example).
- Observe that the file default_api.R has the following:
TokenExchangeStsPostWithHttpInfo = function(...){
args <- list(...)
queryParams <- list()
headerParams <- c()
urlPath <- "/token_exchange_sts"
# API key authentication
if ("Authorization" %in% names(self$apiClient$apiKeys) && nchar(self$apiClient$apiKeys["Authorization"]) > 0) {
headerParams['Authorization'] <- paste(unlist(self$apiClient$apiKeys["Authorization"]), collapse='')
}
resp <- self$apiClient$CallApi(url = paste0(self$apiClient$basePath, urlPath),
method = "POST",
queryParams = queryParams,
headerParams = headerParams,
body = body,
...)
if (httr::status_code(resp) >= 200 && httr::status_code(resp) <= 299) {
deserializedRespObj <- tryCatch(
self$apiClient$deserialize(resp, "TokenExchangeSTSResponse", loadNamespace("rproxyserviceclient")),
error = function(e){
stop("Failed to deserialize response")
}
)
ApiResponse$new(deserializedRespObj, resp)
} else if (httr::status_code(resp) >= 300 && httr::status_code(resp) <= 399) {
ApiResponse$new(paste("Server returned " , httr::status_code(resp) , " response status code."), resp)
} else if (httr::status_code(resp) >= 400 && httr::status_code(resp) <= 499) {
ApiResponse$new("API client error", resp)
} else if (httr::status_code(resp) >= 500 && httr::status_code(resp) <= 599) {
ApiResponse$new("API server error", resp)
}
}
You can see this code is invalid:
body = body,
is "broken".
You can validate it by running the client like so:
library(rproxyserviceclient)
api.instance <- DefaultApi$new()
result <- api.instance$TokenExchangeStsPost()
api.instance$TokenExchangeStsPost() will break (Unknown type of body
: must be NULL, FALSE, character, raw or list").
Suggest a fix/enhancement
I think you should either set
body <- NULL
above in the case where no request is defined. I believe that will fix it.
Setting it here:
would effectively default it to NULL, and then the rest of the code generation could override it.
Alternatively if you weren't using a variable name that maps to the base::body, then that might avoid it as well.
I made a local change to api_client.R
# Default body to NULL when incorrectly set to a function.
# Body is incorrectly set to base::body (a built-in R function) in
# some cases due to a bug in the OpenAPI code generator
# (it assumes it has generated a local variable body when it hasn't).
# This workaround sets body to NULL if it's ever set as function by the time it gets here.
if (is.function(body)) {
body = NULL
}
but that's clearly a hack.. it works because the body is assigned to the built in function in this special case, is otherwise not a function. I only made the change in this file, as it doesn't change as we update the model, unlike default_api.R, so I changed api_client.R and added it to the ignore file for the generator (so the fix sticks).
FYI : the same template works fine in Python without any changes/hacks.