gh
gh copied to clipboard
Generalize gh plumbing?
I'm a huge fan of how gh
parses and builds an HTTP request. As I rely more and more on web APIs, I'm quickly desiring a layer of abstract that allows for me to just copy and paste the endpoint information from the web API. e.g.
GET /api/v1/quiz_submissions/:quiz_submission_id/questions
https://canvas.instructure.com/doc/api/quiz_submissions.html
The function would then: 1. choose the appropriate verb and 2. parse with the parameters in the string. The present way that I'm accomplishing this is:
paste0("GET /api/v1/quiz_submissions/", quiz_submission_id,"/questions")
# or
glue::glue("GET /api/v1/quiz_submissions/{{quiz_submission_id}}/questions")
Thus, to this end, would there be any objection if I worked on a package {rest} that generalized the request build held by gh
? c.f.
https://github.com/r-lib/gh/blob/master/R/gh_request.R
That would make a lot of sense. Somewhat related to #46 I think.
This is also semi-related to what gargle does (but for Google APIs, specifically).
https://github.com/r-lib/gargle/blob/master/R/request-develop.R
request_develop()
takes input from a user (probably already pre-processed by a wrapper function or package) via params
and info about an endpoint
(as published in JSON Discovery Documents, a Google API thing) and does light validation + sticking the info into all the right places (URL substitution, query params, body).
This was also something we'd thought about for httr2
With the extra COVID-19 workload decreasing, I've slowly started to hack away at this in https://github.com/coatless/rest
In particular, I've transformed the gh()
call to rest()
and reduced any GitHub-specific features (more work is needed here). As an example, an API could be called with:
rest::rest("GET /facts", .api_url = "https://cat-fact.herokuapp.com")
#> {
#> "all": [
#> {
#> "_id": "5887e1d85c873e0011036889",
#> "text": "Cats make about 100 different sounds. Dogs make only about 10.",
#> "type": "cat",
#> "user": {
#> "_id": "5a9ac18c7478810ea6c06381",
#> "name": {
#> "first": "Alex",
#> "last": "Wohlbruck"
#> }
#> },
#> "upvotes": 10,
#> "userUpvoted": {}
#> },
#> {
#> "_id": "58e008b80aac31001185ed0d",
#> "text": "Adult cats only meow to communicate with humans.",
#> "type": "cat",
#> "user": {
#> "_id": "58e007480aac31001185ecef",
#> "name": {
#> "first": "Kasimir",
#> "last": "Schulz"
#> }
#> },
#> "upvotes": 9,
#> "userUpvoted": {}
#> }
#> ]
#> }
I still have to solve the best way to register APIs within the package. The easiest way would probably be to have any API package create a wrapper around this function and supply appropriate token names. So, in the context of gh
, that would be:
gh <- function(endpoint, ...) {
rest::rest(endpoint,
.api_url = "https://api.github.com",
.token_name="GITHUB_PAT", ...)
}
Not sure if this aligns with everyone's previous thoughts in this issue ticket.
I think it makes sense. Although, it is not quite clear how a generic solution can handle e.g. GitHub's pagination. There are probably other API dependent aspects as well.
I think the generic handling is now in https://httr2.r-lib.org/reference/req_template.html.