Plumber: Conditional serialisation (or, how to serialise errors when otherwise serialising images)

Created on 7 Mar 2019  路  4Comments  路  Source: rstudio/plumber

I've a toy API with two endrpoints: one that returns a JSON list and one that returns a plot:

#* Plots a time series of the nearest grid cell
#* @param lat
#* @param lon
#* @get /tsplot
#* @png (width = 1920, height = 1080)
function(res, lat = -37, lon = 144) {
}

#* Produces statistics of the nearest grid cell
#* @param lat
#* @param lon
#* @get /stats
function(res, lat = -37, lon = 144) {
}

They both work well for valid input. But I'm trying to make sure I sanitise the inputs (in both cases, lat and lon should be numeric and within certain ranges). For the /stats endpoint, this works great:

if (!is.null(err_msg)) {
  res$status <- 400
  return(list(error = jsonlite::unbox(err_msg)))
}

But for the /tsplot endpoint, which uses the @png serialiser, trying to return the error as JSON results in an error:

Warning in file(data$file, "rb") :
cannot open file '/tmp/Rtmp5bf3eg/file12f072599': No such file or directory

My feeling is that I don't want to necessarily bypass the serialisation entirely鈥擨 just want to switch serialisers when there's an error to handle. I've tried doing it myself, but I get the same error:

if (!is.null(err_msg)) {
  res$status <- 400
  res$body <- jsonlite::toJSON(list(error = err_msg), unboxed = TRUE)
  return(res)
}

How should I approach this?

answered? question

Most helpful comment

When I throw an error with stop(), I'm finding that it makes it to my console output, but it isn't reflected in the API output.

For example:

function(res, lat = -37, lon = 144) {

  assert_bound <- partial(assert_numeric, finite = TRUE, any.missing = FALSE,
    len = 1)
  req_lat <- as.numeric(lat)
  req_lon <- as.numeric(lon)
  assert_bound(req_lat, lower = -90, upper = 90)
  assert_bound(req_lon, lower = -180, upper = 180)

  # continue with endpoint functionality
}

If I pass stats?lat=dog, the console output is:

Warning in (function (res, lat = -37, lon = 144) :
NAs introduced by coercion

But the API output is simply:

{"error":["500 - Internal server error"]}

The same is true if I call stop() myself.

All 4 comments

If you have reached an bad state and you want to return an error message, use stop("My error message").

When changing the status of res and returning a list, we are manually making a return object that is going through the serializer_json serializer which essentially returns the object as is. Unfortunately, the png serializer will try serialize the list object provided, even if it is filled with non png information.

To guarantee it does not get serialized with the _expected_ serializer and instead returns an error, use stop with an appropriate error message.


I have a preference towards the R package checkmate for validating inputs. It's really fast and has assert methods built in, great error messages, and helps keep your methods clean while still throwing when a bad input is found.

Example:

a <- 4.5
checkmate::assert_integer(a)
#> Error: Assertion on 'a' failed: Must be of type 'integer', not 'double'.

a <- 20L
checkmate::assert_integer(a, upper = 10)
#> Error: Assertion on 'a' failed: Element 1 is not <= 10.

That sounds great, @schloerke! Is there any way to get the API to return an appropriate HTTP error code, like 400, if I simply throw an error with stop()?

When I throw an error with stop(), I'm finding that it makes it to my console output, but it isn't reflected in the API output.

For example:

function(res, lat = -37, lon = 144) {

  assert_bound <- partial(assert_numeric, finite = TRUE, any.missing = FALSE,
    len = 1)
  req_lat <- as.numeric(lat)
  req_lon <- as.numeric(lon)
  assert_bound(req_lat, lower = -90, upper = 90)
  assert_bound(req_lon, lower = -180, upper = 180)

  # continue with endpoint functionality
}

If I pass stats?lat=dog, the console output is:

Warning in (function (res, lat = -37, lon = 144) :
NAs introduced by coercion

But the API output is simply:

{"error":["500 - Internal server error"]}

The same is true if I call stop() myself.

Historically, the debug value was set to interactive() and therefore would not display the message in the response

In plumber v1.0.0, this can be manually set:

pr() %>%
    pr_set_debug(TRUE) %>%
    .... # update pr some more

Closing

Was this page helpful?
0 / 5 - 0 ratings

Related issues

cbuchacher picture cbuchacher  路  4Comments

dmenne picture dmenne  路  4Comments

dpmccabe picture dpmccabe  路  6Comments

david-cortes picture david-cortes  路  3Comments

Amalabdu picture Amalabdu  路  6Comments