Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/atheriel/httpproblems

Report errors from R APIs using "Problem Details" (RFC 7807)
https://github.com/atheriel/httpproblems

plumber-api r rfc-7807 rfc7807

Last synced: about 2 months ago
JSON representation

Report errors from R APIs using "Problem Details" (RFC 7807)

Awesome Lists containing this project

README

        

---
output: github_document
---

```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "man/figures/README-",
out.width = "100%"
)
```

# httpproblems

[![CRAN status](https://www.r-pkg.org/badges/version/httpproblems)](https://CRAN.R-project.org/package=httpproblems)
[![R-CMD-check](https://github.com/atheriel/httpproblems/workflows/R-CMD-check/badge.svg)](https://github.com/atheriel/httpproblems/actions)

This R package is an answer to the question "how can I report errors from my R
API in a standard, consistent way?"

Specifically, it implements the "Problem Details" specification from [RFC
7807](https://tools.ietf.org/html/rfc7807), which is an emerging standard for
errors supported by many other languages and tools.

Although **httpproblems** originated for use with
[Plumber](https://www.rplumber.io) (see below), it has no dependencies of its
own and can be used with any R web framework.

## Installation

You can install the latest release of **httpproblems** from
[CRAN](https://CRAN.R-project.org):

``` r
install.packages("httpproblems")
```

or the development version from GitHub with

```r
# install.packages("remotes")
remotes::install_github("atheriel/httpproblems")
```

## Usage

**httpproblems** provides helpers to create a few common HTTP errors:

* `bad_request()`
* `not_found()`
* `unauthorized()`
* `forbidden()`
* `conflict()`
* `internal_server_error()`

as well as a generic `http_problem()` that can be used with any valid status
code.

These functions return a normal list with elements [in line with the
standard](https://tools.ietf.org/html/rfc7807#section-3.1):

```{r bad_request}
library(httpproblems)

body <- bad_request("Parameter 'id' must be a number.")
str(body)
```

This object could be returned by an endpoint as normal after serialization to
e.g. JSON.

In addition to these basic methods, **httpproblems** also has a set of
`stop_for_http_problem()` functions:

* `stop_for_bad_request()`
* `stop_for_not_found()`
* `stop_for_unauthorized()`
* `stop_for_forbidden()`
* `stop_for_conflict()`
* `stop_for_internal_server_error()`

These leverage R's extensible condition system to issue custom errors for these
problems:

```{r stop_for_bad_request, error=TRUE}
stop_for_bad_request("Parameter 'id' must be a number.")

# What does the error actually contain?
tryCatch(
stop_for_bad_request("Parameter 'id' must be a number."),
error = function(e) {
str(e)
}
)
```

Typically, web frameworks allow you to control how uncaught errors are reported
-- which can be combined with these helpers to ensure that all API errors have a
consistent format for your users.

### Use with Plumber

Plumber allows users to control how errors are handled (via `pr_set_error()`),
which we can employ to produce a Problem Details structure for both expected
*and* unexpected errors:

```r
library(plumber)

pr() %>%
pr_get("/bad", function(req, res) {
stop_for_bad_request("The 'id' parameter must be a number.")
}) %>%
pr_get("/bug", function(req, res) {
stop("Another R error.")
}) %>%
pr_set_error(function(req, res, err) {
# Force "unboxed" JSON and the Content-Type from RFC 7807.
res$serializer <- serializer_unboxed_json(
type = "application/problem+json"
)
# If we have an http_problem_error, use its status and body
# fields. Otherwise, issue a 500 Internal Server Error.
if (inherits(err, "http_problem_error")) {
res$status <- err$status
return(err$body)
}
if (isTRUE(req$pr$getDebug())) {
internal_server_error(detail = err$message)
} else {
internal_server_error()
}
}) %>%
pr_run(port = 4444)
```

Users interacting with this API will see the following:

```shell
$ curl -vs localhost:4444/bad
> GET /bad HTTP/1.1
> Host: localhost:4444
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 400 Bad Request
< Date: Tue, 09 Feb 2021 15:19:33 GMT
< Content-Type: application/problem+json
< Content-Length: 160
<
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "Bad Request",
"status": 400,
"detail": "The 'id' parameter must be a number."
}
$ curl -vs localhost:4444/bug
> GET /bug HTTP/1.1
> Host: localhost:4444
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 09 Feb 2021 15:19:59 GMT
< Content-Type: application/problem+json
< Content-Length: 150
<
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.6.1",
"title": "Internal Server Error",
"status": 500,
"detail": "Another R error."
}
```

You may also wish to use `not_found()` for Plumber's 404 handler via
`pr_set_404()`.

## License

Copyright 2021 Aaron Jacobs

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.