-
Notifications
You must be signed in to change notification settings - Fork 1
/
README.Rmd
198 lines (160 loc) · 5.21 KB
/
README.Rmd
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
---
output: github_document
---
<!-- README.md is generated from README.Rmd. Please edit that file -->
```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "man/figures/README-",
out.width = "100%"
)
```
# httpproblems
<!-- badges: start -->
[![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)
<!-- badges: end -->
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.