Skip to content

Export new_response() #751

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export(local_mock)
export(local_mocked_responses)
export(local_verbosity)
export(multi_req_perform)
export(new_response)
export(oauth_cache_clear)
export(oauth_cache_path)
export(oauth_client)
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# httr2 (development version)

* `new_response()` is now exported (#751).
* URL construction is now powered by `curl::curl_modify_url()`, and hence now (correctly) escapes the `path` component (#732). This means that `req_url_path()` now can only affect the path component of the URL, not the query params or fragment.
* Redacted headers are no longer serialized to disk. This is important since it makes it harder to accidentally leak secrets to files on disk, but comes at a cost: you can longer perform such requests that have been saved and reloaded (#721).
* New `req_get_method()` and `req_get_body()` allow you to do some limited prediction of what a request _will_ do when it's performed (#718).
Expand Down
6 changes: 4 additions & 2 deletions R/req-mock.R
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#' Temporarily mock requests
#'
#' Mocking allows you to selectively and temporarily replace the response
#' you would typically receive from a request with your own code. It's
#' primarily used for testing.
#' you would typically receive from a request with your own code. These
#' functions are low-level and we don't recommend using them directly.
#' Instead use package that uses these functions under the hood, like
#' \pkg{httptest2} or \pkg{vcr}.
#'
#' @param mock A function, a list, or `NULL`.
#'
Expand Down
69 changes: 44 additions & 25 deletions R/resp.R
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
#' Create a new HTTP response
#' Create a HTTP response for testing
#'
#' @description
#' Generally, you should not need to call this function directly; you'll
#' get a real HTTP response by calling [req_perform()] and friends. This
#' function is provided primarily for testing, and a place to describe
#' the key components of a response.
#'
#' `response()` creates a generic response; `response_json()` creates a
#' response with a JSON body, automatically adding the correct Content-Type
#' response with a JSON body, automatically adding the correct `Content-Type`
#' header.
#'
#' @keywords internal
#' @param status_code HTTP status code. Must be a single integer.
#' @param url URL response came from; might not be the same as the URL in
#' the request if there were any redirects.
#' @param method HTTP method used to retrieve the response.
#' @param headers HTTP headers. Can be supplied as a raw or character vector
#' which will be parsed using the standard rules, or a named list.
#' @param body Response, if any, contained in the response body.
#' For `response_json()`, a R data structure to serialize to JSON.
#' @param timing A named numeric vector giving the time taken by various
#' components.
#' @returns An HTTP response: an S3 list with class `httr2_response`.
#' Generally, you should not need to call these function directly; you'll
#' get a real HTTP response by calling [req_perform()] and friends. These
#' function is provided primarily for use in tests; if you are creating
#' responses for mocked requests, use the lower-level [new_response()].
#'
#' @inherit new_response params return
#' @export
#' @examples
#' response()
Expand All @@ -40,6 +29,10 @@ response <- function(
check_string(method)

headers <- as_headers(headers)
# ensure we always have a date field
if (!"date" %in% tolower(names(headers))) {
headers$Date <- "Wed, 01 Jan 2020 00:00:00 UTC"
}

new_response(
method = method,
Expand Down Expand Up @@ -74,6 +67,25 @@ response_json <- function(
)
}

#' Create a HTTP response
#'
#' This is the constructor function for the `httr2_response` S3 class. It is
#' useful primarily for mocking.
#'
#' @param method HTTP method used to retrieve the response.
#' @param url URL response came from; might not be the same as the URL in
#' the request if there were any redirects.
#' @param status_code HTTP status code. Must be a single integer.
#' @param headers HTTP headers. Can be supplied as a raw or character vector
#' which will be parsed using the standard rules, or a named list.
#' @param body Response, if any, contained in the response body.
#' For `response_json()`, a R data structure to serialize to JSON.
#' @param timing A named numeric vector giving the time taken by various
#' components.
#' @param request The [request] used to generate this response.
#' @param error_call Environment (on call stack) used in error messages.
#' @returns An HTTP response: an S3 list with class `httr2_response`.
#' @export
new_response <- function(
method,
url,
Expand All @@ -87,15 +99,22 @@ new_response <- function(
check_string(method, call = error_call)
check_string(url, call = error_call)
check_number_whole(status_code, call = error_call)
check_request(request, allow_null = TRUE)
check_request(request, allow_null = TRUE, call = error_call)
if (!is.null(timing) && !is_bare_numeric(timing)) {
stop_input_type(timing, "a numeric vector", allow_null = TRUE)
stop_input_type(
timing,
"a numeric vector",
allow_null = TRUE,
call = error_call
)
}

headers <- as_headers(headers, error_call = error_call)
# ensure we always have a date field
if (!"date" %in% tolower(names(headers))) {
headers$Date <- "Wed, 01 Jan 2020 00:00:00 UTC"
if (!is.raw(body) && !is_path(body) && !inherits(body, "connection")) {
stop_input_type(
body,
"a raw vector, a path, or a connection",
call = error_call
)
}

structure(
Expand Down
13 changes: 10 additions & 3 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ reference:
- last_request
- req_dry_run
- req_verbose
- with_mock
- with_verbosity

- subtitle: Authentication
Expand Down Expand Up @@ -78,8 +77,6 @@ reference:
contents:
- curl_translate
- is_online
- secrets
- obfuscate

- title: OAuth
desc: >
Expand All @@ -88,6 +85,16 @@ reference:
- starts_with("oauth_")
- -starts_with("req_oauth")

- title: Package development
desc: >
These functions are useful when developing packges that use httr2.
contents:
- with_mock
- secrets
- obfuscate
- response
- new_response

articles:
- title: Using httr2
navbar: ~
Expand Down
45 changes: 45 additions & 0 deletions man/new_response.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 7 additions & 8 deletions man/response.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions man/with_mocked_responses.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions tests/testthat/_snaps/resp.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,41 @@
Error:
! `1` must be an HTTP response object, not the number 1.

# new_response() checks its inputs

Code
new_response(1)
Condition
Error:
! `method` must be a single string, not the number 1.
Code
new_response("GET", 1)
Condition
Error:
! `url` must be a single string, not the number 1.
Code
new_response("GET", "http://x.com", "x")
Condition
Error:
! `status_code` must be a whole number, not the string "x".
Code
new_response("GET", "http://x.com", 200, 1)
Condition
Error:
! `headers` must be a list, character vector, or raw.
Code
new_response("GET", "http://x.com", 200, list(), 1)
Condition
Error:
! `body` must be a raw vector, a path, or a connection, not the number 1.
Code
new_response("GET", "http://x.com", 200, list(), raw(), "x")
Condition
Error:
! `timing` must be a numeric vector or `NULL`, not the string "x".
Code
new_response("GET", "http://x.com", 200, list(), raw(), c(x = 1), 1)
Condition
Error:
! `request` must be an HTTP request object or `NULL`, not the number 1.

12 changes: 12 additions & 0 deletions tests/testthat/test-resp.R
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,15 @@ test_that("response adds date if not provided by server", {
test_that("check_response produces helpful error", {
expect_snapshot(check_response(1), error = TRUE)
})

test_that("new_response() checks its inputs", {
expect_snapshot(error = TRUE, {
new_response(1)
new_response("GET", 1)
new_response("GET", "http://x.com", "x")
new_response("GET", "http://x.com", 200, 1)
new_response("GET", "http://x.com", 200, list(), 1)
new_response("GET", "http://x.com", 200, list(), raw(), "x")
new_response("GET", "http://x.com", 200, list(), raw(), c(x = 1), 1)
})
})