Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attempting to use options package. #936

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ Imports:
mime,
lifecycle (>= 0.2.0),
ellipsis (>= 0.3.0),
rlang
rlang,
options
ByteCompile: TRUE
Suggests:
testthat (>= 0.11.0),
Expand Down
2 changes: 1 addition & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export(endpoint_serializer)
export(forward)
export(getCharacterSet)
export(get_character_set)
export(get_option_or_env)
export(include_file)
export(include_html)
export(include_md)
Expand Down Expand Up @@ -102,6 +101,7 @@ export(sessionCookie)
export(session_cookie)
export(validate_api_spec)
import(R6)
import(options)
import(promises)
import(stringi)
importFrom(grDevices,dev.cur)
Expand Down
3 changes: 2 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# plumber (development version)

* Allow to set plumber options using environment variables `?options_plumber`. (@meztez #934)
* Allow to set plumber options using environment variables `?plumber::options`
via the `options` package. (@meztez #936)
* Add support for quoted boundary for multipart request parsing. (@meztez #924)
* Fix #916, related to `parseUTF8` return value attribute `srcfile` on Windows. (#930)

Expand Down
212 changes: 145 additions & 67 deletions R/options_plumber.R
Original file line number Diff line number Diff line change
@@ -1,57 +1,152 @@
#' @import options
options::set_option_name_fn(function(package, name) {
paste0(package, ".", name)
})

options::set_envvar_name_fn(function(package, name) {
gsub("[^A-Z0-9]", "_", toupper(paste0(package, "_", name)))
})

options::define_option(
option = "port",
default = NULL,
desc = paste(
"Port Plumber will attempt to use to start http server.",
"If the port is already in use, server will not be able to start."
)
)

options::define_option(
option = "docs",
default = TRUE,
desc = paste(
'Name of the visual documentation interface to use.',
'Default `TRUE` is the same as `"swagger"`.'
)
)

options::define_option(
option = "docs.callback",
default = NULL,
desc = paste(
"A function. Called with a single parameter corresponding to the visual documentation url",
"after Plumber server is ready. This can be used by RStudio to open the docs when then API",
"is ran from the editor."
)
)

options::define_option(
option = "trailingSlash",
default = FALSE,
desc = paste(
"Logical value which allows the router to redirect any request that has a matching route with",
"a trailing slash. For example, if set to `TRUE` and the GET route `/test/` existed, then a",
"GET request of `/test?a=1` would redirect to `/test/?a=1`.",
"This option will default to `TRUE` in a future release."
),
envvar_fn = options::envvar_is_true()
)

options::define_option(
option = "methodNotAllowed",
default = TRUE,
desc = paste(
'`r lifecycle::badge("experimental")` Logical value which allows the router to notify that an',
"unavailable method was requested, but a different request method is allowed. For example, if",
"set to `TRUE` and the GET route `/test` existed, then a POST request of `/test` would receive",
"a 405 status and the allowed methods."
),
envvar_fn = options::envvar_is_true()
)

options::define_option(
option = "apiURL",
default = NULL,
desc = paste(
"Server urls for OpenAPI Specification respecting pattern `scheme://host:port/path`.",
"Other `api*` options will be ignored when set."
)
)

options::define_option(
option = "apiScheme",
default = "http",
desc = paste(
"Scheme used to build OpenAPI url and server url for OpenAPI Specification."
)
)

options::define_option(
option = "apiHost",
default = character(),
desc = paste(
"Host used to build docs url and server url for OpenAPI Specification.",
"Defaults to `host` defined by `run` method when used inside a running router."
)
)

options::define_option(
option = "apiPort",
default = character(),
desc = paste(
"Port used to build OpenAPI url and server url for OpenAPI Specification.",
"Defaults to `port` defined by `run` method when used inside a running router."
)
)

options::define_option(
option = "apiPath",
default = character(),
desc = "Path used to build OpenAPI url and server url for OpenAPI Specification."
)

options::define_option(
option = "maxRequestSize",
default = 0,
desc = paste(
"Maximum length in bytes of request body.",
"Body larger than maximum are rejected with http error 413.",
"`0` means unlimited size. Defaults to `0`."
)
)

options::define_option(
option = "sharedSecret",
default = NULL,
desc = paste(
"Shared secret used to filter incoming request. When `NULL`, secret is not validated.",
"Otherwise, Plumber compares secret with http header `PLUMBER_SHARED_SECRET`.",
"Failure to match results in http error 400."
)
)

options::define_option(
option = "legacyRedirects",
default = TRUE,
desc = paste(
"Plumber will redirect legacy route `/__swagger__/` and `/__swagger__/index.html` to",
"`../__docs__/` and `../__docs__/index.html`.",
"You can disable this behavior by setting this option to `FALSE`."
),
envvar_fn = options::envvar_is_true()
)

#' @eval options::as_roxygen_docs()
NULL

#' Plumber options
#'
#' There are a number of global options that affect Plumber's behavior. These can
#' be set globally with [options()] or with [options_plumber()]. Options set using
#' [options_plumber()] should not include the `plumber.` prefix. Alternatively,
#' environment variable can be used to set plumber options using uppercase and
#' underscores (i.e. to set `plumber.apiHost` you can set environment variable `PLUMBER_APIHOST`).
#'
#' \describe{
#' \item{`plumber.port`}{Port Plumber will attempt to use to start http server.
#' If the port is already in use, server will not be able to start. Defaults to `NULL`.}
#' \item{`plumber.docs`}{Name of the visual documentation interface to use. Defaults to `TRUE`, which will use `"swagger"`.}
#' \item{`plumber.docs.callback`}{A function. Called with
#' a single parameter corresponding to the visual documentation url after Plumber server is ready. This can be used
#' by RStudio to open the docs when then API is ran from the editor. Defaults to option `NULL`.}
#' \item{`plumber.trailingSlash`}{Logical value which allows the router to redirect any request
#' that has a matching route with a trailing slash. For example, if set to `TRUE` and the
#' GET route `/test/` existed, then a GET request of `/test?a=1` would redirect to
#' `/test/?a=1`. Defaults to `FALSE`. This option will default to `TRUE` in a future release.}
#' \item{`plumber.methodNotAllowed`}{`r lifecycle::badge("experimental")`
#' Logical value which allows the router to notify that an
#' unavailable method was requested, but a different request method is allowed. For example,
#' if set to `TRUE` and the GET route `/test` existed, then a POST request of `/test` would
#' receive a 405 status and the allowed methods. Defaults to `TRUE`.}
#' \item{`plumber.apiURL`}{Server urls for OpenAPI Specification respecting
#' pattern `scheme://host:port/path`. Other `api*` options will be ignored when set.}
#' \item{`plumber.apiScheme`}{Scheme used to build OpenAPI url and server url for
#' OpenAPI Specification. Defaults to `http`, or an empty string
#' when used outside a running router.}
#' \item{`plumber.apiHost`}{Host used to build docs url and server url for
#' OpenAPI Specification. Defaults to `host` defined by `run` method, or an empty string
#' when used outside a running router.}
#' \item{`plumber.apiPort`}{Port used to build OpenAPI url and server url for
#' OpenAPI Specification. Defaults to `port` defined by `run` method, or an empty string
#' when used outside a running router.}
#' \item{`plumber.apiPath`}{Path used to build OpenAPI url and server url for
#' OpenAPI Specification. Defaults to an empty string.}
#' \item{`plumber.maxRequestSize`}{Maximum length in bytes of request body. Body larger
#' than maximum are rejected with http error 413. `0` means unlimited size. Defaults to `0`.}
#' \item{`plumber.sharedSecret`}{Shared secret used to filter incoming request.
#' When `NULL`, secret is not validated. Otherwise, Plumber compares secret with http header
#' `PLUMBER_SHARED_SECRET`. Failure to match results in http error 400. Defaults to `NULL`.}
#' \item{`plumber.legacyRedirects`}{Plumber will redirect legacy route `/__swagger__/` and
#' `/__swagger__/index.html` to `../__docs__/` and `../__docs__/index.html`. You can disable this
#' by settings this option to `FALSE`. Defaults to `TRUE`}
#' }
#' Options that change behaviors can be set globally with
#' \code{\link[base:options]{options}}, \code{\link[plumber:options_plumber]{options_plumber}}
#' or with environment variables.
#'
#' @param ... Ignored. Should be empty
#' @param port,docs,docs.callback,trailingSlash,methodNotAllowed,apiScheme,apiHost,apiPort,apiPath,apiURL,maxRequestSize,sharedSecret,legacyRedirects See details
#' @return
#' The complete, prior set of [options()] values.
#' If a particular parameter is not supplied, it will return the current value.
#' If no parameters are supplied, all returned values will be the current [options()] values.
#' @param port,docs,docs.callback,trailingSlash,methodNotAllowed,apiScheme,apiHost,apiPort,apiPath,apiURL,maxRequestSize,sharedSecret,legacyRedirects
#' See Options.
#' @return Invisibly an options list from `options::opts(env = "plumber")`.
#' @export
#' @keywords internal
#' @rdname options
options_plumber <- function(
...,
port = getOption("plumber.port"),
Expand Down Expand Up @@ -90,24 +185,7 @@ options_plumber <- function(
plumber.sharedSecret = sharedSecret,
plumber.legacyRedirects = legacyRedirects
)
}

#' Get an option value, alternatively look in environment for value
#' @rdname options_plumber
#' @inheritParams base::options
#' @export
get_option_or_env <- function(x, default = NULL) {

getOption(x, default = {
env_name <- toupper(chartr(".", "_", x))
res <- Sys.getenv(env_name)
if (res == "") {
return(default)
}
if (res %in% c("TRUE", "FALSE")) {
return(as.logical(res))
}
res
})
invisible(options::opts(env = "plumber"))

}
14 changes: 7 additions & 7 deletions R/plumber.R
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ Plumber <- R6Class(
#' @importFrom rlang missing_arg
run = function(
host = '127.0.0.1',
port = get_option_or_env('plumber.port', NULL),
port = options::opt("port", env = "plumber"),
swagger = deprecated(),
debug = missing_arg(),
swaggerCallback = missing_arg(),
Expand Down Expand Up @@ -211,15 +211,15 @@ Plumber <- R6Class(
port <- findPort(port)

# Delay setting max size option. It could be set in `plumber.R`, which is after initialization
private$maxSize <- get_option_or_env('plumber.maxRequestSize', 0) #0 Unlimited
private$maxSize <- options::opt("maxRequestSize", env = "plumber") #0 Unlimited

# Delay the setting of swaggerCallback as long as possible.
# An option could be set in `plumber.R`, which is after initialization
# Order: Run method parameter, internally set value, option, fallback option, NULL
swaggerCallback <-
rlang::maybe_missing(swaggerCallback,
rlang::maybe_missing(private$docs_callback,
get_option_or_env('plumber.docs.callback', get_option_or_env('plumber.swagger.url', NULL))
options::opt("docs.callback", default = getOption("plumber.swagger.url", NULL), env = "plumber")
)
)

Expand Down Expand Up @@ -784,7 +784,7 @@ Plumber <- R6Class(
# No endpoint could handle this request. 404
notFoundStep <- function(...) {

if (isTRUE(get_option_or_env("plumber.trailingSlash", FALSE))) {
if (isTRUE(options::opt("trailingSlash", env = "plumber"))) {
# Redirect to the slash route, if it exists
path <- req$PATH_INFO
# If the path does not end in a slash,
Expand Down Expand Up @@ -813,7 +813,7 @@ Plumber <- R6Class(
# No trailing-slash route exists...
# Try allowed verbs

if (isTRUE(get_option_or_env("plumber.methodNotAllowed", TRUE))) {
if (isTRUE(options::opt("methodNotAllowed", env = "plumber"))) {
# Notify about allowed verbs
if (is_405(req$pr, req$PATH_INFO, req$REQUEST_METHOD)) {
res$status <- 405L
Expand Down Expand Up @@ -933,7 +933,7 @@ Plumber <- R6Class(
#' If using [options_plumber()], the value must be set before initializing your Plumber router.
#' @param ... Arguments for the visual documentation. See each visual documentation package for further details.
setDocs = function(
docs = get_option_or_env("plumber.docs", TRUE),
docs = options::opt("docs", env = "plumber"),
...
) {
private$docs_info <- upgrade_docs_parameter(docs, ...)
Expand All @@ -948,7 +948,7 @@ Plumber <- R6Class(
#' See also: [pr_set_docs_callback()]
#' @param callback a callback function for taking action on the docs url. (Also accepts `NULL` values to disable the `callback`.)
setDocsCallback = function(
callback = get_option_or_env('plumber.docs.callback', NULL)
callback = options::opt("docs.callback", env = "plumber")
) {
# Use callback when defined
if (!length(callback) || !is.function(callback)) {
Expand Down
2 changes: 1 addition & 1 deletion R/pr.R
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ pr_filter <- function(pr,
#' @export
pr_run <- function(pr,
host = '127.0.0.1',
port = get_option_or_env('plumber.port', NULL),
port = options::opt("port", env = "plumber"),
...,
debug = missing_arg(),
docs = missing_arg(),
Expand Down
4 changes: 2 additions & 2 deletions R/pr_set.R
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ pr_set_debug <- function(pr, debug = interactive()) {
#' }
pr_set_docs <- function(
pr,
docs = get_option_or_env("plumber.docs", TRUE),
docs = options::opt("docs", env = "plumber"),
...
) {
validate_pr(pr)
Expand Down Expand Up @@ -206,7 +206,7 @@ pr_set_docs <- function(
#' }
pr_set_docs_callback <- function(
pr,
callback = get_option_or_env('plumber.docs.callback', NULL)
callback = options::opt("docs.callback", env = "plumber")
) {
validate_pr(pr)
pr$setDocsCallback(callback = callback)
Expand Down
2 changes: 1 addition & 1 deletion R/shared-secret-filter.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#' @noRd
sharedSecretFilter <- function(req, res){
secret <- get_option_or_env("plumber.sharedSecret", NULL)
secret <- options::opt("sharedSecret", env = "plumber")
if (!is.null(secret)){
supplied <- req$HTTP_PLUMBER_SHARED_SECRET
if (!identical(supplied, secret)){
Expand Down
Loading
Loading