Skip to content

Commit 6399a7a

Browse files
authored
[R] improve binary/text response handling (#20131)
* [R client] better support for binary/compressed responses * cleanup * revert change after PR review * update samples * fix R tests * move private api methods to api-client, revert breaking method name change
1 parent c33d3aa commit 6399a7a

34 files changed

+1522
-1101
lines changed

modules/openapi-generator/src/main/resources/r/ApiResponse.mustache

-3
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,6 @@ ApiResponse <- R6::R6Class(
5050
self$response <- charToRaw(jsonlite::toJSON("NULL"))
5151
}
5252
text_response <- iconv(readBin(self$response, character()), from = from_encoding, to = to_encoding)
53-
if (is.na(text_response)) {
54-
warning("The response is binary and will not be converted to text.")
55-
}
5653
return(text_response)
5754
}
5855
)

modules/openapi-generator/src/main/resources/r/api.mustache

+14-16
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,13 @@
153153
154154
{{/vendorExtensions.x-streaming}}
155155
if (local_var_response$status_code >= 200 && local_var_response$status_code <= 299) {
156-
local_var_response$content
156+
return(local_var_response$content)
157157
} else if (local_var_response$status_code >= 300 && local_var_response$status_code <= 399) {
158-
local_var_response
158+
return(local_var_response)
159159
} else if (local_var_response$status_code >= 400 && local_var_response$status_code <= 499) {
160-
local_var_response
160+
return(local_var_response)
161161
} else if (local_var_response$status_code >= 500 && local_var_response$status_code <= 599) {
162-
local_var_response
162+
return(local_var_response)
163163
}
164164
},
165165
@@ -543,24 +543,21 @@
543543
if (local_var_resp$status_code >= 200 && local_var_resp$status_code <= 299) {
544544
{{#returnType}}
545545
{{#isPrimitiveType}}
546-
local_var_content <- local_var_resp$response
547-
local_var_resp, "text", encoding = "UTF-8", simplifyVector = FALSE
548-
)
549546
# save response in a file
550547
if (!is.null(data_file)) {
551-
write(local_var_content, data_file)
548+
self$api_client$WriteFile(local_var_resp, data_file)
552549
}
553550
554551
ApiResponse$new(content,resp)
555552
{{/isPrimitiveType}}
556553
{{^isPrimitiveType}}
557554
# save response in a file
558555
if (!is.null(data_file)) {
559-
write(local_var_resp$response, data_file)
556+
self$api_client$WriteFile(local_var_resp, data_file)
560557
}
561558
562559
deserialized_resp_obj <- tryCatch(
563-
self$api_client$deserialize(local_var_resp$response_as_text(), "{{returnType}}", loadNamespace("{{packageName}}")),
560+
self$api_client$DeserializeResponse(local_var_resp, "{{returnType}}"),
564561
error = function(e) {
565562
{{#useDefaultExceptionHandling}}
566563
stop("Failed to deserialize response")
@@ -579,10 +576,13 @@
579576
{{! Returning the ApiResponse object with NULL object when the endpoint doesn't return anything}}
580577
local_var_resp$content <- NULL
581578
{{/returnType}}
582-
local_var_resp
583-
} else if (local_var_resp$status_code >= 300 && local_var_resp$status_code <= 399) {
579+
return(local_var_resp)
580+
}
581+
582+
local_var_error_msg <- local_var_resp$response_as_text()
583+
if (local_var_resp$status_code >= 300 && local_var_resp$status_code <= 399) {
584584
{{#returnExceptionOnFailure}}
585-
local_var_error_msg <- local_var_resp$response
585+
586586
if (local_var_error_msg == "") {
587587
local_var_error_msg <- paste("Server returned ", local_var_resp$status_code, " response status code.")
588588
}
@@ -600,7 +600,6 @@
600600
{{/returnExceptionOnFailure}}
601601
} else if (local_var_resp$status_code >= 400 && local_var_resp$status_code <= 499) {
602602
{{#returnExceptionOnFailure}}
603-
local_var_error_msg <- local_var_resp$response
604603
if (local_var_error_msg == "") {
605604
local_var_error_msg <- "Api client exception encountered."
606605
}
@@ -618,7 +617,6 @@
618617
{{/returnExceptionOnFailure}}
619618
} else if (local_var_resp$status_code >= 500 && local_var_resp$status_code <= 599) {
620619
{{#returnExceptionOnFailure}}
621-
local_var_error_msg <- local_var_resp$response
622620
if (local_var_error_msg == "") {
623621
local_var_error_msg <- "Api server exception encountered."
624622
}
@@ -635,7 +633,7 @@
635633
if (is.null(local_var_resp$response) || local_var_resp$response == "") {
636634
local_var_resp$response <- "API server error"
637635
}
638-
local_var_resp
636+
return(local_var_resp)
639637
{{/returnExceptionOnFailure}}
640638
}
641639
}{{^-last}},{{/-last}}

modules/openapi-generator/src/main/resources/r/api_client.mustache

+46
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,52 @@ ApiClient <- R6::R6Class(
430430
# not json mime type, simply return the first one
431431
return(headers[1])
432432
}
433+
},
434+
435+
#' @description
436+
#' Deserialize the response
437+
#'
438+
#' @param local_var_resp The API response
439+
#' @param return_type The target return type for the endpoint (e.g., `"object"`). If `NULL` text will be left as-is.
440+
#' @return If the raw response is corecable to text, return the text. Otherwise return the raw resposne.
441+
DeserializeResponse = function(local_var_resp, return_type = NULL) {
442+
text <- local_var_resp$response_as_text()
443+
if (is.na(text)) {
444+
return(local_var_resp$response)
445+
} else if (is.null(return_type)) {
446+
return(text)
447+
}
448+
return(self$deserialize(text, return_type, loadNamespace("{{packageName}}")))
449+
},
450+
451+
#' @description
452+
#' Write response to a file
453+
#'
454+
#' The function will write out data.
455+
#'
456+
#' 1. If binary data is detected it will use `writeBin`
457+
#' 2. If the raw response is coercable to text, the text will be written to a file
458+
#' 3. If the raw response is not coercable to text, the raw response will be written
459+
#'
460+
#' @param local_var_resp The API response
461+
#' @param file The name of the data file to save the result
462+
WriteFile = function(local_var_resp, file) {
463+
if (self$IsBinary(local_var_resp$response)) {
464+
writeBin(local_var_resp$response, file)
465+
} else {
466+
response <- self$DeserializeResponse(local_var_resp)
467+
base::write(response, file)
468+
}
469+
},
470+
471+
#' @description
472+
#' Check response for binary content
473+
#'
474+
#' @param local_var_resp The API response
475+
IsBinary = function(x) {
476+
# ref: https://stackoverflow.com/a/17098690/1785752
477+
b <- readBin(x, "int", n = 1000, size=1, signed=FALSE)
478+
return(max(b) > 128)
433479
}
434480
)
435481
)

modules/openapi-generator/src/main/resources/r/api_exception.mustache

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,15 @@ ApiException <- R6::R6Class(
3232
initialize = function(status = NULL, reason = NULL, http_response = NULL) {
3333
if (!is.null(http_response)) {
3434
self$status <- http_response$status_code
35-
errorMsg <- http_response$response
35+
errorMsg <- http_response$response_as_text()
3636
if (is.null(errorMsg) || errorMsg == "") {
3737
errorMsg <- "Api exception encountered. No details given."
3838
}
3939
self$body <- errorMsg
4040
self$headers <- http_response$headers
4141
self$reason <- http_response$http_status_desc
4242
{{#errorObjectType}}
43-
self$error_object <- {{errorObjectType}}$new()$fromJSONString(http_response$response)
43+
self$error_object <- {{errorObjectType}}$new()$fromJSONString(http_response$response_as_text())
4444
{{/errorObjectType}}
4545
} else {
4646
self$status <- status

modules/openapi-generator/src/main/resources/r/libraries/httr2/api_client.mustache

+46
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,52 @@ ApiClient <- R6::R6Class(
443443
# not json mime type, simply return the first one
444444
return(headers[1])
445445
}
446+
},
447+
448+
#' @description
449+
#' Deserialize the response
450+
#'
451+
#' @param local_var_resp The API response
452+
#' @param return_type The target return type for the endpoint (e.g., `"object"`). If `NULL` text will be left as-is.
453+
#' @return If the raw response is corecable to text, return the text. Otherwise return the raw resposne.
454+
DeserializeResponse = function(local_var_resp, return_type = NULL) {
455+
text <- local_var_resp$response_as_text()
456+
if (is.na(text)) {
457+
return(local_var_resp$response)
458+
} else if (is.null(return_type)) {
459+
return(text)
460+
}
461+
return(self$deserialize(text, return_type, loadNamespace("{{packageName}}")))
462+
},
463+
464+
#' @description
465+
#' Write response to a file
466+
#'
467+
#' The function will write out data.
468+
#'
469+
#' 1. If binary data is detected it will use `writeBin`
470+
#' 2. If the raw response is coercable to text, the text will be written to a file
471+
#' 3. If the raw response is not coercable to text, the raw response will be written
472+
#'
473+
#' @param local_var_resp The API response
474+
#' @param file The name of the data file to save the result
475+
WriteFile = function(local_var_resp, file) {
476+
if (self$IsBinary(local_var_resp$response)) {
477+
writeBin(local_var_resp$response, file)
478+
} else {
479+
response <- self$DeserializeResponse(local_var_resp)
480+
base::write(response, file)
481+
}
482+
},
483+
484+
#' @description
485+
#' Check response for binary content
486+
#'
487+
#' @param local_var_resp The API response
488+
IsBinary = function(x) {
489+
# ref: https://stackoverflow.com/a/17098690/1785752
490+
b <- readBin(x, "int", n = 1000, size=1, signed=FALSE)
491+
return(max(b) > 128)
446492
}
447493
)
448494
)

samples/client/echo_api/r/R/api_client.R

+46
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,52 @@ ApiClient <- R6::R6Class(
382382
# not json mime type, simply return the first one
383383
return(headers[1])
384384
}
385+
},
386+
387+
#' @description
388+
#' Deserialize the response
389+
#'
390+
#' @param local_var_resp The API response
391+
#' @param return_type The target return type for the endpoint (e.g., `"object"`). If `NULL` text will be left as-is.
392+
#' @return If the raw response is corecable to text, return the text. Otherwise return the raw resposne.
393+
DeserializeResponse = function(local_var_resp, return_type = NULL) {
394+
text <- local_var_resp$response_as_text()
395+
if (is.na(text)) {
396+
return(local_var_resp$response)
397+
} else if (is.null(return_type)) {
398+
return(text)
399+
}
400+
return(self$deserialize(text, return_type, loadNamespace("openapi")))
401+
},
402+
403+
#' @description
404+
#' Write response to a file
405+
#'
406+
#' The function will write out data.
407+
#'
408+
#' 1. If binary data is detected it will use `writeBin`
409+
#' 2. If the raw response is coercable to text, the text will be written to a file
410+
#' 3. If the raw response is not coercable to text, the raw response will be written
411+
#'
412+
#' @param local_var_resp The API response
413+
#' @param file The name of the data file to save the result
414+
WriteFile = function(local_var_resp, file) {
415+
if (self$IsBinary(local_var_resp$response)) {
416+
writeBin(local_var_resp$response, file)
417+
} else {
418+
response <- self$DeserializeResponse(local_var_resp)
419+
base::write(response, file)
420+
}
421+
},
422+
423+
#' @description
424+
#' Check response for binary content
425+
#'
426+
#' @param local_var_resp The API response
427+
IsBinary = function(x) {
428+
# ref: https://stackoverflow.com/a/17098690/1785752
429+
b <- readBin(x, "int", n = 1000, size=1, signed=FALSE)
430+
return(max(b) > 128)
385431
}
386432
)
387433
)

samples/client/echo_api/r/R/api_response.R

-3
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,6 @@ ApiResponse <- R6::R6Class(
5757
self$response <- charToRaw(jsonlite::toJSON("NULL"))
5858
}
5959
text_response <- iconv(readBin(self$response, character()), from = from_encoding, to = to_encoding)
60-
if (is.na(text_response)) {
61-
warning("The response is binary and will not be converted to text.")
62-
}
6360
return(text_response)
6461
}
6562
)

samples/client/echo_api/r/R/auth_api.R

+24-18
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,13 @@ AuthApi <- R6::R6Class(
7878
TestAuthHttpBasic = function(data_file = NULL, ...) {
7979
local_var_response <- self$TestAuthHttpBasicWithHttpInfo(data_file = data_file, ...)
8080
if (local_var_response$status_code >= 200 && local_var_response$status_code <= 299) {
81-
local_var_response$content
81+
return(local_var_response$content)
8282
} else if (local_var_response$status_code >= 300 && local_var_response$status_code <= 399) {
83-
local_var_response
83+
return(local_var_response)
8484
} else if (local_var_response$status_code >= 400 && local_var_response$status_code <= 499) {
85-
local_var_response
85+
return(local_var_response)
8686
} else if (local_var_response$status_code >= 500 && local_var_response$status_code <= 599) {
87-
local_var_response
87+
return(local_var_response)
8888
}
8989
},
9090

@@ -133,26 +133,29 @@ AuthApi <- R6::R6Class(
133133
if (local_var_resp$status_code >= 200 && local_var_resp$status_code <= 299) {
134134
# save response in a file
135135
if (!is.null(data_file)) {
136-
write(local_var_resp$response, data_file)
136+
self$api_client$WriteFile(local_var_resp, data_file)
137137
}
138138

139139
deserialized_resp_obj <- tryCatch(
140-
self$api_client$deserialize(local_var_resp$response_as_text(), "character", loadNamespace("openapi")),
140+
self$api_client$DeserializeResponse(local_var_resp, "character"),
141141
error = function(e) {
142142
stop("Failed to deserialize response")
143143
}
144144
)
145145
local_var_resp$content <- deserialized_resp_obj
146-
local_var_resp
147-
} else if (local_var_resp$status_code >= 300 && local_var_resp$status_code <= 399) {
146+
return(local_var_resp)
147+
}
148+
149+
local_var_error_msg <- local_var_resp$response_as_text()
150+
if (local_var_resp$status_code >= 300 && local_var_resp$status_code <= 399) {
148151
ApiResponse$new(paste("Server returned ", local_var_resp$status_code, " response status code."), local_var_resp)
149152
} else if (local_var_resp$status_code >= 400 && local_var_resp$status_code <= 499) {
150153
ApiResponse$new("API client error", local_var_resp)
151154
} else if (local_var_resp$status_code >= 500 && local_var_resp$status_code <= 599) {
152155
if (is.null(local_var_resp$response) || local_var_resp$response == "") {
153156
local_var_resp$response <- "API server error"
154157
}
155-
local_var_resp
158+
return(local_var_resp)
156159
}
157160
},
158161

@@ -166,13 +169,13 @@ AuthApi <- R6::R6Class(
166169
TestAuthHttpBearer = function(data_file = NULL, ...) {
167170
local_var_response <- self$TestAuthHttpBearerWithHttpInfo(data_file = data_file, ...)
168171
if (local_var_response$status_code >= 200 && local_var_response$status_code <= 299) {
169-
local_var_response$content
172+
return(local_var_response$content)
170173
} else if (local_var_response$status_code >= 300 && local_var_response$status_code <= 399) {
171-
local_var_response
174+
return(local_var_response)
172175
} else if (local_var_response$status_code >= 400 && local_var_response$status_code <= 499) {
173-
local_var_response
176+
return(local_var_response)
174177
} else if (local_var_response$status_code >= 500 && local_var_response$status_code <= 599) {
175-
local_var_response
178+
return(local_var_response)
176179
}
177180
},
178181

@@ -221,26 +224,29 @@ AuthApi <- R6::R6Class(
221224
if (local_var_resp$status_code >= 200 && local_var_resp$status_code <= 299) {
222225
# save response in a file
223226
if (!is.null(data_file)) {
224-
write(local_var_resp$response, data_file)
227+
self$api_client$WriteFile(local_var_resp, data_file)
225228
}
226229

227230
deserialized_resp_obj <- tryCatch(
228-
self$api_client$deserialize(local_var_resp$response_as_text(), "character", loadNamespace("openapi")),
231+
self$api_client$DeserializeResponse(local_var_resp, "character"),
229232
error = function(e) {
230233
stop("Failed to deserialize response")
231234
}
232235
)
233236
local_var_resp$content <- deserialized_resp_obj
234-
local_var_resp
235-
} else if (local_var_resp$status_code >= 300 && local_var_resp$status_code <= 399) {
237+
return(local_var_resp)
238+
}
239+
240+
local_var_error_msg <- local_var_resp$response_as_text()
241+
if (local_var_resp$status_code >= 300 && local_var_resp$status_code <= 399) {
236242
ApiResponse$new(paste("Server returned ", local_var_resp$status_code, " response status code."), local_var_resp)
237243
} else if (local_var_resp$status_code >= 400 && local_var_resp$status_code <= 499) {
238244
ApiResponse$new("API client error", local_var_resp)
239245
} else if (local_var_resp$status_code >= 500 && local_var_resp$status_code <= 599) {
240246
if (is.null(local_var_resp$response) || local_var_resp$response == "") {
241247
local_var_resp$response <- "API server error"
242248
}
243-
local_var_resp
249+
return(local_var_resp)
244250
}
245251
}
246252
)

0 commit comments

Comments
 (0)