Changed customStop to stop(safeError). Refactored some middleware.R code. Fixed downloadHandler's bug of not responding to safeError.

This commit is contained in:
Barbara Borges Ribeiro
2016-04-14 17:31:34 +01:00
parent 3107eec697
commit fb3e4e4881
7 changed files with 72 additions and 71 deletions

View File

@@ -131,8 +131,7 @@ withLogErrors <- function(expr,
captureStackTraces(expr),
error = function(cond) {
# Don't print shiny.silent.error (i.e. validation errors)
if (inherits(cond, "shiny.silent.error"))
return()
if (inherits(cond, "shiny.silent.error")) return()
if (isTRUE(getOption("show.error.messages"))) {
printError(cond, full = full, offset = offset)
}

View File

@@ -307,29 +307,19 @@ HandlerManager <- R6Class("HandlerManager",
}
},
call = .httpServer(
function(req,
full = getOption("shiny.fullstacktrace", FALSE),
offset = getOption("shiny.stacktraceoffset", TRUE)) {
withCallingHandlers(captureStackTraces(handlers$invoke(req)),
error = function(cond) {
# Don't print shiny.silent.error (i.e. validation errors)
if (inherits(cond, "shiny.silent.error")) return()
if (isTRUE(getOption("show.error.messages"))) {
printError(cond, full = full, offset = offset)
sanitizeErrors = getOption('shiny.sanitize.errors', TRUE)
if (inherits(cond, 'shiny.custom.error') || !sanitizeErrors) {
stop(cond$message, call. = FALSE)
}
else {
stop(paste("An error has occurred. Check your logs or",
"contact the app author for clarification."),
call. = FALSE)
}
}
}
)
function (req) {
withCallingHandlers(withLogErrors(handlers$invoke(req)),
error = function(cond) {
sanitizeErrors <- getOption('shiny.sanitize.errors', TRUE)
if (inherits(cond, 'shiny.custom.error') || !sanitizeErrors) {
stop(cond$message, call. = FALSE)
} else {
stop(paste("An error has occurred. Check your logs or",
"contact the app author for clarification."),
call. = FALSE)
}
}
)
},
getOption('shiny.sharedSecret')
),

View File

@@ -97,11 +97,11 @@ NULL
#' of a function appears next to the srcref where it is defined, rather than
#' where it is currently being called from.}
#' \item{shiny.sanitize.errors}{If \code{TRUE} (the default), then normal
#' errors (i.e errors generated by \code{stop}) won't show up in the app; a
#' simple generic error message is printed instead (the error and strack trace
#' errors (i.e. errors not wrapped in \code{safeError}) won't show up in the app;
#' a simple generic error message is printed instead (the error and strack trace
#' printed to the console remain unchanged). If you want this behavior in
#' general, but you DO want a particular error message to get displayed to the
#' user, please use \code{customStop} instead.}
#' general, but you DO want a particular error \code{e} to get displayed to the
#' user, please use \code{stop(safeError(e))} instead.}
#' }
#' @name shiny-options
NULL
@@ -889,7 +889,7 @@ ShinySession <- R6Class(
)), silent = TRUE)
if (inherits(result, 'try-error')) {
unlink(tmpdata)
stop(result)
stop(attr(result, "condition", exact = TRUE))
}
return(httpResponse(
200,

View File

@@ -878,27 +878,29 @@ columnToRowData <- function(data) {
)
}
#' Create a custom error
#' Declare an error safe for the user to see
#'
#' This should be used when you want to create a custom error to which error
#' default options do not apply. In particular, \code{customStop} will
#' This should be used when you want to let the user see an error
#' message even if the default is to sanitize all errors. If you have an
#' error \code{e} and call \code{stop(safeError(e))}, then Shiny will
#' ignore the value of \code{getOption("shiny.sanitize.errors")} and always
#' display the error in the app itself.
#'
#' @param error Either an "error" object or a "character" object (string).
#' The former will just be rethrown, while the latter will be used as the
#' message of the error returned by \code{customStop}.
#' In the latter case, the string will become the message of the error
#' returned by \code{safeError}.
#' @param errorClass An optional class to add to the error.
#'
#' @details An error generated by \code{customStop} has priority over all
#' @return An "error" object
#'
#' @details An error generated by \code{safeError} has priority over all
#' other Shiny errors. This can be dangerous. For example, if you have set
#' \code{options(shiny.sanitize.errors = TRUE)}, then by default all error
#' messages are omitted in the app, and replaced by a generic error message.
#' However, this does not apply to \code{customStop}: whatever you pass
#' However, this does not apply to \code{safeError}: whatever you pass
#' through \code{error} will be displayed to the user. So, this should only
#' be used when you are sure that your error message does not contain any
#' sensitive information. In those situations, \code{customStop} can make
#' sensitive information. In those situations, \code{safeError} can make
#' your users' lives much easier by giving them a hint as to where the
#' error occurred.
#'
@@ -915,13 +917,13 @@ columnToRowData <- function(data) {
#' # Define UI
#' ui <- fluidPage(
#' textInput('number', 'Enter your favorite number from 1 to 10', '5'),
#' textOutput('errorStop'),
#' textOutput('errorCustomStop')
#' textOutput('normalError'),
#' textOutput('safeError')
#' )
#'
#' # Server logic
#' server <- function(input, output) {
#' output$errorStop <- renderText({
#' output$normalError <- renderText({
#' number <- input$number
#' if (number %in% 1:10) {
#' return(paste('You chose', number, '!'))
@@ -931,14 +933,14 @@ columnToRowData <- function(data) {
#' )
#' }
#' })
#' output$errorCustomStop <- renderText({
#' output$safeError <- renderText({
#' number <- input$number
#' if (number %in% 1:10) {
#' return(paste('You chose', number, '!'))
#' } else {
#' customStop(
#' stop(safeError(
#' paste(number, 'is not a number between 1 and 10')
#' )
#' ))
#' }
#' })
#' }
@@ -947,15 +949,21 @@ columnToRowData <- function(data) {
#' shinyApp(ui, server)
#' }
#' @export
customStop <- function(error, errorClass = character(0)) {
safeError <- function(error, errorClass = character(0)) {
if (inherits(error, "error")) {
class(error) <- c("shiny.custom.error", errorClass, class(error))
stop(error)
cond <- structure(
list(message = error$message),
class = c("shiny.custom.error", errorClass, class(error))
)
} else if (inherits(error, "character")) {
stopWithCondition(c("shiny.custom.error", errorClass), error)
cond <- structure(
list(message = error),
class = c("shiny.custom.error", errorClass, "error", "condition")
)
} else {
stop("The class of the `error` parameter must be either 'error' or 'character'")
}
return(cond)
}
#' Validate input values and other conditions