/
build-mock-url.R
116 lines (108 loc) 路 4.07 KB
/
build-mock-url.R
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
#' Convert a request to a mock file path
#'
#' Requests are translated to mock file paths according to several rules that
#' incorporate the request method, URL, query parameters, and body.
#'
#' First, the request protocol, such as "https://", is removed from the URL.
#' Second, if the request URL contains a query string, it will be popped off,
#' hashed by [digest::digest()], and the first six characters appended to the
#' file being read. Third, request bodies are similarly hashed and
#' appended. Finally, if a request method other than GET is used it will be
#' appended to the end of the end of the file name.
#'
#' Mock file paths also have a file extension appended, based on the
#' `Content-Type` of the response, though this function, which is only concerned
#' with the request, does not add the extension. In an
#' HTTP API, a "directory" itself is a resource,
#' so the extension allows distinguishing directories and files in the file
#' system. That is, a mocked `GET http://example.com/api/` may read a
#' "example.com/api.json" file, while
#' `GET http://example.com/api/object1/` reads "example.com/api/object1.json".
#'
#' Other examples:
#' * `GET http://example.com/api/object1/?a=1` may read
#' "example.com/api/object1-b64371.xml".
#' * `POST http://example.com/api/object1/?a=1` may read
#' "example.com/api/object1-b64371-POST.json".
#'
#' Note that if you are trying to guess the mock file paths corresponding to a
#' test for which you intend to create a mock file manually,
#' instead of trying to build the URL, you should run the test
#' with `with_mock_api()` as the error message will contain the mock file path.
#'
#' @param req A `httr2_request` object
#' @return A file path and name, without an extension. The file, or a file with
#' some extension appended, may or may not
#' exist: existence is not a concern of this function.
#' @importFrom digest digest
#' @seealso [with_mock_api()] [capture_requests()]
#' @export
#' @keywords internal
build_mock_url <- function(req) {
method <- get_request_method(req)
body <- get_string_request_body(req)
# Remove protocol
url <- sub("^.*?://", "", req$url)
# Handle query params
parts <- unlist(strsplit(url, "?", fixed = TRUE))
# Remove trailing slash
f <- sub("\\/$", "", parts[1])
# Sanitize the path to be portable for all R platforms
f <- gsub(":", "-", f)
if (length(parts) > 1) {
# There's a query string. Append the digest as a suffix.
f <- paste0(f, "-", hash(parts[2]))
}
# Handle body and append its hash if present
if (!is.null(body)) {
f <- paste0(f, "-", hash(body))
}
if (method != "GET") {
# Append method to the file name for non GET requests
f <- paste0(f, "-", method)
}
return(f)
}
get_string_request_body <- function(req) {
# Returns a string if the request has a body, NULL otherwise
body_apply <- utils::getFromNamespace("req_body_apply", "httr2")
req <- body_apply(req)
b <- request_postfields(req)
if (is.null(b)) {
if (length(req$fields)) {
b <- lapply(req$fields, function(x) {
if (inherits(x, "form_file")) {
# hash the file contents
paste("File:", digest(x$path, serialize = FALSE, file = TRUE))
} else {
# assume form_data
rawToChar(x$value)
}
})
b <- paste(c(
"Multipart form:",
paste(names(b), b, sep = " = ")
), collapse = "\n ")
# add a newline at the end too
b <- paste0(b, "\n")
} else if (inherits(req$body$data, "httr2_path")) {
b <- paste("File:", digest(req$body$data, serialize = FALSE, file = TRUE))
}
}
b
}
request_postfields <- function(req) {
b <- req[["options"]][["postfields"]]
if (length(b) > 0) {
# Check length this way because b may be NULL or length 0 raw vector
return(rawToChar(b))
} else {
return(NULL)
}
}
get_request_method <- function(req) {
# At the time that we process the request, some defaults may not have been
# applied, and the request method may be NULL
utils::getFromNamespace("req_method_get", "httr2")(req)
}
hash <- function(string, n = 6) substr(digest(string), 1, n)