Skip to content

Commit

Permalink
Merge commit '1d07aeb4c40bcf9d0140113d49e1970cc7c0b2f2'
Browse files Browse the repository at this point in the history
  • Loading branch information
hadley committed Jan 6, 2025
2 parents ad9a999 + 1d07aeb commit 4316eed
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 7 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Depends:
R (>= 4.0)
Imports:
cli (>= 3.0.0),
curl (>= 6.0.1),
curl (>= 6.1.0),
glue,
lifecycle,
magrittr,
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# httr2 (development version)

* `curl_translate()` now translates cookie headers to `req_cookies_set()` (#431).
* `curl_transform()` will now use `req_body_json_modify()` for JSON data (#258).
* `resp_stream_is_complete()` tells you if there is still data remaining to be streamed (#559).
* New `url_modify()`, `url_modify_query()`, and `url_modify_relative()` make it easier to modify an existing url (#464).
* New `url_query_parse()` and `url_query_build()` allow you to parse and build a query string (#425).
Expand Down
26 changes: 21 additions & 5 deletions R/curl.R
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,14 @@ curl_translate <- function(cmd, simplify_headers = TRUE) {

if (!identical(data$data, "")) {
type <- type %||% "application/x-www-form-urlencoded"
body <- data$data
steps <- add_curl_step(steps, "req_body_raw", main_args = c(body, type))
if (type == "application/json" && idempotent_json(data$data)) {
json <- jsonlite::parse_json(data$data)
args <- list(data = I(deparse1(json)))
steps <- add_curl_step(steps, "req_body_json", dots = args)
} else {
body <- data$data
steps <- add_curl_step(steps, "req_body_raw", main_args = c(body, type))
}
}

steps <- add_curl_step(steps, "req_auth_basic", main_args = unname(data$auth))
Expand Down Expand Up @@ -155,6 +161,11 @@ curl_normalize <- function(cmd, error_call = caller_env()) {
method <- NULL
}

if (has_name(args, "--json")) {
args <- c(args, list(`--data-raw` = args[["--json"]]))
headers[["Content-Type"]] <- "application/json"
}

# https://curl.se/docs/manpage.html#-d
# --data-ascii, --data
# * if first element is @, treat as path to read from, stripping CRLF
Expand Down Expand Up @@ -215,6 +226,7 @@ curl_opts <- "Usage: curl [<url>] [-H <header> ...] [-d <data> ...] [options] [<
--data-ascii <data> HTTP POST ASCII data
--data-binary <data> HTTP POST binary data
--data-urlencode <data> HTTP POST data url encoded
--json <data> HTTP POST JSON
-G, --get Put the post data in the URL and use GET
-I, --head Show document info only
-H, --header <header> Pass custom header(s) to server
Expand Down Expand Up @@ -271,19 +283,17 @@ quote_name <- function(x) {

add_curl_step <- function(steps,
f,
...,
main_args = NULL,
dots = NULL,
keep_if_empty = FALSE) {
check_dots_empty0(...)
args <- c(main_args, dots)

if (is_empty(args) && !keep_if_empty) {
return(steps)
}

names <- quote_name(names2(args))
string <- vapply(args, is.character, logical(1L))
string <- map_lgl(args, function(x) is.character(x) && !inherits(x, "AsIs"))
values <- unlist(args)
values <- ifelse(string, encode_string2(values), values)

Expand Down Expand Up @@ -338,3 +348,9 @@ cookies_parse <- function(x) {
names(out) <- curl::curl_unescape(names(cookies))
out
}

idempotent_json <- function(old) {
args <- formals(req_body_json)[c("auto_unbox", "null", "digits")]
new <- exec(jsonlite::toJSON, jsonlite::parse_json(old), !!!args)
jsonlite::minify(old) == jsonlite::minify(new)
}
24 changes: 23 additions & 1 deletion tests/testthat/_snaps/curl.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,26 @@
) |>
req_perform()

# can translate json

Code
curl_translate(
"curl http://example.com --data-raw '{\"a\": 1, \"b\": \"text\"}' -H Content-Type:application/json")
Output
request("http://example.com/") |>
req_body_json(
data = list(a = 1L, b = "text"),
) |>
req_perform()
Code
curl_translate("curl http://example.com --json '{\"a\": 1, \"b\": \"text\"}'")
Output
request("http://example.com/") |>
req_body_json(
data = list(a = 1L, b = "text"),
) |>
req_perform()

# content type stays in header if no data

Code
Expand Down Expand Up @@ -158,6 +178,8 @@
Output
request("http://example.com/") |>
req_method("PATCH") |>
req_body_raw('{"data":{"x":1,"y":"a","nested":{"z":[1,2,3]}}}', "application/json") |>
req_body_json(
data = list(data = list(x = 1L, y = "a", nested = list(z = list(1L, 2L, 3L)))),
) |>
req_perform()

9 changes: 9 additions & 0 deletions tests/testthat/test-curl.R
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,15 @@ test_that("can translate ocokies", {
})
})

test_that("can translate json", {
skip_if(getRversion() < "4.1")

expect_snapshot({
curl_translate(r"--{curl http://example.com --data-raw '{"a": 1, "b": "text"}' -H Content-Type:application/json}--")
curl_translate(r"--{curl http://example.com --json '{"a": 1, "b": "text"}'}--")
})
})

test_that("content type stays in header if no data", {
skip_if(getRversion() < "4.1")

Expand Down

0 comments on commit 4316eed

Please sign in to comment.