Skip to content

Commit d888cd2

Browse files
authored
Provide helpers to get last JSON body from request/response (#743)
Fixes #734
1 parent 32451b3 commit d888cd2

File tree

8 files changed

+241
-46
lines changed

8 files changed

+241
-46
lines changed

NAMESPACE

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ S3method(close,httr2_response)
77
S3method(format,httr2_redacted)
88
S3method(print,httr2_cmd)
99
S3method(print,httr2_headers)
10+
S3method(print,httr2_json)
1011
S3method(print,httr2_oauth_client)
1112
S3method(print,httr2_obfuscated)
1213
S3method(print,httr2_request)
@@ -29,7 +30,9 @@ export(jwt_claim)
2930
export(jwt_encode_hmac)
3031
export(jwt_encode_sig)
3132
export(last_request)
33+
export(last_request_json)
3234
export(last_response)
35+
export(last_response_json)
3336
export(local_mock)
3437
export(local_mocked_responses)
3538
export(local_verbosity)

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# httr2 (development version)
22

3+
* New `last_request_json()` and `last_response_json()` to conveniently see JSON bodies (#734).
34
* `req_body_json_modify()` can now be used on a request with an empty body.
45
* `resp_timing()` exposes timing information about the request measured by libcurl (@arcresu, #725).
56
* `req_url_query()` now re-calculates n lengths when using `.multi = "explode"` to avoid select/recycling issues (@Kevanness, #719).

R/last.R

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#' Retrieve most recent request/response
2+
#'
3+
#' @description
4+
#' `last_request()` and `last_response()` retrieve the most recent request
5+
#' made by httr2 and the response it received, to facilitate debugging problems
6+
#' _after_ they occur.
7+
#'
8+
#' `last_request_json()` and `last_response_json()` return the JSON bodies of
9+
#' the most recent request and response. They will error if not JSON.
10+
#'
11+
#' @returns `last_request()` and `last_response()` return an HTTP
12+
#' [request] or [response] respectively. If no request has been made,
13+
#' `last_request()` will return `NULL`; if no request has been made
14+
#' or the last request was unsuccessful, `last_response()` will return
15+
#' `NULL`.
16+
#'
17+
#' `last_request_json()` and `last_response_json()` always return a string.
18+
#' They will error if `last_request()` or `last_response()` are `NULL` or
19+
#' don't have JSON bodies.
20+
#' @export
21+
#' @examples
22+
#' . <- request("http://httr2.r-lib.org") |> req_perform()
23+
#' last_request()
24+
#' last_response()
25+
#'
26+
#' . <- request(example_url("/post")) |>
27+
#' req_body_json(list(a = 1, b = 2)) |>
28+
#' req_perform()
29+
#' last_request_json()
30+
#' last_request_json(pretty = FALSE)
31+
#' last_response_json()
32+
#' last_response_json(pretty = FALSE)
33+
last_response <- function() {
34+
the$last_response
35+
}
36+
37+
#' @export
38+
#' @rdname last_response
39+
last_request <- function() {
40+
the$last_request
41+
}
42+
43+
#' @export
44+
#' @rdname last_response
45+
last_request_json <- function(pretty = TRUE) {
46+
req <- last_request()
47+
if (is.null(req)) {
48+
cli::cli_abort("No request has been made yet.")
49+
}
50+
if (req_body_type(req) != "json") {
51+
cli::cli_abort("Last request doesn't have a JSON body.")
52+
}
53+
httr2_json(req_body_get(req), pretty = pretty)
54+
}
55+
56+
#' @param pretty Should the JSON be pretty-printed?
57+
#' @export
58+
#' @rdname last_response
59+
last_response_json <- function(pretty = TRUE) {
60+
resp <- last_response()
61+
if (is.null(resp)) {
62+
cli::cli_abort("No request has been made successfully yet.")
63+
}
64+
if (!identical(resp_content_type(resp), "application/json")) {
65+
cli::cli_abort("Last response doesn't have a JSON body.")
66+
}
67+
httr2_json(resp_body_string(resp), pretty = pretty)
68+
}
69+
70+
httr2_json <- function(x, pretty = TRUE) {
71+
check_string(x)
72+
structure(x, pretty = pretty, class = "httr2_json")
73+
}
74+
#' @export
75+
print.httr2_json <- function(x, ...) {
76+
if (attr(x, "pretty")) {
77+
cat(pretty_json(x))
78+
} else {
79+
cat(x)
80+
}
81+
82+
cat("\n")
83+
invisible(x)
84+
}

R/req-perform.R

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -219,29 +219,6 @@ req_verbosity <- function(req, verbosity, error_call = caller_env()) {
219219
)
220220
}
221221

222-
#' Retrieve most recent request/response
223-
#'
224-
#' These functions retrieve the most recent request made by httr2 and
225-
#' the response it received, to facilitate debugging problems _after_ they
226-
#' occur. If the request did not succeed (or no requests have been made)
227-
#' `last_response()` will be `NULL`.
228-
#'
229-
#' @returns An HTTP [response]/[request].
230-
#' @export
231-
#' @examples
232-
#' invisible(request("http://httr2.r-lib.org") |> req_perform())
233-
#' last_request()
234-
#' last_response()
235-
last_response <- function() {
236-
the$last_response
237-
}
238-
239-
#' @export
240-
#' @rdname last_response
241-
last_request <- function() {
242-
the$last_request
243-
}
244-
245222
# Must call req_prepare(), then req_handle(), then after the request has been
246223
# performed, req_completed() (on the prepared requests)
247224
req_prepare <- function(req) {

man/last_response.Rd

Lines changed: 34 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/_snaps/last.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# can get json response
2+
3+
Code
4+
last_response_json()
5+
Output
6+
{
7+
"x": 1
8+
}
9+
Code
10+
last_response_json(pretty = FALSE)
11+
Output
12+
{"x":1}
13+
14+
# can get json request
15+
16+
Code
17+
last_request_json()
18+
Output
19+
{
20+
"x": 1
21+
}
22+
Code
23+
last_request_json(pretty = FALSE)
24+
Output
25+
{"x":1}
26+
27+
# useful errors if not json request/response
28+
29+
Code
30+
last_request_json()
31+
Condition
32+
Error in `last_request_json()`:
33+
! Last request doesn't have a JSON body.
34+
Code
35+
last_response_json()
36+
Condition
37+
Error in `last_response_json()`:
38+
! Last response doesn't have a JSON body.
39+
40+
# useful errors if no last request/response
41+
42+
Code
43+
last_request_json()
44+
Condition
45+
Error in `last_request_json()`:
46+
! No request has been made yet.
47+
Code
48+
last_response_json()
49+
Condition
50+
Error in `last_response_json()`:
51+
! No request has been made successfully yet.
52+

tests/testthat/test-last.R

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
test_that("can retrieve last request and response", {
2+
req <- request_test()
3+
resp <- req_perform(req)
4+
5+
expect_equal(last_request(), req)
6+
expect_equal(last_response(), resp)
7+
})
8+
9+
test_that("last response is NULL if it fails", {
10+
req <- request("")
11+
try(req_perform(req), silent = TRUE)
12+
13+
expect_equal(last_request(), req)
14+
expect_equal(last_response(), NULL)
15+
})
16+
17+
test_that("returns NULL if no last request/response", {
18+
the$last_request <- NULL
19+
the$last_response <- NULL
20+
21+
expect_equal(last_request(), NULL)
22+
expect_equal(last_response(), NULL)
23+
})
24+
25+
# JSON -----------------------------------------------------------------------
26+
27+
test_that("can get json response", {
28+
req <- local_app_request(function(req, res) {
29+
res$set_status(200L)$send_json(text = '{"x":1}')
30+
})
31+
req_perform(req)
32+
33+
expect_snapshot({
34+
last_response_json()
35+
last_response_json(pretty = FALSE)
36+
})
37+
})
38+
39+
test_that("can get json request", {
40+
request(example_url("/post")) |>
41+
req_body_json(list(x = 1)) |>
42+
req_perform()
43+
44+
expect_snapshot({
45+
last_request_json()
46+
last_request_json(pretty = FALSE)
47+
})
48+
})
49+
50+
test_that("useful errors if not json request/response", {
51+
req_perform(request(example_url("/xml")))
52+
53+
expect_snapshot(error = TRUE, {
54+
last_request_json()
55+
last_response_json()
56+
})
57+
})
58+
59+
test_that("useful errors if no last request/response", {
60+
the$last_request <- NULL
61+
the$last_response <- NULL
62+
63+
expect_snapshot(error = TRUE, {
64+
last_request_json()
65+
last_response_json()
66+
})
67+
})

tests/testthat/test-req-perform.R

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -195,22 +195,6 @@ test_that("can cache requests with paths (if-modified-since)", {
195195
expect_equal(resp2$body[[1]], path2)
196196
})
197197

198-
test_that("can retrieve last request and response", {
199-
req <- request_test()
200-
resp <- req_perform(req)
201-
202-
expect_equal(last_request(), req)
203-
expect_equal(last_response(), resp)
204-
})
205-
206-
test_that("last response is NULL if it fails", {
207-
req <- request("")
208-
try(req_perform(req), silent = TRUE)
209-
210-
expect_equal(last_request(), req)
211-
expect_equal(last_response(), NULL)
212-
})
213-
214198
test_that("checks input types", {
215199
req <- request_test()
216200
expect_snapshot(error = TRUE, {

0 commit comments

Comments
 (0)