--- title: "nectar" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{nectar} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ```{r setup} library(nectar) ``` nectar is a framework for building R packages that wrap web APIs. It provides a set of opinionated functions that handle the most common patterns in API wrappers: authentication, request preparation, pagination, response parsing, and tidying. This vignette demonstrates how to use nectar's core functions together, using the [Crossref Unified Resource API](https://api.crossref.org/swagger-ui/index.html) as a real-world example. ## Preparing a request with `req_prepare()` The main entry point in nectar is `req_prepare()`. It wraps `httr2::request()` and a collection of `req_*` functions into a single, composable call. You provide the base URL and any options you need, such as authentication, pagination, and response tidying, and `req_prepare()` stores those settings directly on the request object so that downstream functions can use them automatically. Here we prepare a request to the Crossref `/works` endpoint. We ask for ten results per page (`rows = 10`), select only the "publisher" and "DOI" fields, tell `{httr2}` to concatenate the `select` parameter with commas (`.multi`), and set the `cursor` parameter to `"*"` to trigger cursor-based pagination: ```{r prepare, eval = FALSE} req <- req_prepare( "https://api.crossref.org/works", query = list( rows = 10, cursor = "*", select = c("publisher", "DOI"), .multi = "comma" ) ) ``` ## Authentication with `auth_api_key()` Many APIs accept an optional key (or, as in Crossref's case, an email address) to identify your application and gain access to a higher rate limit. nectar provides `auth_api_key()` to prepare this authentication and pass it through `req_prepare()` via the `auth` argument: ```{r auth, eval = FALSE} req <- req_prepare( "https://api.crossref.org/works", query = list( rows = 10, cursor = "*", select = c("publisher", "DOI"), .multi = "comma" ), auth = auth_api_key("mailto", api_key = "your@email.com", location = "query") ) ``` If you need to remove the key (for example, to fall back to anonymous access), pass `api_key = NULL`: ```{r auth-remove, eval = FALSE} req <- req_auth_api_key( req, # From above, with "your@email.com" attached as the key. parameter_name = "mailto", api_key = NULL, location = "query" ) ``` ## Response tidying with `resp_tidy_json()` The Crossref API returns JSON. nectar's `resp_tidy_json()` function parses the JSON response body and converts the result to a tibble using `tibblify::tibblify()`. You can extract a nested path from the response at the same time with the `subset_path` argument. For Crossref, the actual work items live at `message$items` in the response body. To have `req_prepare()` automatically tidy each response when you call `resp_parse()` later, supply a prepared tidying policy object via the `tidy_policy` argument, such as `tidy_policy_json(subset_path = c("message", "items"))`: ```{r tidy, eval = FALSE} req <- req_prepare( "https://api.crossref.org/works", query = list( rows = 10, cursor = "*", select = c("publisher", "DOI"), .multi = "comma" ), tidy_policy = tidy_policy_json(subset_path = c("message", "items")) ) ``` ## Pagination with `iterate_with_json_cursor()` Crossref uses cursor-based pagination: each response contains a `message$next-cursor` field that should be sent as the `cursor` query parameter in the next request. nectar's `iterate_with_json_cursor()` generates the iterator function for this pattern, given the parameter name and the path to the cursor value in the response body: ```{r paginate, eval = FALSE} iterate_xref <- iterate_with_json_cursor( param_name = "cursor", next_cursor_path = c("message", "next-cursor") ) ``` You can attach this iterator to the request via the `pagination_fn` argument in `req_prepare()`: ```{r prepare-full, eval = FALSE} req <- req_prepare( "https://api.crossref.org/works", query = list( rows = 10, cursor = "*", select = c("publisher", "DOI"), .multi = "comma" ), tidy_policy = tidy_policy_json(subset_path = c("message", "items")), pagination_fn = iterate_xref ) ``` ## Performing the request with `req_perform_opinionated()` `req_perform_opinionated()` performs the request. If a pagination function is attached to the request (as above), it automatically uses `httr2::req_perform_iterative()` to fetch multiple pages; otherwise it falls back to a single `httr2::req_perform()` call. It also applies a retry policy to handle transient errors. The `max_reqs` argument controls how many pages to fetch (default: `2`). During development, keep it small. Set it to `Inf` once you are confident the request works: ```{r perform, eval = FALSE} resps <- req_perform_opinionated(req, max_reqs = 2) ``` The result is always a list of `httr2_response` objects with additional class `nectar_responses`, so downstream handling is consistent regardless of whether one or many pages were fetched. ## Parsing the response with `resp_parse()` `resp_parse()` converts the raw responses into a usable R object. Because the request was prepared with `tidy_policy = tidy_policy_json(...)`, `resp_parse()` will find that function automatically and apply it to each response, then combine the results: ```{r parse, eval = FALSE} result <- resp_parse(resps) result ``` ## Putting it all together The three core functions compose naturally into a pipeline. Here is the full workflow in one expression: ```{r pipeline, eval = FALSE} result <- req_prepare( "https://api.crossref.org/works", query = list( rows = 10, cursor = "*", select = c("publisher", "DOI"), .multi = "comma" ), 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") ) ) |> req_perform_opinionated(max_reqs = 3) |> resp_parse() result #> # A tibble: 30 × 2 #> publisher DOI #> #> 1 Springer Fachmedien Wiesbaden 10.1007/978-3-658-17671-6_18-1 #> 2 Springer International Publishing 10.1007/978-3-031-23161-2_300726 #> 3 Elsevier 10.1016/b978-0-08-102696-0.00020-8 #> 4 Springer International Publishing 10.1007/978-3-031-28170-9_6 #> 5 Springer Nature Singapore 10.1007/978-981-16-8679-5_306 #> 6 Springer Singapore 10.1007/978-981-15-1636-8_42 #> 7 Springer Berlin Heidelberg 10.1007/978-3-642-33832-8_41 #> 8 Springer Nature Switzerland 10.1007/978-3-031-72371-1_11 #> 9 Springer International Publishing 10.1007/978-3-319-18938-3 #> 10 Springer International Publishing 10.1007/978-3-319-19932-0_5 #> # i 20 more rows #> # i Use `print(n = ...)` to see more rows ``` The resulting tibble contains the DOIs from the first two pages of Crossref works. Once you are ready to fetch all pages, replace `max_reqs = 2` with `max_reqs = Inf`. ## Building an API package with nectar In practice, you would wrap these calls inside exported functions in your own package. For example, a `crossref` package might provide: ```{r package-example, eval = FALSE} # R/works.R (inside a hypothetical "crossref" package) works <- function( rows = 20, select = NULL, mailto = NULL ) { req_prepare( "https://api.crossref.org/works", query = list(rows = rows, cursor = "*", select = select), auth = auth_api_key("mailto", api_key = mailto, location = "query"), 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") ) ) |> req_perform_opinionated() |> resp_parse() } ``` Users of the package never need to know about cursors, retry logic, or JSON parsing; nectar handles it all.