Skip to content

feat: add httr2_translate#797

Merged
hadley merged 32 commits into
r-lib:mainfrom
JosiahParry:httr2-translate
Jun 22, 2026
Merged

feat: add httr2_translate#797
hadley merged 32 commits into
r-lib:mainfrom
JosiahParry:httr2-translate

Conversation

@JosiahParry

Copy link
Copy Markdown
Contributor

This PR closes #795 by adding an httr2_translate() function.

This function takes an httr2_request object and does its best to faithfully create the equivalent curl request while respecting headers, cookies, options, request type etc.

library(httr2)

request("https://httpbin.org/post") |>
  req_body_form(name = "value") |>
  httr2_translate() |>
  cat()
#> curl -X POST \
#>   -H "Content-Type: application/x-www-form-urlencoded" \
#>   -d "name=value" \
#>   "https://httpbin.org/post"

path <- tempfile()
writeLines("test content", path)
request("https://httpbin.org/post") |>
  req_body_file(path, type = "text/plain") |>
  httr2_translate() |>
  cat()
#> curl -X POST \
#>   -H "Content-Type: text/plain" \
#>   --data-binary "@/var/folders/wd/xq999jjj3bx2w8cpg7lkfxlm0000gn/T//Rtmp6Xs5Mq/file13ec039518760" \
#>   "https://httpbin.org/post"

request("https://httpbin.org/get") |>
  req_options(timeout = 30, verbose = TRUE, ssl_verifypeer = FALSE) |>
  httr2_translate() |>
  cat()
#> curl --max-time 30 \
#>   --verbose \
#>   --insecure \
#>   "https://httpbin.org/get"

request("https://httpbin.org/get") |>
  req_headers("Authorization" = obfuscated("ZdYJeG8zwISodg0nu4UxBhs")) |>
  httr2_translate() |>
  cat()
#> curl -H "Authorization: y" \
#>   "https://httpbin.org/get"

Created on 2025-08-20 with reprex v2.1.1

@JosiahParry

Copy link
Copy Markdown
Contributor Author

CI has been tamed. Please let me know if there is anything I can do to move this along! I'd quite like to be able to use it as a debug option in {arcgislayers} with something like:

if (isTRUE(getOption("arcgis.debug_curl")) {
    cat(httr2::httr2_translate(req))
}

This will help tremendously in getting reproducible examples from users as well as providing them to our distributed team of software engineers.

Thanks!

Comment thread R/httr2-translate.R Outdated
Comment thread R/httr2-translate.R Outdated
Comment thread R/httr2-translate.R Outdated
Comment thread R/httr2-translate.R Outdated
Comment thread R/httr2-translate.R Outdated
Comment thread R/httr2-translate.R Outdated
Comment thread R/httr2-translate.R Outdated
Comment thread R/httr2-translate.R Outdated
Comment thread R/httr2-translate.R Outdated
Comment thread R/httr2-translate.R Outdated
Comment on lines +167 to +169
if (length(cmd_parts) <= 2) {
paste(cmd_parts, collapse = " ")
} else {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this branch actually necessary?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch is used so that the output is compatible with curl_translate() if curl is on its own line curl_translate() fails to read the result.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that but I think if remove the first branch then the results will still be the same.

I'd suggest renaming and refocussing cmd_parts to curl_args (i.e. adding curl and the url at the very end) to make this more clear.

@JosiahParry

Copy link
Copy Markdown
Contributor Author

Thank you so much for the feedback! I'll review this and make the requested changes.

Comment thread R/req-as-curl.R Outdated
headers <- req_get_headers(req, redacted = "reveal")

# if headers are present, add them using -H flag
if (!rlang::is_empty(headers)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, you don't need to first check if the vector is empty, if it is empty the for loop already doesn't do anything.

Comment thread R/req-as-curl.R Outdated
}
}

known_curl_opts <- c(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest making this a separate function

Comment thread R/req-as-curl.R Outdated
# if the headers aren't empty AND the content-type header is set
# we use that instead of what is inferred from the request object
if (
!rlang::is_empty(headers) && ("content-type" %in% tolower(names(headers)))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If x is empty, then "foo" %in% x will always return FALSE.

hadley and others added 20 commits June 21, 2026 17:38
- Extract option translation into req_options_as_curl() so it can be
  tested in isolation
- Drop redundant is_empty() guards before the headers loop and the
  content-type %in% check
- Fix misplaced @Seealso so it renders as its own section

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Restructure into small parallel helpers (req_method_as_curl(),
req_headers_as_curl(), req_options_as_curl(), req_body_as_curl()) that
each return a character vector of curl arguments, assembled by a single
curl_command() formatter. This removes the cmd_args accumulation, the
manual quote-building (now dquote()), and the multi-line special-casing.

- Add `obfuscated` argument (matching req_get_body()), passed through to
  both header and body extraction; secrets are redacted by default.
- Upgrade the untranslatable-option alert to a real cli_warn().
- Avoid emitting a duplicate Content-Type when set as a request header.
- Reach 100% coverage with unit tests for each helper plus the raw-body
  and Content-Type cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@JosiahParry

Copy link
Copy Markdown
Contributor Author

Sorry for letting this die, and thank you so much for bringing it back <3

@hadley

hadley commented Jun 22, 2026

Copy link
Copy Markdown
Member

No problems. FWIW I switched back to httr2_translate() so it better parallels curl_translate()

@hadley hadley merged commit 7d1fbcb into r-lib:main Jun 22, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: httr2_translate()

2 participants