Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
3bad2f0
Migrate to new swagger dist files. As of now, these are all local. Ev…
blairj09 Aug 24, 2018
a2ae0c6
Update parse-globals defaultGlobals to reflect openapi 3.0.2
blairj09 Dec 7, 2018
fd07683
Add comments on swagger-ui html
blairj09 Dec 7, 2018
b11c9a5
ifelse to switch
schloerke Dec 7, 2018
c8f7e3e
Merge branch 'openapi' of https://github.com/blairj09/plumber into sw…
schloerke Dec 10, 2018
b85ac85
remove unused top level items in swagger and add a blank paths list
schloerke Dec 11, 2018
5ae59d1
udpate rbuildignore
schloerke Dec 18, 2018
37c7f23
add download script for swagger-ui
schloerke Dec 18, 2018
646eae0
add [email protected]
schloerke Dec 18, 2018
93e700b
commit working code before gutting v2 spec
schloerke Dec 18, 2018
4115aa3
comment unused code
schloerke Dec 18, 2018
7594a17
use new swagger wrapper function
schloerke Dec 18, 2018
62cff5b
use relative swagger.json url
schloerke Dec 18, 2018
7fc90ba
relative url back one dir
schloerke Dec 18, 2018
1796d8e
try basic server info for cloud dev
schloerke Dec 18, 2018
828effa
use dynamic relative url given window.location information
schloerke Dec 18, 2018
d83a533
schemes should not be in globals
schloerke Dec 18, 2018
0d3affe
use swagger pkg
schloerke Dec 18, 2018
ab6a11f
clean up swagger creation
schloerke Dec 18, 2018
94bad55
answer TODO about trailing slashes
schloerke Dec 18, 2018
08f834b
use swagger::swagger_spec
schloerke Dec 18, 2018
1bb36dd
have swagger params be done with a list and not a data.frame
schloerke Dec 19, 2018
5b6e038
do not allow asJSON to be passed into pr$swaggerFile()
schloerke Dec 19, 2018
8a6ed82
swagger endpoints will use list information and not clone r6 objects …
schloerke Dec 19, 2018
6b619d4
recursively remove NA and NULL values from swagger output
schloerke Dec 19, 2018
dc9b2c0
is.na(list()) returns NA. it should return FALSE
schloerke Dec 19, 2018
533caef
move param type to schema
schloerke Jan 3, 2019
7d87801
add test to check swagger against a cli validator
schloerke Jan 3, 2019
9e782ca
add notes on how to install node.js pkg
schloerke Jan 3, 2019
3a129f2
Merge pull request #364 from trestletech/swagger_3_params
schloerke Jan 4, 2019
4924983
remove ... from swaggerFile/openAPIFile
schloerke Jan 4, 2019
b4112ec
use swagger master v3.20.3.9999
schloerke Jan 4, 2019
02ebd37
add a manual testing file for manual testing before release
schloerke Jan 4, 2019
e1205a9
add an index route to the static only plumber example
schloerke Jan 8, 2019
f7ea16d
update example ot use addGlobalProcessor instead of registerHook
schloerke Jan 8, 2019
052b47b
use `spec` and not `sf`for param name to swagger file fn
schloerke Jan 8, 2019
8d85aca
add expect_silent (or comment) test for tests that have no tests
schloerke Jan 8, 2019
5936636
run validation on all inst/examples for plumber
schloerke Jan 8, 2019
8dce0f7
make sure basic http protocol is supplied to rstudio swaggerCallback
schloerke Jan 9, 2019
8eab635
add news item and split to look like shiny's
schloerke Jan 11, 2019
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
18 changes: 11 additions & 7 deletions .Rbuildignore
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
^appveyor\.yml$
^.*\.Rproj$
^\.Rproj\.user$
.travis.yml
Dockerfile
inst/analog-keys.R
inst/examples/03-github/github-key.txt
.httr-oauth
docs
scripts
^\.travis\.yml$
^Dockerfile
^inst/analog-keys.R
^inst/examples/03-github/github-key.txt
^\.httr-oauth
^docs
^scripts
^revdep$
^cran-comments\.md$
^inst/swagger_ui/.*\.map
^yarn\.lock$
^node_modules
^package\.json$
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@
.idea
plumber.Rcheck
plumber_*.tar.gz
yarn.lock
node_modules/*
5 changes: 4 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ Suggests:
base64enc,
htmlwidgets,
visNetwork,
analogsea
analogsea,
swagger (> 3.20.3)
Remotes:
rstudio/swagger
Collate:
'content-types.R'
'cookie-parser.R'
Expand Down
44 changes: 36 additions & 8 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,47 @@
plumber 0.4.7
plumber 0.5.0
--------------------------------------------------------------------------------
* Add support for swagger for mounted routers (@bradleyhd, [#274](https://github.com/trestletech/plumber/issues/274)).
* BUGFIX: A multiline POST body is now collapsed to a single line ([#270](https://github.com/trestletech/plumber/issues/270)).
* The source files used in plumber **must use** the UTF-8 encoding if they contain
non-ASCII characters (@shrektan, [#312](https://github.com/trestletech/plumber/pull/312),
[#328](https://github.com/trestletech/plumber/pull/328)).
* SECURITY: Wrap `jsonlite::fromJSON` to ensure that `jsonlite` never reads
## Full changelog

### Security

* Wrap `jsonlite::fromJSON` to ensure that `jsonlite` never reads
input as a remote address (such as a file path or URL) and attempts to parse
that. The only known way to exploit this behavior in plumber unless an
API were using encrypted cookies and an attacker knew the encryption key in
order to craft arbitrary cookies. ([#325](https://github.com/trestletech/plumber/pull/325))
* BUGFIX: Plumber files are now only evaluated once. Prior plumber behavior sourced endpoint

### Breaking changes

* Plumber's swagger definition is now defined using
[OpenAPI 3](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md),
upgrading from Swagger Specification. ([#365](https://github.com/trestletech/plumber/pull/365))

* The source files used in plumber **must use** the UTF-8 encoding if they contain
non-ASCII characters (@shrektan, [#312](https://github.com/trestletech/plumber/pull/312),
[#328](https://github.com/trestletech/plumber/pull/328)).

### New features

* Added support to a router's run method to allow the `swagger` parameter to be a function that
enhances the existing swagger specification before being returned to `/openapi.json`. ([#365](https://github.com/trestletech/plumber/pull/365))

* Add support for swagger for mounted routers (@bradleyhd, [#274](https://github.com/trestletech/plumber/issues/274)).


### Minor new features and improvements

* Changed Swagger UI to use [swagger](https://github.com/rstudio/swagger) R package to display the
swagger page. ([#365](https://github.com/trestletech/plumber/pull/365))

* Plumber files are now only evaluated once. Prior plumber behavior sourced endpoint
functions twice and non-endpoint code blocks once.
([#328](https://github.com/trestletech/plumber/pull/328/commits/cde0d3d2543a654fd0c5799b670767ccb0e22e35))

### Bug fixes

* A multiline POST body is now collapsed to a single line (@robertdj, [#270](https://github.com/trestletech/plumber/issues/270) [#297](https://github.com/trestletech/plumber/pull/297)).



plumber 0.4.6
--------------------------------------------------------------------------------
Expand Down
14 changes: 5 additions & 9 deletions R/parse-globals.R
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,11 @@ parseGlobals <- function(lines){
fields
}

#' The default set of Swagger API globals. Some of these properties are subject
#' to being overridden by @api* annotations.
#' The default set of OpenAPI specification (OAS) globals. Some of these properties
#' are subject to being overridden by @api* annotations.
#' @noRd
defaultGlobals <- list(
swagger = "2.0",
info = list(description="API Description", title="API Title", version="1.0.0"),
host=NA,
schemes= I("http"),
produces=I("application/json")
#securityDefinitions = list(),
#definitions = list()
openapi = "3.0.2",
info = list(description = "API Description", title = "API Title", version = "1.0.0"),
paths = list()
)
168 changes: 99 additions & 69 deletions R/plumber.R
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ plumber <- R6Class(
private$serializer <- serializer_json()
private$errorHandler <- defaultErrorHandler()
private$notFoundHandler <- default404Handler

# Add in the initial filters
for (fn in names(filters)){
fil <- PlumberFilter$new(fn, filters[[fn]], private$envir, private$serializer, NULL)
Expand All @@ -195,8 +195,13 @@ plumber <- R6Class(
}

},
run = function(host='127.0.0.1', port=getOption('plumber.port'), swagger=interactive(),
debug=interactive(), swaggerCallback=getOption('plumber.swagger.url', NULL)){
run = function(
host = '127.0.0.1',
port = getOption('plumber.port'),
swagger = interactive(),
debug = interactive(),
swaggerCallback = getOption('plumber.swagger.url', NULL)
) {
port <- findPort(port)

message("Starting server to listen on port ", port)
Expand All @@ -211,54 +216,76 @@ plumber <- R6Class(
setwd(dirname(private$filename))
}

if (swagger){
sf <- self$swaggerFile()

if (is.na(sf$host)){
accessHost <- ifelse(host == "0.0.0.0", "127.0.0.1", host)
accessPath <- paste(accessHost, port, sep=":")
sf$host <- accessPath

if (!is.null(getOption("plumber.apiHost"))){
sf$host <- getOption("plumber.apiHost")
}

if (!is.null(getOption("plumber.apiScheme"))){
sf$schemes <- getOption("plumber.apiScheme")
}

if (!is.null(getOption("plumber.apiPath"))){
sf$basePath <- getOption("plumber.apiPath")
}
}
if (isTRUE(swagger) || is.function(swagger)) {
host <- getOption(
"plumber.apiHost",
ifelse(identical(host, "0.0.0.0"), "127.0.0.1", host)
)
spec <- self$swaggerFile()

# Create a function that's hardcoded to return the swaggerfile -- regardless of env.
fun <- function(schemes, host, path){
if (!missing(schemes)){
sf$schemes <- I(schemes)
}

if (!missing(host)){
sf$host <- host
swagger_fun <- function(req, res, ..., scheme = "deprecated", host = "deprecated", path = "deprecated") {
if (!missing(scheme) || !missing(host) || !missing(path)) {
warning("`scheme`, `host`, or `path` are not supported to produce swagger.json")
}
# allows swagger-ui to provide proper callback location given the referrer location
# ex: rstudio cloud
# use the HTTP_REFERER so RSC can find the swagger location to ask
## (can't directly ask for 127.0.0.1)
referrer_url <- req$HTTP_REFERER
referrer_url <- sub("index\\.html$", "", referrer_url)
referrer_url <- sub("__swagger__/$", "", referrer_url)
spec$servers <- list(
list(
url = referrer_url,
description = "OpenAPI"
)
)

if (!missing(path)){
sf$basePath <- path
if (is.function(swagger)) {
# allow users to update the swagger file themselves
ret <- swagger(self, spec, ...)
# Since users could have added more NA or NULL values...
ret <- removeNaOrNulls(ret)
} else {
# NA/NULL values already removed
ret <- spec
}
sf
ret
}
# https://swagger.io/specification/#document-structure
# "It is RECOMMENDED that the root OpenAPI document be named: openapi.json or openapi.yaml."
self$handle("GET", "/openapi.json", swagger_fun, serializer = serializer_unboxed_json())
# keeping for legacy purposes
self$handle("GET", "/swagger.json", swagger_fun, serializer = serializer_unboxed_json())

swagger_index <- function(...) {
swagger::swagger_spec(
'window.location.origin + window.location.pathname.replace(/\\(__swagger__\\\\/|__swagger__\\\\/index.html\\)$/, "") + "openapi.json"'
)
}
for (path in c("/__swagger__/index.html", "/__swagger__/")) {
self$handle(
"GET", path, swagger_index,
serializer = serializer_html()
)
}
self$mount("/__swagger__", PlumberStatic$new(swagger::swagger_path()))
swaggerUrl = paste0(host, ":", port, "/__swagger__/")
if (!grepl("^http://", swaggerUrl)) {
# must have http protocol for use within RStudio
# does not work if supplying "127.0.0.1:1234/route"
swaggerUrl <- paste0("http://", swaggerUrl)
}
self$handle("GET", "/swagger.json", fun, serializer=serializer_unboxed_json())
message("Running the swagger UI at ", swaggerUrl, sep = "")

plumberFileServer <- PlumberStatic$new(system.file("swagger-ui", package = "plumber"))
self$mount("/__swagger__", plumberFileServer)
swaggerUrl = paste(sf$schemes[1], "://", sf$host, "/__swagger__/", sep="")
message("Running the swagger UI at ", swaggerUrl, sep="")
if (!is.null(swaggerCallback) && is.function(swaggerCallback)){
# notify swaggerCallback of plumber swagger location
if (!is.null(swaggerCallback) && is.function(swaggerCallback)) {
swaggerCallback(swaggerUrl)
}
}

on.exit(private$runHooks("exit"), add=TRUE)
on.exit(private$runHooks("exit"), add = TRUE)

httpuv::runServer(host, port, self)
},
Expand Down Expand Up @@ -527,17 +554,24 @@ plumber <- R6Class(
filter <- PlumberFilter$new(name, expr, private$envir, serializer)
private$addFilterInternal(filter)
},
swaggerFile = function(){ #FIXME: test
swaggerFile = function() { #FIXME: test

endpoints <- private$swaggerFileWalkMountsInternal(self)
endpoints <- prepareSwaggerEndpoints(endpoints)
swaggerPaths <- private$swaggerFileWalkMountsInternal(self)

# Extend the previously parsed settings with the endpoints
def <- modifyList(private$globalSettings, list(paths=endpoints))
def <- modifyList(private$globalSettings, list(paths = swaggerPaths))

# Lay those over the default globals so we ensure that the required fields
# (like API version) are satisfied.
modifyList(defaultGlobals, def)
ret <- modifyList(defaultGlobals, def)

# remove NA or NULL values, which swagger doesn't like
ret <- removeNaOrNulls(ret)

ret
},
openAPIFile = function() {
self$swaggerFile()
},

### Legacy/Deprecated
Expand Down Expand Up @@ -686,36 +720,32 @@ plumber <- R6Class(
paste(x, y, sep = "/")
}

endpoints <- lapply(router$endpoints, function(endpoint) {
# clone and make path a full path
endpointEntries <- lapply(endpoint, function(endpointEntry) {
endpointEntry <- endpointEntry$clone()
endpointEntry$path <- join_paths(parentPath, endpointEntry$path)
endpointEntry
})
# make sure to use the full path
endpointList <- list()

endpointEntries
})
for (endpoint in router$endpoints) {
for (endpointEntry in endpoint) {
swaggerEndpoint <- prepareSwaggerEndpoint(
endpointEntry,
join_paths(parentPath, endpointEntry$path)
)
endpointList <- modifyList(endpointList, swaggerEndpoint)
}
}

# recursively gather mounted enpoint entries
mountedEndpoints <- mapply(
names(router$mounts),
router$mounts,
FUN = function(mountPath, mountedSubrouter) {
private$swaggerFileWalkMountsInternal(
mountedSubrouter,
if (length(router$mounts) > 0) {
for (mountPath in names(router$mounts)) {
mountEndpoints <- private$swaggerFileWalkMountsInternal(
router$mounts[[mountPath]],
join_paths(parentPath, mountPath)
)
endpointList <- modifyList(endpointList, mountEndpoints)
}
)

# returning a single list of entries,
# not nested entries using the filter / `__no-preempt__` as names within the list
# (the filter name is not required when making swagger docs and do not want to misrepresent the endpoints)
unname(append(
unlist(endpoints),
unlist(mountedEndpoints)
))
}

# returning a single list of swagger entries
endpointList
}
)
)
Loading