File: req-error.R

package info (click to toggle)
r-cran-httr2 1.2.2-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 1,684 kB
  • sloc: sh: 13; makefile: 2
file content (131 lines) | stat: -rw-r--r-- 4,363 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#' Control handling of HTTP errors
#'
#' `req_perform()` will automatically convert HTTP errors (i.e. any 4xx or 5xx
#' status code) into R errors. Use `req_error()` to either override the
#' defaults, or extract additional information from the response that would
#' be useful to expose to the user.
#'
#' # Error handling
#'
#' `req_perform()` is designed to succeed if and only if you get a valid HTTP
#' response. There are two ways a request can fail:
#'
#' * The HTTP request might fail, for example if the connection is dropped
#'   or the server doesn't exist. This type of error will have class
#'   `c("httr2_failure", "httr2_error")`.
#'
#' * The HTTP request might succeed, but return an HTTP status code that
#'   represents an error, e.g. a `404 Not Found` if the specified resource is
#'   not found. This type of error will have (e.g.) class
#'   `c("httr2_http_404", "httr2_http", "httr2_error")`.
#'
#' These error classes are designed to be used in conjunction with R's
#' condition handling tools (<https://adv-r.hadley.nz/conditions.html>).
#' For example, if you want to return a default value when the server returns
#' a 404, use `tryCatch()`:
#'
#' ```
#' tryCatch(
#'   req |> req_perform() |> resp_body_json(),
#'   httr2_http_404 = function(cnd) NULL
#' )
#' ```
#'
#' Or if you want to re-throw the error with some additional context, use
#' `withCallingHandlers()`, e.g.:
#'
#' ```R
#' withCallingHandlers(
#'   req |> req_perform() |> resp_body_json(),
#'   httr2_http_404 = function(cnd) {
#'     rlang::abort("Couldn't find user", parent = cnd)
#'   }
#' )
#' ```
#'
#' Learn more about error chaining at [rlang::topic-error-chaining].
#'
#' @seealso [req_retry()] to control when errors are automatically retried.
#' @inheritParams req_perform
#' @param is_error A predicate function that takes a single argument (the
#'   response) and returns `TRUE` or `FALSE` indicating whether or not an
#'   R error should be signalled.
#' @param body A callback function that takes a single argument (the response)
#'   and returns a character vector of additional information to include in the
#'   body of the error. This vector is passed along to the `message` argument
#'   of [rlang::abort()] so you can use any formatting that it supports.
#' @returns A modified HTTP [request].
#' @export
#' @examples
#' # Performing this request usually generates an error because httr2
#' # converts HTTP errors into R errors:
#' req <- request(example_url()) |>
#'   req_url_path("/status/404")
#' try(req |> req_perform())
#' # You can still retrieve it with last_response()
#' last_response()
#'
#' # But you might want to suppress this behaviour:
#' resp <- req |>
#'   req_error(is_error = \(resp) FALSE) |>
#'   req_perform()
#' resp
#'
#' # Or perhaps you're working with a server that routinely uses the
#' # wrong HTTP error codes only 500s are really errors
#' request("http://example.com") |>
#'   req_error(is_error = \(resp) resp_status(resp) == 500)
#'
#' # Most typically you'll use req_error() to add additional information
#' # extracted from the response body (or sometimes header):
#' error_body <- function(resp) {
#'   resp_body_json(resp)$error
#' }
#' request("http://example.com") |>
#'   req_error(body = error_body)
#' # Learn more in https://httr2.r-lib.org/articles/wrapping-apis.html
req_error <- function(req, is_error = NULL, body = NULL) {
  check_request(req)

  req_policies(
    req,
    error_is_error = as_callback(is_error, 1, "is_error"),
    error_body = as_callback(body, 1, "body")
  )
}

error_is_error <- function(req, resp) {
  req_policy_call(req, "error_is_error", list(resp), default = resp_is_error)
}

error_body <- function(req, resp, call = caller_env()) {
  try_fetch(
    req_policy_call(req, "error_body", list(resp), default = NULL),
    error = function(cnd) {
      cli::cli_abort(
        "Failed to parse error body with method defined in {.fn req_error}.",
        parent = cnd,
        call = call
      )
    }
  )
}

capture_curl_error <- function(code, call = caller_env()) {
  tryCatch(
    {
      code
      NULL
    },
    error = function(err) curl_cnd(err, call = call)
  )
}

curl_cnd <- function(err, call = caller_env()) {
  error_cnd(
    message = "Failed to perform HTTP request.",
    class = c("httr2_failure", "httr2_error"),
    parent = err,
    call = call
  )
}