Skip to content

Commit

Permalink
Merge pull request #3 from psolymos/main
Browse files Browse the repository at this point in the history
Add client/server error handling logic to template
  • Loading branch information
ALanguillaume authored Jun 16, 2023
2 parents f348729 + 560abd7 commit 20abf32
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 4 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ Imports:
yaml
Suggests:
httr,
pkgload,
rcmdcheck,
pkgload,
testthat (>= 3.0.0),
withr
Config/testthat/edition: 3
Expand Down
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ export(add_post)
export(add_put)
export(build_plumber_file)
export(create_mariobox)
export(http_error)
export(mario_log)
export(mario_try)
export(new_api)
export(remove_endpoint)
importFrom(cli,cat_bullet)
Expand Down
4 changes: 2 additions & 2 deletions R/dep_port.R
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ usethis_use_r <- function(
path = name
)
if (open) {
file.edit(name)
utils::file.edit(name)
}
}
#' @noRd
Expand Down Expand Up @@ -58,7 +58,7 @@ usethis_use_test <- function(
write_there(" expect_equal(2 * 2, 4)")
write_there("})")
if (open) {
file.edit(name)
utils::file.edit(name)
}
}

Expand Down
127 changes: 127 additions & 0 deletions R/http_error.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#' A data frame with HTTP error codes
#'
#' @noRd
http_error_codes <- structure(
list(category = c("Client Error", "Client Error", "Client Error",
"Client Error", "Client Error", "Client Error", "Client Error",
"Client Error", "Client Error", "Client Error", "Client Error",
"Client Error", "Client Error", "Client Error", "Client Error",
"Client Error", "Client Error", "Client Error", "Client Error",
"Client Error", "Client Error", "Client Error", "Client Error",
"Client Error", "Client Error", "Client Error", "Client Error",
"Client Error", "Client Error", "Server Error", "Server Error",
"Server Error", "Server Error", "Server Error", "Server Error",
"Server Error", "Server Error", "Server Error", "Server Error",
"Server Error"), status = c(400L, 401L, 402L, 403L, 404L, 405L,
406L, 407L, 408L, 409L, 410L, 411L, 412L, 413L, 414L, 415L, 416L,
417L, 418L, 421L, 422L, 423L, 424L, 425L, 426L, 428L, 429L, 431L,
451L, 500L, 501L, 502L, 503L, 504L, 505L, 506L, 507L, 508L, 510L,
511L), message = c("Bad Request", "Unauthorized", "Payment Required",
"Forbidden", "Not Found", "Method Not Allowed", "Not Acceptable",
"Proxy Authentication Required", "Request Timeout", "Conflict",
"Gone", "Length Required", "Precondition Failed", "Payload Too Large",
"URI Too Long", "Unsupported Media Type", "Range Not Satisfiable",
"Expectation Failed", "I'm a teapot", "Misdirected Request",
"Unprocessable Entity", "Locked", "Failed Dependency", "Too Early",
"Upgrade Required", "Precondition Required", "Too Many Requests",
"Request Header Fields Too Large", "Unavailable For Legal Reasons",
"Internal Server Error", "Not Implemented", "Bad Gateway", "Service Unavailable",
"Gateway Timeout", "HTTP Version Not Supported", "Variant Also Negotiates",
"Insufficient Storage", "Loop Detected", "Not Extended", "Network Authentication Required"
)), row.names = c(400L, 401L, 402L, 403L, 404L, 405L, 406L, 407L,
408L, 409L, 410L, 411L, 412L, 413L, 414L, 415L, 416L, 417L, 418L,
421L, 422L, 423L, 424L, 425L, 426L, 428L, 429L, 431L, 451L, 500L,
501L, 502L, 503L, 504L, 505L, 506L, 507L, 508L, 510L, 511L), class = "data.frame")

#' Produce HTTP errors
#'
#' This function is similar to [stop()] and is used to automatically
#' distinguish server vs. client errors in a Plumber API.
#'
#' @param status Integer, HTTP status code for the error (4xx or 5xx).
#' @param message Character, the error message (can be a vector).
#'
#' @return A condition object of class `"http_error"`.
#'
#' @export
#'
#' @examples
#' ## R's default error
#' str(attr(try(stop("Hey, stop!")), "condition"))
#'
#' ## default status code 500 with default error message
#' str(attr(try(http_error()), "condition"))
#'
#' ## custom status code with default error message
#' str(attr(try(http_error(501L)), "condition"))
#'
#' ## custom status code for client error
#' str(attr(try(http_error(400L, "Provide valid email address.")), "condition"))
#'
http_error <- function(status = 500L, message = NULL) {
status <- as.integer(status)
if (!(status %in% http_error_codes$status))
stop("Unrecognized status code.")
i <- as.list(http_error_codes[as.character(status),])
if (!is.null(message))
i[["message"]] <- message
x <- structure(i, class = c("http_error", "error", "condition"))
stop(x)
}

#' Try an expression in a Plumber endpoint allowing error recovery
#'
#' This function is similar to [try()] and is called inside a Plumber API endpoint
#' definition. The function needs access to Plumbers response object.
#' Use [http_error()] to automate the handling of HTTP status codes
#' and differentiate between server vs. client errors.
#'
#' @param res The HTTP response object.
#' @param expr An R expression to try.
#' @param message Logical, should the report of error messages be suppressed?
#' @param ... Other arguments passed to [try()].
#'
#' @return The value of the expression if 'expr' is evaluated without error,
#' but an invisible object of class ‘"try-error"’ containing the
#' error message, and the error condition as the ‘"condition"’
#' attribute, if it fails.
#'
#' @export
#'
mario_try <- function(res, expr, silent = TRUE, ...) {
x <- try(
expr,
silent = silent,
...
)
if (inherits(x, "try-error")) {
if (!inherits(attr(x, "condition"), "http_error")) {
mariobox::mario_log(
method = "500",
name = http_error_codes["500", "message"]
)
message(
geterrmessage()
)
res$status <- 500L
as.list(
http_error_codes["500",]
)
} else {
mariobox::mario_log(
method = attr(x, "condition")$status,
name = attr(x, "condition")$message
)
res$status <- attr(x, "condition")$status
unclass(
attr(x, "condition")
)
}
} else {
mariobox::mario_log(
method = "200",
name = "Success"
)
x
}
}
2 changes: 1 addition & 1 deletion R/manage_endpoints.R
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ add_endpoint <- function(
write_there(" )")
write_there(
sprintf(
" %s_%s_f()",
" mariobox::mario_try(res, %s_%s_f())",
tolower(method),
name
)
Expand Down
34 changes: 34 additions & 0 deletions man/http_error.Rd

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

29 changes: 29 additions & 0 deletions man/mario_try.Rd

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

0 comments on commit 20abf32

Please sign in to comment.