Skip to content
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
9 changes: 9 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ S3method(.as_nectar_auth,nectar_auth)
S3method(.as_nectar_request,default)
S3method(.as_nectar_request,httr2_request)
S3method(.as_nectar_request,nectar_request)
S3method(.as_nectar_tidy_policy,"NULL")
S3method(.as_nectar_tidy_policy,"function")
S3method(.as_nectar_tidy_policy,default)
S3method(.as_nectar_tidy_policy,list)
S3method(.as_nectar_tidy_policy,nectar_tidy_policy)
S3method(resp_parse,default)
S3method(resp_parse,httr2_response)
S3method(resp_parse,list)
Expand Down Expand Up @@ -39,6 +44,10 @@ export(resp_parse)
export(resp_tidy)
export(resp_tidy_json)
export(resp_tidy_unknown)
export(tidy_policy_body_auto)
export(tidy_policy_json)
export(tidy_policy_prepare)
export(tidy_policy_unknown)
export(url_normalize)
export(url_path_append)
importFrom(fs,path)
Expand Down
22 changes: 18 additions & 4 deletions R/aaa-shared_params.R
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,24 @@
#' [httr2::req_perform()].
#' @param response_parser_args (`list`) An optional list of arguments to pass to
#' the `response_parser` function (in addition to `resp`).
#' @param tidy_fn (`function`) A function that will be invoked by [resp_tidy()]
#' to tidy the response.
#' @param tidy_args (`list`) A list of additional arguments to pass to
#' `tidy_fn`.
#' @param spec (`tspec` or `NULL`) A specification used by
#' [tibblify::tibblify()] to parse the extracted body of `resp`. When `spec`
#' is `NULL` (the default), [tibblify::tibblify()] will attempt to guess a
#' spec.
#' @param tidy_policy (`nectar_tidy_policy` or `NULL`) A tidying policy prepared
#' with [tidy_policy_prepare()]. By default, [tidy_policy_body_auto()] is used
#' to automatically apply [resp_body_auto()] to responses.
#' @param unspecified (`length-1 character`) A string that describes what
#' happens if the extracted body of `resp` contains fields that are not
#' specified in `spec`. While [tibblify::tibblify()] defaults to `NULL` for
#' this value, we set it to `list` so that the body will still parse when
#' `resp` contains extra data without throwing errors.
#' @param subset_path (`character`) An optional vector indicating the path to
#' the "real" object within the body of `resp`. For example, many APIs return
#' a body with information about the status of the response, cache
#' information, perhaps pagination information, and then the actual data in a
#' field such as `data`. If the desired part of the response body is in
#' `data$objects`, the value of this argument should be `c("data", "object")`.
#' @param url (`length-1 character`) An optional url associated with `name`.
#' @param version (`length-1 character`) The version of `name`.
#' @param x (multiple types) The object to update.
Expand Down
5 changes: 0 additions & 5 deletions R/auth_prepare.R
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,6 @@ auth_prepare <- function(auth_fn, ..., call = rlang::caller_env()) {
return(NextMethod())
}
auth_args <- stbl::to_lst(auth$auth_args) %||% list()
if (setequal(names(auth), c("auth_fn", "auth_args"))) {
auth$auth_args <- auth_args
class(auth) <- "nectar_auth"
return(auth)
}
structure(
list(
auth_fn = auth$auth_fn,
Expand Down
7 changes: 3 additions & 4 deletions R/req_prepare.R
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ req_prepare <- function(
method = NULL,
additional_user_agent = NULL,
auth = NULL,
tidy_fn = NULL,
tidy_args = list(),
tidy_policy = tidy_policy_body_auto(),
pagination_fn = NULL,
call = rlang::caller_env()
) {
Expand All @@ -52,8 +51,8 @@ req_prepare <- function(
if (length(pagination_fn)) {
req <- req_pagination_policy(req, pagination_fn, call = call)
}
if (length(tidy_fn)) {
req <- req_tidy_policy(req, tidy_fn, tidy_args = tidy_args, call = call)
if (length(tidy_policy)) {
Comment thread
jonthegeek marked this conversation as resolved.
req <- req_tidy_policy(req, tidy_policy = tidy_policy, call = call)
}
return(.as_nectar_request(req))
}
Expand Down
13 changes: 8 additions & 5 deletions R/req_tidy_policy.R
Comment thread
jonthegeek marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,24 @@
#' @inheritParams .shared-params
#' @inherit .shared-request return
#' @family opinionated request functions
#' @family opinionated response parsers
#' @export
#'
#' @examples
#' req <- httr2::request("https://example.com")
#' req_tidy_policy(req, httr2::resp_body_json, list(simplifyVector = TRUE))
#' req_tidy_policy(
#' req,
#' tidy_policy_json()
#' )
req_tidy_policy <- function(
req,
tidy_fn = resp_body_auto,
tidy_args = list(),
tidy_policy = tidy_policy_body_auto(),
call = rlang::caller_env()
) {
Comment thread
jonthegeek marked this conversation as resolved.
tidy_fn <- rlang::as_function(tidy_fn, call = call)
tidy_policy <- .as_nectar_tidy_policy(tidy_policy, call = call)
.req_policy(
req,
resp_tidy = list(tidy_fn = tidy_fn, tidy_args = tidy_args),
resp_tidy = tidy_policy,
call = call
)
}
15 changes: 15 additions & 0 deletions R/resp_body_auto.R
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,21 @@ resp_body_auto <- function(resp) {
)
}

#' A policy to automatically parse a response body
#'
#' Create a reusable tidy policy that applies [resp_body_auto()].
#'
#' @returns A list with class `"nectar_tidy_policy"` and elements `tidy_fn` and
#' `tidy_args`.
#' @family opinionated response parsers
#' @export
#'
#' @examples
#' tidy_policy_body_auto()
tidy_policy_body_auto <- function() {
tidy_policy_prepare(resp_body_auto)
}

#' Automatically choose more body parsers
#'
#' This helper function exists to find somewhat variable content types and
Expand Down
3 changes: 2 additions & 1 deletion R/resp_tidy.R
Comment thread
jonthegeek marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#' httr2 response parsers, and [resp_parse()] for an alternative approach to
#' dealing with responses (particularly useful if the request does not include
#' a `resp_tidy` policy).
#' @family opinionated response parsers
#' @export
#'
#' @examples
Expand All @@ -28,7 +29,7 @@
#' # With a tidy policy, resp_tidy() uses the policy's tidy function.
#' req <- req_tidy_policy(
#' httr2::request("https://example.com"),
#' httr2::resp_body_json
#' tidy_policy_prepare(httr2::resp_body_json)
#' )
#' # In practice, the request is attached automatically when the response is
#' # fetched with httr2::req_perform() or req_perform_opinionated().
Expand Down
41 changes: 26 additions & 15 deletions R/resp_tidy_json.R
Comment thread
jonthegeek marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,9 @@
#' subset of that body, and tidy the result with [tibblify::tibblify()].
#'
#' @inheritParams .shared-params
#' @param spec (`tspec` or `NULL`) A specification used by
#' [tibblify::tibblify()] to parse the extracted body of `resp`. When `spec`
#' is `NULL` (the default), [tibblify::tibblify()] will attempt to guess a
#' spec.
#' @param unspecified (`length-1 character`) A string that describes what
#' happens if the extracted body of `resp` contains fields that are not
#' specified in `spec`. While [tibblify::tibblify()] defaults to `NULL` for
#' this value, we set it to `list` so that the body will still parse when
#' `resp` contains extra data without throwing errors.
#' @param subset_path (`character`) An optional vector indicating the path to
#' the "real" object within the body of `resp`. For example, many APIs return
#' a body with information about the status of the response, cache
#' information, perhaps pagination information, and then the actual data in a
#' field such as `data`. If the desired part of the response body is in
#' `data$objects`, the value of this argument should be `c("data", "object")`.
#'
#' @returns The tibblified response body.
#' @family opinionated response parsers
#' @export
#'
#' @examplesIf rlang::is_installed("tibblify")
Expand Down Expand Up @@ -59,3 +45,28 @@ resp_tidy_json <- function(
}
return(NULL)
}

#' A policy to parse a response body as JSON
#'
#' Create a reusable tidy policy that applies [resp_tidy_json()].
#'
#' @inheritParams .shared-params
#' @returns A list with class `"nectar_tidy_policy"` and elements `tidy_fn` and
#' `tidy_args`.
#' @family opinionated response parsers
#' @export
#'
#' @examplesIf rlang::is_installed("tibblify")
#' tidy_policy_json(subset_path = "data")
tidy_policy_json <- function(
spec = NULL,
unspecified = "list",
subset_path = NULL
) {
tidy_policy_prepare(
resp_tidy_json,
spec = spec,
unspecified = unspecified,
subset_path = subset_path
)
}
17 changes: 17 additions & 0 deletions R/resp_tidy_unknown.R
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#'
#' @returns This function always throws an error. The error lists the names of
#' the response pieces after parsing with [resp_body_auto()].
#' @family opinionated response parsers
#' @export
#'
#' @examples
Expand All @@ -25,3 +26,19 @@ resp_tidy_unknown <- function(resp, call = rlang::caller_env()) {
call = call
)
}

#' A policy to error for unknown response bodies
#'
#' Create a reusable tidy policy that applies [resp_tidy_unknown()], signaling
#' an informative error.
#'
#' @returns A list with class `"nectar_tidy_policy"` and elements `tidy_fn` and
#' `tidy_args`.
#' @family opinionated response parsers
#' @export
#'
#' @examples
#' tidy_policy_unknown()
tidy_policy_unknown <- function() {
tidy_policy_prepare(resp_tidy_unknown)
}
85 changes: 85 additions & 0 deletions R/tidy_policy_prepare.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#' Prepare tidying independent of a request
#'
#' This constructor stores a response tidying function and arguments so the same
#' tidying strategy can be reused across requests.
#'
#' @param tidy_fn (`function`) A function that will be invoked by [resp_tidy()]
#' to tidy a response.
#' @param ... (`any`) Arguments to pass to `tidy_fn`.
#' @returns A list with class `"nectar_tidy_policy"` and elements `tidy_fn` and
#' `tidy_args`.
#' @family opinionated response parsers
#' @export
#'
#' @examples
#' tidy_policy_prepare(httr2::resp_body_json, simplifyVector = TRUE)
tidy_policy_prepare <- function(tidy_fn, ...) {
tidy_fn <- rlang::as_function(tidy_fn, call = rlang::caller_env())
.as_nectar_tidy_policy(
list(tidy_fn = tidy_fn, tidy_args = rlang::list2(...))
)
}

.as_nectar_tidy_policy <- function(tidy_policy, call = rlang::caller_env()) {
UseMethod(".as_nectar_tidy_policy")
}

#' @export
.as_nectar_tidy_policy.nectar_tidy_policy <- function(
tidy_policy,
call = rlang::caller_env()
) {
return(tidy_policy)
}

#' @export
.as_nectar_tidy_policy.NULL <- function(
tidy_policy,
call = rlang::caller_env()
) {
return(NULL)
}

#' @export
.as_nectar_tidy_policy.function <- function(
tidy_policy,
call = rlang::caller_env()
) {
.as_nectar_tidy_policy(list(tidy_fn = tidy_policy), call = call)
}

#' @export
.as_nectar_tidy_policy.list <- function(
tidy_policy,
call = rlang::caller_env()
) {
if (!("tidy_fn" %in% names(tidy_policy))) {
return(NextMethod())
}
tidy_args <- stbl::to_lst(tidy_policy$tidy_args) %||% list()
structure(
list(
tidy_fn = tidy_policy$tidy_fn,
tidy_args = c(
tidy_args,
tidy_policy[setdiff(names(tidy_policy), c("tidy_fn", "tidy_args"))]
)
),
class = "nectar_tidy_policy"
)
}

#' @export
.as_nectar_tidy_policy.default <- function(
tidy_policy,
call = rlang::caller_env()
) {
.nectar_abort(
c(
"{.arg {tidy_policy}} must be `NULL` or a {.cls nectar_tidy_policy}.",
x = "{.arg {tidy_policy}} is {.obj_type_friendly {tidy_policy}}."
),
subclass = "unsupported_tidy_policy_class",
call = call
)
Comment thread
jonthegeek marked this conversation as resolved.
}
3 changes: 1 addition & 2 deletions README.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ req <- req_prepare(
query = list(
rows = 10, cursor = "*", select = c("publisher", "DOI"), .multi = "comma"
),
tidy_fn = resp_tidy_json,
tidy_args = list(subset_path = c("message", "items")),
tidy_policy = tidy_policy_json(subset_path = c("message", "items")),
pagination_fn = iterate_with_json_cursor(
param_name = "cursor",
next_cursor_path = c("message", "next-cursor")
Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ req <- req_prepare(
query = list(
rows = 10, cursor = "*", select = c("publisher", "DOI"), .multi = "comma"
),
tidy_fn = resp_tidy_json,
tidy_args = list(subset_path = c("message", "items")),
tidy_policy = tidy_policy_json(subset_path = c("message", "items")),
pagination_fn = iterate_with_json_cursor(
param_name = "cursor",
next_cursor_path = c("message", "next-cursor")
Expand Down
26 changes: 21 additions & 5 deletions man/dot-shared-params.Rd

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

11 changes: 4 additions & 7 deletions man/req_prepare.Rd

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

Loading
Loading