diff --git a/NAMESPACE b/NAMESPACE index fe13d26a..8419ec7d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -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) diff --git a/NEWS.md b/NEWS.md index 2d86514a..a2c6dc5a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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). diff --git a/R/req-mock.R b/R/req-mock.R index b05c4912..0ab73f85 100644 --- a/R/req-mock.R +++ b/R/req-mock.R @@ -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`. #' diff --git a/R/resp.R b/R/resp.R index 1c998103..94188028 100644 --- a/R/resp.R +++ b/R/resp.R @@ -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() @@ -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, @@ -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, @@ -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( diff --git a/_pkgdown.yml b/_pkgdown.yml index dacbe7d9..be0c3bc7 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -33,7 +33,6 @@ reference: - last_request - req_dry_run - req_verbose - - with_mock - with_verbosity - subtitle: Authentication @@ -78,8 +77,6 @@ reference: contents: - curl_translate - is_online - - secrets - - obfuscate - title: OAuth desc: > @@ -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: ~ diff --git a/man/new_response.Rd b/man/new_response.Rd new file mode 100644 index 00000000..6d416b4c --- /dev/null +++ b/man/new_response.Rd @@ -0,0 +1,45 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/resp.R +\name{new_response} +\alias{new_response} +\title{Create a HTTP response} +\usage{ +new_response( + method, + url, + status_code, + headers, + body, + timing = NULL, + request = NULL, + error_call = caller_env() +) +} +\arguments{ +\item{method}{HTTP method used to retrieve the response.} + +\item{url}{URL response came from; might not be the same as the URL in +the request if there were any redirects.} + +\item{status_code}{HTTP status code. Must be a single integer.} + +\item{headers}{HTTP headers. Can be supplied as a raw or character vector +which will be parsed using the standard rules, or a named list.} + +\item{body}{Response, if any, contained in the response body. +For \code{response_json()}, a R data structure to serialize to JSON.} + +\item{timing}{A named numeric vector giving the time taken by various +components.} + +\item{request}{The \link{request} used to generate this response.} + +\item{error_call}{Environment (on call stack) used in error messages.} +} +\value{ +An HTTP response: an S3 list with class \code{httr2_response}. +} +\description{ +This is the constructor function for the \code{httr2_response} S3 class. It is +useful primarily for mocking. +} diff --git a/man/response.Rd b/man/response.Rd index f0eb95a0..7dc2874b 100644 --- a/man/response.Rd +++ b/man/response.Rd @@ -3,7 +3,7 @@ \name{response} \alias{response} \alias{response_json} -\title{Create a new HTTP response} +\title{Create a HTTP response for testing} \usage{ response( status_code = 200, @@ -43,18 +43,17 @@ components.} An HTTP response: an S3 list with class \code{httr2_response}. } \description{ -Generally, you should not need to call this function directly; you'll -get a real HTTP response by calling \code{\link[=req_perform]{req_perform()}} and friends. This -function is provided primarily for testing, and a place to describe -the key components of a response. - \code{response()} creates a generic response; \code{response_json()} creates a -response with a JSON body, automatically adding the correct Content-Type +response with a JSON body, automatically adding the correct \code{Content-Type} header. + +Generally, you should not need to call these function directly; you'll +get a real HTTP response by calling \code{\link[=req_perform]{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 \code{\link[=new_response]{new_response()}}. } \examples{ response() response(404, method = "POST") response(headers = c("Content-Type: text/html", "Content-Length: 300")) } -\keyword{internal} diff --git a/man/with_mocked_responses.Rd b/man/with_mocked_responses.Rd index f0190605..7cdbb12d 100644 --- a/man/with_mocked_responses.Rd +++ b/man/with_mocked_responses.Rd @@ -31,8 +31,10 @@ handle the request) or a \link{response} (if it does). } \description{ 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}. } \examples{ # This function should perform a response against google.com: diff --git a/tests/testthat/_snaps/resp.md b/tests/testthat/_snaps/resp.md index a8e851fe..05cf1fa1 100644 --- a/tests/testthat/_snaps/resp.md +++ b/tests/testthat/_snaps/resp.md @@ -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. + diff --git a/tests/testthat/test-resp.R b/tests/testthat/test-resp.R index 2c2030b5..934bb090 100644 --- a/tests/testthat/test-resp.R +++ b/tests/testthat/test-resp.R @@ -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) + }) +})