diff --git a/.Rbuildignore b/.Rbuildignore index 89edc0b59..5aa579196 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -25,3 +25,4 @@ rsconnect ^CRAN-RELEASE$ ^legacy_support.txt$ +^codecov\.yml$ diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 967314ffe..b515fa9da 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -1,125 +1,21 @@ +# Workflow derived from https://github.com/rstudio/shiny-workflows +# +# NOTE: This Shiny team GHA workflow is overkill for most R packages. +# For most R packages it is better to use https://github.com/r-lib/actions on: push: - branches: - - master + branches: [main, rc-**] pull_request: - branches: - - master + branches: [main] schedule: - - cron: '0 0 1,11,21 * *' # every 10 days (GHA cache invalidates after 7) + - cron: '0 7 * * 1' # every monday -name: R-CMD-check +name: Package checks jobs: + website: + uses: rstudio/shiny-workflows/.github/workflows/website.yaml@v1 + routine: + uses: rstudio/shiny-workflows/.github/workflows/routine.yaml@v1 R-CMD-check: - runs-on: ${{ matrix.config.os }} - - name: ${{ matrix.config.os }} (${{ matrix.config.r }}) - - strategy: - fail-fast: false - matrix: - config: - - {os: macOS-latest, r: 'devel'} - - {os: macOS-latest, r: 'release'} - - {os: macOS-latest, r: 'oldrel'} - - - {os: windows-latest, r: 'devel'} - - {os: windows-latest, r: 'release'} - # - {os: windows-latest, r: 'oldrel'} # do not test due to binaries not being created - - # check a lot of ubuntu combinations as that's where plumber is hosted - - {os: ubuntu-18.04, r: 'release', rspm: "https://packagemanager.rstudio.com/cran/__linux__/bionic/latest"} - - {os: ubuntu-18.04, r: 'oldrel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/bionic/latest"} - - {os: ubuntu-18.04, r: '3.5', rspm: "https://packagemanager.rstudio.com/cran/__linux__/bionic/latest"} - - {os: ubuntu-18.04, r: '3.4', rspm: "https://packagemanager.rstudio.com/cran/__linux__/bionic/latest"} - - # would use RSPM for ubuntu 16, but sodium has issues finding libsodium - - {os: ubuntu-16.04, r: 'release'} - - {os: ubuntu-16.04, r: 'oldrel'} - - {os: ubuntu-16.04, r: '3.5'} - - {os: ubuntu-16.04, r: '3.4'} - - env: - R_REMOTES_NO_ERRORS_FROM_WARNINGS: true - RSPM: ${{ matrix.config.rspm }} - GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - - steps: - - uses: actions/checkout@v2 - - - name: Install Node.js - uses: actions/setup-node@v1 - with: - node-version: '12.x' - - - uses: r-lib/actions/setup-r@master - id: install-r - with: - r-version: ${{ matrix.config.r }} - - - uses: r-lib/actions/setup-pandoc@master - - - name: Install pak and query dependencies - shell: Rscript {0} - run: | - install.packages("pak", repos = "https://r-lib.github.io/p/pak/dev/") - saveRDS(pak::pkg_deps_tree("local::.", dependencies = TRUE), ".github/r-depends.rds") - - name: Cache R packages - uses: actions/cache@v2 - with: - path: ${{ env.R_LIBS_USER }} - key: ${{ matrix.config.os }}-${{ steps.install-r.outputs.installed-r-version }}-1-${{ hashFiles('.github/r-depends.rds') }} - restore-keys: ${{ matrix.config.os }}-${{ steps.install-r.outputs.installed-r-version }}-1- - - - name: Install system dependencies - if: runner.os == 'Linux' - shell: Rscript {0} - run: | - pak::local_system_requirements(execute = TRUE) - - # Install Cairo system dependencies - # used for svg testing - - name: Mac systemdeps - if: runner.os == 'macOS' - run: | - brew install --cask xquartz - brew install cairo - - - name: macOS oldrel Rcpp - if: runner.os == 'macOS' && matrix.config.r == 'oldrel' - shell: Rscript {0} - run: | - install.packages("Rcpp", type = "source") - - - name: Install dependencies - shell: Rscript {0} - run: | - pak::local_install_dev_deps(upgrade = TRUE) - pak::pkg_install("rcmdcheck") - pak::pkg_install("sessioninfo") - - - name: Session info - run: | - options(width = 100) - pkgs <- installed.packages()[, "Package"] - sessioninfo::session_info(pkgs, include_base = TRUE) - shell: Rscript {0} - - - name: Check - env: - _R_CHECK_CRAN_INCOMING_: false - run: rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check") - shell: Rscript {0} - - - name: Show testthat output - if: always() - run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true - shell: bash - - - name: Upload check results - if: failure() - uses: actions/upload-artifact@master - with: - name: ${{ matrix.config.os }}-r${{ matrix.config.r }}-results - path: check + uses: rstudio/shiny-workflows/.github/workflows/R-CMD-check.yaml@v1 diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index ea14c01f3..1c673ac4b 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -3,7 +3,8 @@ name: Docker on: push: branches: - - "master" + - "main" + - "rc-v**" - "docker**" schedule: - cron: '0 0 1 * *' # first of every month @@ -36,11 +37,11 @@ jobs: - name: GitHub # 'next' tag signifies the _next_ release tags: "next" - ref: "master" + ref: "main" steps: - - uses: actions/checkout@master + - uses: actions/checkout@v2 # https://github.com/marketplace/actions/publish-docker - name: rstudio/plumber diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml deleted file mode 100644 index 9abd08b53..000000000 --- a/.github/workflows/pkgdown.yaml +++ /dev/null @@ -1,83 +0,0 @@ -on: - push: - branches: master - pull_request: - branches: master - -name: pkgdown - -jobs: - pkgdown: - runs-on: ${{ matrix.config.os }} - strategy: - fail-fast: false - matrix: - config: - - {os: ubuntu-18.04, r: 'release', rspm: "https://packagemanager.rstudio.com/cran/__linux__/bionic/latest"} - env: - RSPM: ${{ matrix.config.rspm }} - GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v2 - - - uses: r-lib/actions/setup-r@master - id: install-r - with: - r-version: ${{ matrix.config.r }} - - - uses: r-lib/actions/setup-pandoc@master - - - name: Install pak and query dependencies - shell: Rscript {0} - run: | - install.packages("pak", repos = "https://r-lib.github.io/p/pak/dev/") - saveRDS(pak::pkg_deps_tree("local::.", dependencies = TRUE), ".github/r-depends.rds") - - name: Cache R packages - uses: actions/cache@v2 - with: - path: ${{ env.R_LIBS_USER }} - key: ${{ matrix.config.os }}-${{ steps.install-r.outputs.installed-r-version }}-1-pkgdown-${{ hashFiles('.github/r-depends.rds') }} - restore-keys: | - ${{ matrix.config.os }}-${{ steps.install-r.outputs.installed-r-version }}-1-pkgdown- - ${{ matrix.config.os }}-${{ steps.install-r.outputs.installed-r-version }}-1- - - - name: Install system dependencies - if: runner.os == 'Linux' - shell: Rscript {0} - run: | - pak::local_system_requirements(execute = TRUE) - pak::pkg_system_requirements("pkgdown", execute = TRUE) - - - name: Install dependencies - shell: Rscript {0} - run: | - pak::local_install_dev_deps(upgrade = TRUE) - pak::pkg_install("pkgdown") - - - name: Install package - run: R CMD INSTALL . - - - name: Build Site (PR) - if: github.event_name != 'push' - shell: Rscript {0} - run: | - pkgdown::build_site(new_process = FALSE) - # Must validate after. Otherwise files are saved and `pkgdown::build_site()` gets mad - - name: Validate all topics exist (PR) - if: github.event_name != 'push' - shell: Rscript {0} - run: | - pkgdown::build_reference_index() - stopifnot(length(warnings()) == 0) - - - name: Git Config - if: github.event_name == 'push' - run: | - git config --local user.email "${GITHUB_ACTOR}@users.noreply.github.com" && \ - git config --local user.name "${GITHUB_ACTOR}" - - - name: Build and Deploy Site - if: github.event_name == 'push' - shell: Rscript {0} - run: | - pkgdown::deploy_to_branch(new_process = FALSE) diff --git a/.github/workflows/pr-commands.yaml b/.github/workflows/pr-commands.yaml deleted file mode 100644 index 08ec01dca..000000000 --- a/.github/workflows/pr-commands.yaml +++ /dev/null @@ -1,57 +0,0 @@ -on: - issue_comment: - types: [created] -name: Commands -jobs: - # added so that the workflow doesn't fail. - always_runner: - runs-on: ubuntu-latest - steps: - - name: Always run - run: echo "This job is used to prevent the workflow status from showing as failed when all other jobs are skipped" - document: - if: startsWith(github.event.comment.body, '/document') - name: document - runs-on: macOS-latest - env: - GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v2 - - uses: r-lib/actions/pr-fetch@master - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - - uses: r-lib/actions/setup-r@master - - name: Install dependencies - run: Rscript -e 'install.packages(c("remotes", "roxygen2"))' -e 'remotes::install_deps(dependencies = TRUE)' - - name: Document - run: Rscript -e 'roxygen2::roxygenise()' - - name: commit - run: | - git add man/\* NAMESPACE - git commit -m 'Document' - - uses: r-lib/actions/pr-push@master - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - # style: - # if: startsWith(github.event.comment.body, '/style') - # name: style - # runs-on: macOS-latest - # env: - # GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - # steps: - # - uses: actions/checkout@v2 - # - uses: r-lib/actions/pr-fetch@master - # with: - # repo-token: ${{ secrets.GITHUB_TOKEN }} - # - uses: r-lib/actions/setup-r@master - # - name: Install dependencies - # run: Rscript -e 'install.packages("styler")' - # - name: Style - # run: Rscript -e 'styler::style_pkg()' - # - name: commit - # run: | - # git add \*.R - # git commit -m 'Style' - # - uses: r-lib/actions/pr-push@master - # with: - # repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml deleted file mode 100644 index 9dbaa27c7..000000000 --- a/.github/workflows/test-coverage.yaml +++ /dev/null @@ -1,61 +0,0 @@ -on: - push: - branches: - - master - pull_request: - branches: - - master - -name: test-coverage - -jobs: - test-coverage: - runs-on: ${{ matrix.config.os }} - strategy: - fail-fast: false - matrix: - config: - - {os: ubuntu-18.04, r: 'release', rspm: "https://packagemanager.rstudio.com/cran/__linux__/bionic/latest"} - env: - RSPM: ${{ matrix.config.rspm }} - GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v2 - - - uses: r-lib/actions/setup-r@master - id: install-r - with: - r-version: ${{ matrix.config.r }} - - - uses: r-lib/actions/setup-pandoc@master - - - name: Install pak and query dependencies - shell: Rscript {0} - run: | - install.packages("pak", repos = "https://r-lib.github.io/p/pak/dev/") - saveRDS(pak::pkg_deps_tree("local::.", dependencies = TRUE), ".github/r-depends.rds") - - name: Cache R packages - uses: actions/cache@v2 - with: - path: ${{ env.R_LIBS_USER }} - key: ${{ matrix.config.os }}-${{ steps.install-r.outputs.installed-r-version }}-1-testing-${{ hashFiles('.github/r-depends.rds') }} - restore-keys: | - ${{ matrix.config.os }}-${{ steps.install-r.outputs.installed-r-version }}-1-testing- - ${{ matrix.config.os }}-${{ steps.install-r.outputs.installed-r-version }}-1- - - - name: Install system dependencies - if: runner.os == 'Linux' - shell: Rscript {0} - run: | - pak::local_system_requirements(execute = TRUE) - pak::pkg_system_requirements("pkgdown", execute = TRUE) - - - name: Install dependencies - shell: Rscript {0} - run: | - pak::local_install_dev_deps(upgrade = TRUE) - pak::pkg_install("covr") - - - name: Test coverage - run: covr::codecov() - shell: Rscript {0} diff --git a/DESCRIPTION b/DESCRIPTION index 496353eb2..0bf2745da 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -2,10 +2,10 @@ Encoding: UTF-8 Package: plumber Type: Package Title: An API Generator for R -Version: 1.0.0.90002 +Version: 1.1.0.9000 Roxygen: list(markdown = TRUE) Authors@R: c( - person("Barret", "Schloerke", role = c("cre", "aut"), email = "barret@rstudio.com"), + person("Barret", "Schloerke", role = c("cre", "aut"), email = "barret@rstudio.com", comment = c(ORCID = "0000-0001-9986-114X")), person("Jeff", "Allen", role = c("aut", "ccp"), email = "cran@trestletech.com"), person("Bruno", "Tremblay", role = "ctb", email = "cran@neoxone.com"), person("Frans", "van Dunné", role = "ctb", email = "frans@ixpantia.com"), @@ -24,7 +24,7 @@ Imports: stringi (>= 0.3.0), jsonlite (>= 0.9.16), webutils (>= 1.1), - httpuv (>= 1.5.0), + httpuv (>= 1.5.5), crayon, promises (>= 1.1.0), sodium, @@ -34,7 +34,6 @@ Imports: lifecycle (>= 0.2.0), ellipsis (>= 0.3.0), rlang -LazyData: TRUE ByteCompile: TRUE Suggests: testthat (>= 0.11.0), @@ -45,12 +44,15 @@ Suggests: later, readr, yaml, - feather, + arrow, future, coro, rstudioapi, - mockery (>= 0.4.2) -RoxygenNote: 7.1.1 + spelling, + mockery (>= 0.4.2), + geojsonsf, + sf +RoxygenNote: 7.2.0 Collate: 'async.R' 'content-types.R' @@ -90,3 +92,6 @@ Collate: 'validate_api_spec.R' 'zzz.R' RdMacros: lifecycle +Language: en-US +Config/Needs/check: Cairo +Config/Needs/website: tidyverse/tidytemplate diff --git a/Dockerfile b/Dockerfile index 8fa4f85d7..572068ec8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,7 @@ RUN install2.r remotes ## https://stackoverflow.com/a/55621942/591574 #ADD https://github.com/rstudio/plumber/commits/ _docker_cache -ARG PLUMBER_REF=master +ARG PLUMBER_REF=main RUN Rscript -e "remotes::install_github('rstudio/plumber@${PLUMBER_REF}')" EXPOSE 8000 diff --git a/NAMESPACE b/NAMESPACE index fa6fd8477..fbef9ad95 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -28,10 +28,12 @@ export(options_plumber) export(parser_csv) export(parser_feather) export(parser_form) +export(parser_geojson) export(parser_json) export(parser_multi) export(parser_none) export(parser_octet) +export(parser_parquet) export(parser_rds) export(parser_read_file) export(parser_text) @@ -76,11 +78,13 @@ export(serializer_csv) export(serializer_device) export(serializer_feather) export(serializer_format) +export(serializer_geojson) export(serializer_headers) export(serializer_html) export(serializer_htmlwidget) export(serializer_jpeg) export(serializer_json) +export(serializer_parquet) export(serializer_pdf) export(serializer_png) export(serializer_print) diff --git a/NEWS.md b/NEWS.md index f04c937c6..8d9594cf0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,7 +1,26 @@ -plumber 1.0.0.9999 Development version --------------------------------------------------------------------------------- +# plumber (development version) -### Breaking changes +## Breaking changes + +## New features + +* Introduces new GeoJSON serializer and parser. GeoJSON objects are parsed into `sf` objects and `sf` or `sfc` objects will be serialized into GeoJSON. (@josiahparry, #830) +* Update feather serializer to use the arrow package. The new default feather MIME type is `application/vnd.apache.arrow.file`. (@pachadotdev #849) +* Add parquet serializer and parser by using the arrow package (@pachadotdev #849) + +## Bug fixes + +* OpenAPI response type detection had a scoping issue. Use serializer defined `Content-Type` header instead. (@meztez, #789) + +* The default shared secret filter returns error responses without throwing an error. (#808) + +* Remove response bodies (and therefore the Content-Length header) for status codes which forbid it under the HTTP specification (e.g. 1xx, 204, 304). (@atheriel #758, @meztez #760) + +* Decode path URI before attempting to serve static assets (@meztez #754). + +# plumber 1.1.0 + +## Breaking changes * Force json serialization of endpoint error responses instead of using endpoint serializer. (@meztez, #689) @@ -9,21 +28,19 @@ plumber 1.0.0.9999 Development version * `options_plumber()` now requires that all options are named. If no option name is provided, an error with be thrown. (#746) -### New features +## New features * Added option `plumber.trailingSlash`. This option (which is **disabled** by default) allows routes to be redirected to route definitions with a trailing slash. For example, if a `GET` request is submitted to `/test?a=1` with no `/test` route is defined, but a `GET` `/test/` route definition does exist, then the original request will respond with a `307` to reattempt against `GET` `/test/?a=1`. This option will be _enabled_ by default in a future release. This logic executed for before calling the `404` handler. (#746) -* Added option `plumber.methodNotFound`. This option (which is enabled by default) allows for a status of `405` to be returned if an invalid method is used when requesting a valid route. This logic executed for before calling the default `404` handler. (#746) - -* Guess OpenApi response content type from serializer. (@meztez #684) +* Added an experimental option `plumber.methodNotAllowed`. This option (which is enabled by default) allows for a status of `405` to be returned if an invalid method is used when requesting a valid route. This logic executed for before calling the default `404` handler. (#746) * Passing `edit = TRUE` to `plumb_api()` will open the API source file. (#699) -* Allow for spaces in `@apiTag` and `@tag` when tag is surrended by single or double quotes. (#685) - * OpenAPI Specification can be set using a file path. (@meztez #696) -* Un-deprecated `Plumber$run(debug=, swaggerCallback=)` and added the parameters for `Plumber$run(docs=, quiet=)` and `pr_run(debug=, docs=, swaggerCallback=, quiet=)`. Now, all four parameters will not produce lingering effects on the `Plumber` router. (@jcheng5 #765) +* Guess OpenAPI response content type from serializer. (@meztez #684) + +* Undeprecated `Plumber$run(debug=, swaggerCallback=)` and added the parameters for `Plumber$run(docs=, quiet=)` and `pr_run(debug=, docs=, swaggerCallback=, quiet=)`. Now, all four parameters will not produce lingering effects on the `Plumber` router. (@jcheng5 #765) * Setting `quiet = TRUE` will suppress routine startup messages. * Setting `debug = TRUE`, will display information when an error occurs. See `pr_set_debug()`. * Setting `docs` will update the visual documentation. See `pr_set_docs()`. @@ -33,19 +50,21 @@ plumber 1.0.0.9999 Development version * `PlumberStep` (and `PlumberEndpoint` and `PlumberFilter`) received a new field `$srcref` and method `$getFunc()`. `$srcref` will contain the corresponding `srcref` information from original source file. `$getFunc()` will return the evaluated function. (#782) -### Bug fixes - -* Fixed bug where `httpuv` would return a status of `500` with body `An exception occurred` if no headers were set on the response object. (#745) +* Allow for spaces in `@apiTag` and `@tag` when tag is surrounded by single or double quotes. (#685) -* Fixed bug where all `pr_*()` returned invisibly. Now all `pr_*()` methods will print the router if displayed in the console. (#740) +## Bug fixes -* Ignore regular comments in block parsing (@meztez #718) +* Ignore regular comments in block parsing. (@meztez #718) * Block parsing comments, tags and responses ordering match plumber api ordering. (#722) +* Fixed bug where `httpuv` would return a status of `500` with body `An exception occurred` if no headers were set on the response object. (#745) + +* Fixed bug where all `pr_*()` returned invisibly. Now all `pr_*()` methods will print the router if displayed in the console. (#740) + * When calling `Plumber$handle()` and defining a new `PlumberEndpoint`, `...` will be checked for invalid names. (@meztez, #677) -* `/__swagger__/` now always redirect to `/__docs__/`, even when Swagger isn't the selected interface. Use `options(plumber.legacyRedirects = FALSE)` to disable this behavior. (@blairj09 #694) +* `/__swagger__/` now always redirects to `/__docs__/`, even when Swagger isn't the selected interface. Use `options(plumber.legacyRedirects = FALSE)` to disable this behavior. (@blairj09 #694) * Fixed `available_apis()` bug where all packages printed all available APIs. (@meztez #708) @@ -57,12 +76,11 @@ plumber 1.0.0.9999 Development version * Setting options `plumber.docs.callback` to `NULL` will also set deprecated but supported option `plumber.swagger.url`. (#766) -plumber 1.0.0 --------------------------------------------------------------------------------- +# plumber 1.0.0 -### New features +## New features -#### Plumber router +### Plumber router * Added support for promises in endpoints, filters, and hooks. This allows for multi-core execution when paired with `future`. See `plumb_api("plumber", "13-promises")` and `plumb_api("plumber", "14-future")` for example implementations. (#248) * Added a Tidy API for more natural usage with magrittr's `%>%`. For example, a plumber object can now be initiated and run with `pr() %>% pr_run(port = 8080)`. For more examples, see [here](https://www.rplumber.io/articles/programmatic-usage.html) (@blairj09, #590) @@ -78,7 +96,7 @@ plumber 1.0.0 * Unnamed elements that are added to `req$args` by filters or creating `req$argsBody` will no longer throw an error. They will only be passed through via `...` (#666) -#### OpenAPI +### OpenAPI * API Documentation is now hosted at `/__docs__`. If `swagger` documentation is being used, `/__swagger__` will redirect to `/__docs__`. (#654) @@ -88,7 +106,7 @@ plumber 1.0.0 * Added `validate_api_spec()` to validate a Plumber API produces a valid OpenAPI Specification. (Experimental!) (#633) -#### Serializers +### Serializers * Added `as_attachment(value, filename)` method which allows routes to return a file attachment with a custom name. (#585) @@ -109,7 +127,7 @@ plumber 1.0.0 * `serializer_headers(header_list)`: Method which sets a list of static headers for each serialized value. Heavily inspired from @ycphs (#455). (#585) * `serializer_write_file()`: Method which wraps `serializer_content_type()`, but orchestrates creating, writing serialized content to, reading from, and removing a temp file. (#660) -#### Body parsing +### Body parsing * Added support for request body parsing (@meztez, #532) @@ -132,7 +150,7 @@ plumber 1.0.0 * If `multipart/*` content is parsed, `req$body` will contain named output from `webutils::parse_multipart()` and add the parsed value to each part. Look here for access to all provided information (e.g., `name`, `filename`, `content_type`, etc). In addition, `req$argsBody` (which is used for route argument matching) will contain a named reduced form of this information where `parsed` values (and `filename`s) are combined on the same `name`. (#663) -#### Visual Documentation +### Visual Documentation * Generalize user interface integration. Plumber can now use other OpenAPI compatible user interfaces like `RapiDoc` (https://github.com/mrin9/RapiDoc) and `Redoc` (https://github.com/Redocly/redoc). Pending CRAN approbations, development R packages are available from https://github.com/meztez/rapidoc/ and https://github.com/meztez/redoc/. (@meztez, #562) @@ -140,7 +158,7 @@ plumber 1.0.0 * Added support for swagger for mounted routers (@bradleyhd, #274). -### Security improvements +## Security improvements * Secret session cookies are now encrypted using `sodium`. All prior `req$session` information will be lost. @@ -157,7 +175,7 @@ plumber 1.0.0 API were using encrypted cookies and an attacker knew the encryption key in order to craft arbitrary cookies. (#325) -### Breaking changes +## Breaking changes * When `plumb()`ing a file (or `Plumber$new(file)`), the working directory is set to the file's directory before parsing the file. When running the Plumber API, the working directory will be set to file's directory before running.(#631) @@ -181,7 +199,7 @@ plumber 1.0.0 * When creating a `PlumberFilter` or `PlumberEndpoint`, an error will be thrown if `expr` does not evaluate to a function. (#666) -### Deprecations +## Deprecations * Shorthand serializers are now deprecated. `@html`, `@json`, `@png`, `@jpeg`, `@svg` should be replaced with the `@serializer` syntax. Ex: `@serializer html` or `@serializer jpeg` (#630) @@ -196,7 +214,7 @@ plumber 1.0.0 * DigitalOcean helper functions are now defunct (`do_*()`). The functionality and documentation on how to deploy to DigitalOcean has been moved to [`plumberDeploy`](https://github.com/meztez/plumberDeploy) (by @meztez) (#649) -### Minor new features and improvements +## Minor new features and improvements * Documentation is updated and now presented using `pkgdown` (#570) @@ -234,7 +252,7 @@ plumber 1.0.0 * Endpoints that produce images within a `promises::promise()` will now use the expected graphics device. (#669) -### Bug fixes +## Bug fixes * Handle plus signs in URI as space characters instead of actual plus signs (@meztez, #618) @@ -259,8 +277,8 @@ plumber 1.0.0 -plumber 0.4.6 --------------------------------------------------------------------------------- +# plumber 0.4.6 + * BUGFIX: Hooks that accept a `value` argument (`postroute`, `preserialize`, and `postserialize`) now modify the incoming value as documented. * BUGFIX: The `postserialize` hook is now given the serialized data as its @@ -272,8 +290,8 @@ plumber 0.4.6 * Add [RStudio Project Template](https://rstudio.github.io/rstudio-extensions/rstudio_project_templates.html) to package. -plumber 0.4.4 --------------------------------------------------------------------------------- +# plumber 0.4.4 + * Support Expiration, HTTPOnly, and Secure flags on cookies (#87). **EDIT**: see #216 which prevented expiration from working. @@ -307,15 +325,15 @@ plumber 0.4.4 * Support `.` in string path segments -plumber 0.4.2 --------------------------------------------------------------------------------- +# plumber 0.4.2 + * Development version for 0.4.2. Will be working to move to even/odd release cycles, but I had prematurely bumped to 0.4.0 so that one might get skipped, making the next CRAN release 0.4.2. -plumber 0.4.0 --------------------------------------------------------------------------------- +# plumber 0.4.0 + * BREAKING: Listen on localhost instead of listening publicly by default. * BREAKING: We no longer set the `Access-Control-Allow-Origin` HTTP header to `*`. This was previously done for convenience but we've decided to prioritize @@ -358,14 +376,14 @@ plumber 0.4.0 order to get more insight into your API errors. -plumber 0.3.3 --------------------------------------------------------------------------------- +# plumber 0.3.3 + * `plumb()` now accepts an argument `dir`, referring to a directory containing `plumber.R`, which may be provided instead of `file`. -plumber 0.3.2 --------------------------------------------------------------------------------- +# plumber 0.3.2 + * Introduced the `do_provision()`, `do_deploy_api()`, `do_remove_api()` and `do_configure_https()` functions to provision and manage your APIs on a cloud server running on DigitalOcean. @@ -378,14 +396,14 @@ plumber 0.3.2 * Don't convert `+` character in a query string to a space. -plumber 0.3.1 --------------------------------------------------------------------------------- +# plumber 0.3.1 + * Add a method to consume JSON on post (you can still send a query string in the body of a POST request as well). -plumber 0.3.0 --------------------------------------------------------------------------------- +# plumber 0.3.0 + * BREAKING CHANGE: serializer factories are now registered instead of the serializer themselves. Thus, `addSerializer()` now expects a function that returns a serializer, and `Response$new()` now expects a serializer itself @@ -397,8 +415,8 @@ plumber 0.3.0 `09-content-type`. -plumber 0.2.4 --------------------------------------------------------------------------------- +# plumber 0.2.4 + * Add a filter which parses and sets req$cookies to be a list corresponding to the cookies provided with the request. * Responses can set multiple cookies @@ -406,15 +424,15 @@ plumber 0.2.4 encoding. -plumber 0.2.3 --------------------------------------------------------------------------------- +# plumber 0.2.3 + * Set options(warn=1) during execution of user code so that warnings are immediately visible in the console, rather than storing them until the server is stopped. -plumber 0.2.2 --------------------------------------------------------------------------------- +# plumber 0.2.2 + * Add `sessionCookie` function to define a processor that can be used as a globalProcessor on a router to encrypt values from req$session and store them as an encrypted cookie in on the user's browser. @@ -426,14 +444,14 @@ plumber 0.2.2 * Document all public params so CHECK passes -plumber 0.2.1 --------------------------------------------------------------------------------- -* Add more Roxygen documentation for exported functions +# plumber 0.2.1 + +* Add more `roxygen2` documentation for exported functions * Remove the warning in the README as the API seems to be stabilizing. -plumber 0.2.0 --------------------------------------------------------------------------------- +# plumber 0.2.0 + * BREAKING: Changed variable-path routing to use bracketed format instead of just a colon. * BREAKING: Renamed `PlumberRouter` R6 object to just `Plumber`. @@ -441,6 +459,6 @@ plumber 0.2.0 * Added support for the `#*` prefix. -plumber 0.1.0 --------------------------------------------------------------------------------- +# plumber 0.1.0 + * Initial Release diff --git a/R/content-types.R b/R/content-types.R index c60d8087f..91d1fc7a6 100644 --- a/R/content-types.R +++ b/R/content-types.R @@ -42,7 +42,8 @@ knownContentTypes <- c( dotx = "application/vnd.openxmlformats-officedocument.wordprocessingml.template", xlam = "application/vnd.ms-excel.addin.macroEnabled.12", xlsb = "application/vnd.ms-excel.sheet.binary.macroEnabled.12", - feather = "application/feather", + feather = "application/vnd.apache.arrow.file", + parquet = "application/vnd.apache.parquet", rds = "application/rds", tsv = "application/tab-separated-values", csv = "application/csv", diff --git a/R/openapi-spec.R b/R/openapi-spec.R index 7dce8466f..8b4a635a4 100644 --- a/R/openapi-spec.R +++ b/R/openapi-spec.R @@ -63,14 +63,9 @@ responsesSpecification <- function(endpts){ if (!length(resps[[resp]]$content)) { ctype <- NULL if (is.function(endpts$serializer)) { - ctype <- formals(endpts$serializer)$type - if (is.null(ctype)) { - ctype <- tryCatch({ - get("type", envir = - environment(get("serialize_fn", envir = - environment(endpts$serializer))))}, - error = function(e) {NULL}) - } + # Must safe-guard against partial name matching + # since we are reaching into the function env + ctype <- environment(endpts$serializer)[["headers"]][["Content-Type"]] } if (isTRUE(nchar(ctype) > 0)) { ctype <- stri_split_regex(ctype, "[ ;]")[[1]][1] @@ -291,7 +286,7 @@ getArgsMetadata <- function(endpoint_func) { error = function(cond) {NA}) } # Check that it is possible to transform arg value into - # an example for the openAPI spec. Valid transform are + # an example for the OpenAPI spec. Valid transform are # either a logical, a numeric, a character or a list that # is json serializable. Otherwise set to NA. if (!is.logical(arg) && !is.numeric(arg) && !is.character(arg) diff --git a/R/openapi-types.R b/R/openapi-types.R index 45a558e9f..e498d59bd 100644 --- a/R/openapi-types.R +++ b/R/openapi-types.R @@ -72,7 +72,7 @@ add_api_info_onLoad <- function() { ) addApiInfo( "object", - c("list", "data.frame", "df"), + c("list", "data.frame", "df", "object"), location = "requestBody" ) addApiInfo( diff --git a/R/options_plumber.R b/R/options_plumber.R index a08b5f396..94f4ff5a4 100644 --- a/R/options_plumber.R +++ b/R/options_plumber.R @@ -15,7 +15,8 @@ #' 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`}{Logical value which allows the router to notify that an +#' \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`.} diff --git a/R/parse-body.R b/R/parse-body.R index ae5f7b456..6ca067d86 100644 --- a/R/parse-body.R +++ b/R/parse-body.R @@ -365,7 +365,7 @@ make_parser <- function(aliases) { #' @examples #' \dontrun{ #' # Overwrite `text/json` parsing behavior to not allow JSON vectors to be simplified -#' #* @parser json simplifyVector = FALSE +#' #* @parser json list(simplifyVector = FALSE) #' # Activate `rds` parser in a multipart request #' #* @parser multi #' #* @parser rds @@ -386,6 +386,16 @@ parser_json <- function(...) { }) } +#' @describeIn parsers GeoJSON parser. See [geojsonsf::geojson_sf()] for more details. +#' @export +parser_geojson <- function(...) { + if (!requireNamespace("geojsonsf", quietly = TRUE)) { + stop("`geojsonsf` must be installed for `parser_geojson` to work") + } + parser_text(function(val) { + geojsonsf::geojson_sf(val, ...) + }) +} #' @describeIn parsers Helper parser to parse plain text #' @param parse_fn function to further decode a text string into an object @@ -470,18 +480,27 @@ parser_rds <- function(...) { }) } -#' @describeIn parsers feather parser. See [feather::read_feather()] for more details. +#' @describeIn parsers feather parser. See [arrow::read_feather()] for more details. #' @export parser_feather <- function(...) { parser_read_file(function(tmpfile) { - if (!requireNamespace("feather", quietly = TRUE)) { - stop("`feather` must be installed for `parser_feather` to work") + if (!requireNamespace("arrow", quietly = TRUE)) { + stop("`arrow` must be installed for `parser_feather` to work") } - feather::read_feather(tmpfile, ...) + arrow::read_feather(tmpfile, ...) }) } - +#' @describeIn parsers parquet parser. See [arrow::read_parquet()] for more details. +#' @export +parser_parquet <- function(...) { + parser_read_file(function(tmpfile) { + if (!requireNamespace("arrow", quietly = TRUE)) { + stop("`arrow` must be installed for `parser_parquet` to work") + } + arrow::read_parquet(tmpfile, ...) + }) +} #' @describeIn parsers Octet stream parser. Returns the raw content. #' @export @@ -558,12 +577,14 @@ register_parsers_onLoad <- function() { register_parser("octet", parser_octet, fixed = "application/octet-stream") register_parser("form", parser_form, fixed = "application/x-www-form-urlencoded") register_parser("rds", parser_rds, fixed = "application/rds") - register_parser("feather", parser_feather, fixed = "application/feather") + register_parser("feather", parser_feather, fixed = c("application/vnd.apache.arrow.file", "application/feather")) + register_parser("parquet", parser_parquet, fixed = "application/vnd.apache.parquet") register_parser("text", parser_text, fixed = "text/plain", regex = "^text/") register_parser("tsv", parser_tsv, fixed = c("application/tab-separated-values", "text/tab-separated-values")) # yaml types: https://stackoverflow.com/a/38000954/591574 register_parser("yaml", parser_yaml, fixed = c("text/vnd.yaml", "application/yaml", "application/x-yaml", "text/yaml", "text/x-yaml")) register_parser("none", parser_none, regex = "*") + register_parser("geojson", parser_geojson, fixed = c("application/geo+json", "application/vdn.geo+json")) parser_all <- function() { stop("This function should never be called. It should be handled by `make_parser('all')`") diff --git a/R/plumber-response.R b/R/plumber-response.R index 37a382ce6..973ce202e 100644 --- a/R/plumber-response.R +++ b/R/plumber-response.R @@ -18,15 +18,16 @@ PlumberResponse <- R6Class( h <- self$headers body <- self$body - if (is.null(body)){ - body <- "" - } - charset <- get_character_set(h$HTTP_CONTENT_TYPE) if (is.character(body)) { + charset <- get_character_set(h$HTTP_CONTENT_TYPE) Encoding(body) <- charset } + if (self$status %in% c(100:199, 204, 304)) { + body <- NULL + } + list( status = self$status, headers = h, diff --git a/R/plumber-static.R b/R/plumber-static.R index d50ceccf8..c038e8060 100644 --- a/R/plumber-static.R +++ b/R/plumber-static.R @@ -59,6 +59,7 @@ PlumberStatic <- R6Class( path <- '/index.html' } + path <- httpuv::decodeURIComponent(path) abs.path <- resolve_path(direc, path) if (is.null(abs.path)){ # TODO: Should this be inherited from a parent router? diff --git a/R/plumber-step.R b/R/plumber-step.R index bf442523c..a94fa8319 100644 --- a/R/plumber-step.R +++ b/R/plumber-step.R @@ -166,7 +166,7 @@ getRelevantArgs <- function(args, func) { #' Plumber Endpoint #' -#' Defines a terminal handler in a PLumber router. +#' Defines a terminal handler in a Plumber router. #' #' @export PlumberEndpoint <- R6Class( diff --git a/R/plumber.R b/R/plumber.R index d4dfebd3a..03cefd730 100644 --- a/R/plumber.R +++ b/R/plumber.R @@ -1017,7 +1017,7 @@ Plumber <- R6Class( } private$api_spec_handler <- api_fun }, - #' @description Retrieve openAPI file + #' @description Retrieve OpenAPI file getApiSpec = function() { #FIXME: test routerSpec <- private$routerSpecificationInternal(self) @@ -1093,12 +1093,12 @@ Plumber <- R6Class( warning("addGlobalProcessor has been deprecated in v0.4.0 and will be removed in a coming release. Please use `registerHook`(s) instead.") self$registerHooks(proc) }, - #' @description Deprecated. Retrieve openAPI file + #' @description Deprecated. Retrieve OpenAPI file openAPIFile = function() { warning("`$openAPIFile()` has been deprecated in v1.0.0 and will be removed in a coming release. Please use `$getApiSpec()`.") self$getApiSpec() }, - #' @description Deprecated. Retrieve openAPI file + #' @description Deprecated. Retrieve OpenAPI file swaggerFile = function() { warning("`$swaggerFile()` has been deprecated in v1.0.0 and will be removed in a coming release. Please use `$getApiSpec()`.") self$getApiSpec() diff --git a/R/serializer.R b/R/serializer.R index 5ac4ac8a2..76ef759c8 100644 --- a/R/serializer.R +++ b/R/serializer.R @@ -231,6 +231,18 @@ serializer_unboxed_json <- function(auto_unbox = TRUE, ..., type = "application/ serializer_json(auto_unbox = auto_unbox, ..., type = type) } +#' @describeIn serializers GeoJSON serializer. See also [geojsonsf::sf_geojson()] and [[geojsonsf::sfc_geojson()]]. +#' @export +serializer_geojson <- function(..., type = "application/geo+json") { + if (!requireNamespace("geojsonsf", quietly = TRUE)) { + stop("`geojsonsf` must be installed for `serializer_geojson` to work") + } + serializer_content_type(type, function(val) { + if (inherits(val, "sfc")) return(geojsonsf::sfc_geojson(val, ...)) + if (inherits(val, "sf")) return(geojsonsf::sf_geojson(val, ...)) + stop("Did not receive an `sf` or `sfc` object. ") + }) +} @@ -251,17 +263,32 @@ serializer_rds <- function(version = "2", ascii = FALSE, ..., type = "applicatio }) } -#' @describeIn serializers feather serializer. See also: [feather::write_feather()] +#' @describeIn serializers feather serializer. See also: [arrow::write_feather()] #' @export -serializer_feather <- function(type = "application/feather") { - if (!requireNamespace("feather", quietly = TRUE)) { - stop("`feather` must be installed for `serializer_feather` to work") +serializer_feather <- function(type = "application/vnd.apache.arrow.file") { + if (!requireNamespace("arrow", quietly = TRUE)) { + stop("`arrow` must be installed for `serializer_feather` to work") } serializer_write_file( fileext = ".feather", type = type, write_fn = function(val, tmpfile) { - feather::write_feather(val, tmpfile) + arrow::write_feather(val, tmpfile) + } + ) +} + +#' @describeIn serializers parquet serializer. See also: [arrow::write_parquet()] +#' @export +serializer_parquet <- function(type = "application/vnd.apache.parquet") { + if (!requireNamespace("arrow", quietly = TRUE)) { + stop("`arrow` must be installed for `serializer_parquet` to work") + } + serializer_write_file( + fileext = ".parquet", + type = type, + write_fn = function(val, tmpfile) { + arrow::write_parquet(val, tmpfile) } ) } @@ -602,7 +629,9 @@ add_serializers_onLoad <- function() { register_serializer("csv", serializer_csv) register_serializer("tsv", serializer_tsv) register_serializer("feather", serializer_feather) + register_serializer("parquet", serializer_parquet) register_serializer("yaml", serializer_yaml) + register_serializer("geojson", serializer_geojson) # text register_serializer("text", serializer_text) diff --git a/R/shared-secret-filter.R b/R/shared-secret-filter.R index fe8266268..0e508f2b4 100644 --- a/R/shared-secret-filter.R +++ b/R/shared-secret-filter.R @@ -5,7 +5,16 @@ sharedSecretFilter <- function(req, res){ supplied <- req$HTTP_PLUMBER_SHARED_SECRET if (!identical(supplied, secret)){ res$status <- 400 - stop("The provided shared secret did not match expected secret.") + # Force the route to return as unboxed json + res$serializer <- serializer_unboxed_json() + # Using output similar to `defaultErrorHandler()` + li <- list(error = "400 - Bad request") + + # Don't overly leak data unless they opt-in + if (is.function(req$pr$getDebug) && isTRUE(req$pr$getDebug())) { + li$message <- "Shared secret mismatch" + } + return(li) } } diff --git a/R/ui.R b/R/ui.R index b4c779d6b..f6bcf11ee 100644 --- a/R/ui.R +++ b/R/ui.R @@ -21,7 +21,7 @@ mount_docs <- function(pr, host, port, docs_info, callback, quiet = FALSE) { ) ) - # Mount openAPI spec paths openapi.json + # Mount OpenAPI spec paths openapi.json mount_openapi(pr, api_url) # Mount Docs @@ -71,7 +71,7 @@ unmount_docs <- function(pr, docs_info) { return() } - # Unount openAPI spec paths openapi.json + # Unount OpenAPI spec paths openapi.json unmount_openapi(pr) # Mount Docs diff --git a/README.md b/README.md index b4b120cfb..bb36762e1 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ -# `plumber` +# plumber -[![R build status](https://github.com/rstudio/plumber/workflows/R-CMD-check/badge.svg)](https://github.com/rstudio/plumber/actions) +[![R build status](https://github.com/rstudio/plumber/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/rstudio/plumber/actions) [![](https://www.r-pkg.org/badges/version/plumber)](https://www.r-pkg.org/pkg/plumber) [![CRAN RStudio mirror downloads](https://cranlogs.r-pkg.org/badges/plumber?color=brightgreen)](https://www.r-pkg.org/pkg/plumber) -[![codecov](https://codecov.io/gh/rstudio/plumber/branch/master/graph/badge.svg)](https://codecov.io/gh/rstudio/plumber) -[![RStudio community](https://img.shields.io/badge/community-plumber-blue?style=social&logo=rstudio&logoColor=75AADB)](https://community.rstudio.com/tags/plumber) +[![codecov](https://codecov.io/gh/rstudio/plumber/branch/main/graph/badge.svg)](https://codecov.io/gh/rstudio/plumber) +[![RStudio community](https://img.shields.io/badge/community-plumber-blue?style=social&logo=rstudio&logoColor=75AADB)](https://community.rstudio.com/tag/plumber) Plumber allows you to create a web API by merely decorating your existing R -source code with special comments. Take a look at an example. +source code with `roxygen2`-like comments. Take a look at an example. ```r # plumber.R @@ -40,7 +40,7 @@ function(a, b) { These comments allow `plumber` to make your R functions available as API endpoints. You can use either `#*` as the prefix or `#'`, but we recommend the -former since `#'` will collide with Roxygen. +former since `#'` will collide with `roxygen2`. ```r library(plumber) @@ -95,9 +95,9 @@ remotes::install_github("rstudio/plumber") library(plumber) ``` -## Cheatsheet +## Cheat Sheet - + ## Hosting diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..04c558599 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,14 @@ +comment: false + +coverage: + status: + project: + default: + target: auto + threshold: 1% + informational: true + patch: + default: + target: auto + threshold: 1% + informational: true diff --git a/cran-comments.md b/cran-comments.md index 1521fdd6b..b3bf50434 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -1,87 +1,63 @@ ## Comments -#### 2020-9-14 +#### 2020-3-23 -I've fixed the urls and resubmitting. +Bug fixes and new features. -Best, -Barret +CRAN checks: +* I have disabled the brittle test that is currently failing on https://www.r-project.org/nosvn/R.check/r-devel-windows-x86_64-gcc10-UCRT/plumber-00check.html . Checking this test on windows GHA only. +* I can not see why this installation is failing: https://www.r-project.org/nosvn/R.check/r-release-windows-ix86+x86_64/plumber-00check.html -#### 2020-9-14 +Please let me know if there is anything else I can provide. -Flavor: r-devel-linux-x86_64-debian-gcc -Check: CRAN incoming feasibility, Result: NOTE - Maintainer: 'Barret Schloerke ' +Thank you, +Barret - New maintainer: - Barret Schloerke - Old maintainer(s): - Jeff Allen - Found the following (possibly) invalid URLs: - URL: https://rstudio.github.io/plumber (moved to https://www.rplumber.io/) - From: README.md - Status: 200 - Message: OK - URL: https://www.rstudio.com/products/connect/ (moved to https://rstudio.com/products/connect/) - From: README.md - Status: 200 - Message: OK +#### 2020-1-5 -#### 2020-9-14 +These checks have naturally resolved. -This is a major version update. +- Barret -Please let me know if there is anything else I can provide. -Thank you, -Barret +#### 2020-12-13 -#### 2020-9-14 +Dear maintainer, -Confirmed! You may change the maintainer. +Please see the problems shown on +. -- Jeff +Please correct before 2021-01-08 to safely retain your package on CRAN. -#### 2020-9-14 - -Hi Jeff, +Best, +-k -I'm emailing to have a formal request to change the maintainer in `plumber` to Barret Schloerke... -My I change `plumber`'s maintainer to Barret Schloerke? - -Thank you, -Barret ## Test environments -* local macOS, R 4.0.0 +* local macOS, R 4.0.2 * GitHub Actions * macOS * oldrel, release, devel * windows - * oldrel, release, devel + * release, devel * ubuntu18 * 3.4, 3.5, oldrel, release, devel * ubuntu16 * 3.4, 3.5, oldrel, release, devel +* devtools:: + * check_win_devel() + * check_win_release() + * check_win_oldrelease() 0 errors ✔ | 0 warnings ✔ | 0 notes ✔ -* devtools::build_win() - * checking CRAN incoming feasibility ... NOTE - Maintainer: 'Barret Schloerke ' - - New maintainer: - Barret Schloerke - Old maintainer(s): - Jeff Allen -Status: 1 NOTE ## revdepcheck results -We checked 8 reverse dependencies, comparing R CMD check results across CRAN and dev versions of this package. +We checked 13 reverse dependencies, comparing R CMD check results across CRAN and dev versions of this package. * We saw 0 new problems * We failed to check 0 packages diff --git a/inst/WORDLIST b/inst/WORDLIST new file mode 100644 index 000000000..7af2d1994 --- /dev/null +++ b/inst/WORDLIST @@ -0,0 +1,143 @@ +API's +Api +BMP +BUGFIX +CLI +CORS +CentOS +Crypto +DDoS +DNS +Deprecations +DigitalOcean +DoS +Dockerfile +Dockerfiles +DuckDuckGo +GeoJSON +GlobalEnv +HAProxy +HSTS +HTTPOnly +HTTPS +Hookable +IPs +IPv +JSON +MacOS +Mailgun +Nginx +OpenAPI +OpenCPU +Packrat +Parsers +Phusion +PlumberEndpoint's +Quickstart +README +RedHat +RedShift +Rscript +SSL +SameSite +Sanitization +Serializer +Serializers +TLS +TSV +UI +URI +Unencrypted +Unmount +WebSocket +Webhook +Webhooks +XSS +YAML +addAssets +addEndpoint +api +apis +autogenerated +balancer +cloneable +codecov +config +contentType +crypto +cryptographically +css +decrypt +decrypted +df +dir +dockercloud +entrypoint +expr +filesystem +ggplot +globalProcessor +haproxy +htmlwidget +http +httpuv +httr +imposter +integrations +interpretable +javascript +js +js's +json +kB +keyring +lexicographically +libsodium +lifecycle +magrittr's +mailgun +multiprocess +natively +nginx +npm +onHeaders +onWSOpen +param +params +parsers +performant +postBody +postserialize +pre +proc +programmatically +pseudorandom +pwd +randomPort +renderers +repo +req +retransmitted +rstudio +runnable +scalability +scalable +serializer +serializers +setCookie +setosa +ssl +subfolder +subrouters +systemd +transactional +unmount +unparsed +unsecure +untrusted +validator +webhook +webhook’s + +ing +Undeprecated diff --git a/man/Hookable.Rd b/man/Hookable.Rd index 37e225403..ee1622bdf 100644 --- a/man/Hookable.Rd +++ b/man/Hookable.Rd @@ -12,14 +12,14 @@ Hookable \section{Methods}{ \subsection{Public methods}{ \itemize{ -\item \href{#method-registerHook}{\code{Hookable$registerHook()}} -\item \href{#method-registerHooks}{\code{Hookable$registerHooks()}} -\item \href{#method-clone}{\code{Hookable$clone()}} +\item \href{#method-Hookable-registerHook}{\code{Hookable$registerHook()}} +\item \href{#method-Hookable-registerHooks}{\code{Hookable$registerHooks()}} +\item \href{#method-Hookable-clone}{\code{Hookable$clone()}} } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-registerHook}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Hookable-registerHook}{}}} \subsection{Method \code{registerHook()}}{ Register a hook on a router \subsection{Usage}{ @@ -37,8 +37,8 @@ Register a hook on a router } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-registerHooks}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Hookable-registerHooks}{}}} \subsection{Method \code{registerHooks()}}{ Register hooks on a router \subsection{Usage}{ @@ -54,8 +54,8 @@ Register hooks on a router } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-clone}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Hookable-clone}{}}} \subsection{Method \code{clone()}}{ The objects of this class are cloneable with this method. \subsection{Usage}{ diff --git a/man/Plumber.Rd b/man/Plumber.Rd index f1a1bf705..70a14009c 100644 --- a/man/Plumber.Rd +++ b/man/Plumber.Rd @@ -141,49 +141,49 @@ pr$setErrorHandler(function(req, res, err) { \section{Methods}{ \subsection{Public methods}{ \itemize{ -\item \href{#method-new}{\code{Plumber$new()}} -\item \href{#method-run}{\code{Plumber$run()}} -\item \href{#method-mount}{\code{Plumber$mount()}} -\item \href{#method-unmount}{\code{Plumber$unmount()}} -\item \href{#method-registerHook}{\code{Plumber$registerHook()}} -\item \href{#method-handle}{\code{Plumber$handle()}} -\item \href{#method-removeHandle}{\code{Plumber$removeHandle()}} -\item \href{#method-print}{\code{Plumber$print()}} -\item \href{#method-serve}{\code{Plumber$serve()}} -\item \href{#method-route}{\code{Plumber$route()}} -\item \href{#method-call}{\code{Plumber$call()}} -\item \href{#method-onHeaders}{\code{Plumber$onHeaders()}} -\item \href{#method-onWSOpen}{\code{Plumber$onWSOpen()}} -\item \href{#method-setSerializer}{\code{Plumber$setSerializer()}} -\item \href{#method-setParsers}{\code{Plumber$setParsers()}} -\item \href{#method-set404Handler}{\code{Plumber$set404Handler()}} -\item \href{#method-setErrorHandler}{\code{Plumber$setErrorHandler()}} -\item \href{#method-setDocs}{\code{Plumber$setDocs()}} -\item \href{#method-setDocsCallback}{\code{Plumber$setDocsCallback()}} -\item \href{#method-setDebug}{\code{Plumber$setDebug()}} -\item \href{#method-getDebug}{\code{Plumber$getDebug()}} -\item \href{#method-filter}{\code{Plumber$filter()}} -\item \href{#method-setApiSpec}{\code{Plumber$setApiSpec()}} -\item \href{#method-getApiSpec}{\code{Plumber$getApiSpec()}} -\item \href{#method-addEndpoint}{\code{Plumber$addEndpoint()}} -\item \href{#method-addAssets}{\code{Plumber$addAssets()}} -\item \href{#method-addFilter}{\code{Plumber$addFilter()}} -\item \href{#method-addGlobalProcessor}{\code{Plumber$addGlobalProcessor()}} -\item \href{#method-openAPIFile}{\code{Plumber$openAPIFile()}} -\item \href{#method-swaggerFile}{\code{Plumber$swaggerFile()}} -\item \href{#method-clone}{\code{Plumber$clone()}} -} -} -\if{html}{ -\out{
Inherited methods} -\itemize{ -\item \out{}\href{../../plumber/html/Hookable.html#method-registerHooks}{\code{plumber::Hookable$registerHooks()}}\out{} -} -\out{
} -} +\item \href{#method-Plumber-new}{\code{Plumber$new()}} +\item \href{#method-Plumber-run}{\code{Plumber$run()}} +\item \href{#method-Plumber-mount}{\code{Plumber$mount()}} +\item \href{#method-Plumber-unmount}{\code{Plumber$unmount()}} +\item \href{#method-Plumber-registerHook}{\code{Plumber$registerHook()}} +\item \href{#method-Plumber-handle}{\code{Plumber$handle()}} +\item \href{#method-Plumber-removeHandle}{\code{Plumber$removeHandle()}} +\item \href{#method-Plumber-print}{\code{Plumber$print()}} +\item \href{#method-Plumber-serve}{\code{Plumber$serve()}} +\item \href{#method-Plumber-route}{\code{Plumber$route()}} +\item \href{#method-Plumber-call}{\code{Plumber$call()}} +\item \href{#method-Plumber-onHeaders}{\code{Plumber$onHeaders()}} +\item \href{#method-Plumber-onWSOpen}{\code{Plumber$onWSOpen()}} +\item \href{#method-Plumber-setSerializer}{\code{Plumber$setSerializer()}} +\item \href{#method-Plumber-setParsers}{\code{Plumber$setParsers()}} +\item \href{#method-Plumber-set404Handler}{\code{Plumber$set404Handler()}} +\item \href{#method-Plumber-setErrorHandler}{\code{Plumber$setErrorHandler()}} +\item \href{#method-Plumber-setDocs}{\code{Plumber$setDocs()}} +\item \href{#method-Plumber-setDocsCallback}{\code{Plumber$setDocsCallback()}} +\item \href{#method-Plumber-setDebug}{\code{Plumber$setDebug()}} +\item \href{#method-Plumber-getDebug}{\code{Plumber$getDebug()}} +\item \href{#method-Plumber-filter}{\code{Plumber$filter()}} +\item \href{#method-Plumber-setApiSpec}{\code{Plumber$setApiSpec()}} +\item \href{#method-Plumber-getApiSpec}{\code{Plumber$getApiSpec()}} +\item \href{#method-Plumber-addEndpoint}{\code{Plumber$addEndpoint()}} +\item \href{#method-Plumber-addAssets}{\code{Plumber$addAssets()}} +\item \href{#method-Plumber-addFilter}{\code{Plumber$addFilter()}} +\item \href{#method-Plumber-addGlobalProcessor}{\code{Plumber$addGlobalProcessor()}} +\item \href{#method-Plumber-openAPIFile}{\code{Plumber$openAPIFile()}} +\item \href{#method-Plumber-swaggerFile}{\code{Plumber$swaggerFile()}} +\item \href{#method-Plumber-clone}{\code{Plumber$clone()}} +} +} +\if{html}{\out{ +
Inherited methods + +
+}} \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-new}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-new}{}}} \subsection{Method \code{new()}}{ Create a new \code{Plumber} router @@ -208,8 +208,8 @@ A new \code{Plumber} router } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-run}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-run}{}}} \subsection{Method \code{run()}}{ Start a server using \code{Plumber} object. @@ -262,8 +262,8 @@ For more customization, see \verb{$setDocs()} or \code{\link[=pr_set_docs]{pr_se } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-mount}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-mount}{}}} \subsection{Method \code{mount()}}{ Mount a Plumber router @@ -303,8 +303,8 @@ root$mount("/products", products) } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-unmount}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-unmount}{}}} \subsection{Method \code{unmount()}}{ Unmount a Plumber router \subsection{Usage}{ @@ -320,8 +320,8 @@ Unmount a Plumber router } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-registerHook}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-registerHook}{}}} \subsection{Method \code{registerHook()}}{ Register a hook @@ -400,8 +400,8 @@ pr$handle("GET", "/", function(){ 123 }) } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-handle}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-handle}{}}} \subsection{Method \code{handle()}}{ Define endpoints @@ -462,8 +462,8 @@ pr$handle("GET", "/", function(){ } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-removeHandle}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-removeHandle}{}}} \subsection{Method \code{removeHandle()}}{ Remove endpoints \subsection{Usage}{ @@ -483,8 +483,8 @@ Remove endpoints } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-print}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-print}{}}} \subsection{Method \code{print()}}{ Print representation of plumber router. \subsection{Usage}{ @@ -508,8 +508,8 @@ A terminal friendly representation of a plumber router. } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-serve}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-serve}{}}} \subsection{Method \code{serve()}}{ Serve a request \subsection{Usage}{ @@ -527,8 +527,8 @@ Serve a request } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-route}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-route}{}}} \subsection{Method \code{route()}}{ Route a request \subsection{Usage}{ @@ -546,8 +546,8 @@ Route a request } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-call}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-call}{}}} \subsection{Method \code{call()}}{ \pkg{httpuv} interface call function. (Required for \pkg{httpuv}) \subsection{Usage}{ @@ -563,8 +563,8 @@ Route a request } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-onHeaders}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-onHeaders}{}}} \subsection{Method \code{onHeaders()}}{ httpuv interface onHeaders function. (Required for \pkg{httpuv}) \subsection{Usage}{ @@ -580,8 +580,8 @@ httpuv interface onHeaders function. (Required for \pkg{httpuv}) } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-onWSOpen}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-onWSOpen}{}}} \subsection{Method \code{onWSOpen()}}{ httpuv interface onWSOpen function. (Required for \pkg{httpuv}) \subsection{Usage}{ @@ -597,8 +597,8 @@ httpuv interface onWSOpen function. (Required for \pkg{httpuv}) } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-setSerializer}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-setSerializer}{}}} \subsection{Method \code{setSerializer()}}{ Sets the default serializer of the router. @@ -627,8 +627,8 @@ pr$setSerializer(serializer_unboxed_json()) } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-setParsers}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-setParsers}{}}} \subsection{Method \code{setParsers()}}{ Sets the default parsers of the router. Initialized to \code{c("json", "form", "text", "octet", "multi")} \subsection{Usage}{ @@ -649,7 +649,9 @@ Sets the default parsers of the router. Initialized to \code{c("json", "form", " If the parser name \code{"all"} is found in any character value or list name, all remaining parsers will be added. When using a list, parser information already defined will maintain their existing argument values. All remaining parsers will use their default arguments. -Example:\preformatted{# provide a character string +Example: + +\if{html}{\out{
}}\preformatted{# provide a character string parsers = "json" # provide a named list with no arguments @@ -660,14 +662,14 @@ parsers = list(json = list(simplifyVector = FALSE), rds = list()) # default plumber parsers parsers = c("json", "form", "text", "octet", "multi") -}} +}\if{html}{\out{
}}} } \if{html}{\out{}} } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-set404Handler}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-set404Handler}{}}} \subsection{Method \code{set404Handler()}}{ Sets the handler that gets called if an incoming request can’t be served by any filter, endpoint, or sub-router. @@ -697,8 +699,8 @@ pr$set404Handler(function(req, res) {cat(req$PATH_INFO)}) } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-setErrorHandler}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-setErrorHandler}{}}} \subsection{Method \code{setErrorHandler()}}{ Sets the error handler which gets invoked if any filter or endpoint generates an error. @@ -731,8 +733,8 @@ pr$setErrorHandler(function(req, res, err) { } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-setDocs}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-setDocs}{}}} \subsection{Method \code{setDocs()}}{ Set visual documentation to use for API @@ -753,8 +755,8 @@ If using \code{\link[=options_plumber]{options_plumber()}}, the value must be se } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-setDocsCallback}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-setDocsCallback}{}}} \subsection{Method \code{setDocsCallback()}}{ Set a callback to notify where the API's visual documentation is located. @@ -777,8 +779,8 @@ See also: \code{\link[=pr_set_docs_callback]{pr_set_docs_callback()}} } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-setDebug}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-setDebug}{}}} \subsection{Method \code{setDebug()}}{ Set debug value to include error messages. @@ -796,8 +798,8 @@ See also: \verb{$getDebug()} and \code{\link[=pr_set_debug]{pr_set_debug()}} } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-getDebug}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-getDebug}{}}} \subsection{Method \code{getDebug()}}{ Retrieve the \code{debug} value. If it has never been set, the result of \code{interactive()} will be used. @@ -808,8 +810,8 @@ See also: \verb{$getDebug()} and \code{\link[=pr_set_debug]{pr_set_debug()}} } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-filter}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-filter}{}}} \subsection{Method \code{filter()}}{ Add a filter to plumber router @@ -831,8 +833,8 @@ See also: \code{\link[=pr_filter]{pr_filter()}} } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-setApiSpec}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-setApiSpec}{}}} \subsection{Method \code{setApiSpec()}}{ Allows to modify router autogenerated OpenAPI Specification @@ -860,18 +862,18 @@ The value returned will not be validated for OAS compatibility.} } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-getApiSpec}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-getApiSpec}{}}} \subsection{Method \code{getApiSpec()}}{ -Retrieve openAPI file +Retrieve OpenAPI file \subsection{Usage}{ \if{html}{\out{
}}\preformatted{Plumber$getApiSpec()}\if{html}{\out{
}} } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-addEndpoint}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-addEndpoint}{}}} \subsection{Method \code{addEndpoint()}}{ addEndpoint has been deprecated in v0.4.0 and will be removed in a coming release. Please use \code{handle()} instead. \subsection{Usage}{ @@ -910,8 +912,8 @@ addEndpoint has been deprecated in v0.4.0 and will be removed in a coming releas } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-addAssets}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-addAssets}{}}} \subsection{Method \code{addAssets()}}{ addAssets has been deprecated in v0.4.0 and will be removed in a coming release. Please use \code{mount} and \code{PlumberStatic$new()} instead. \subsection{Usage}{ @@ -931,8 +933,8 @@ addAssets has been deprecated in v0.4.0 and will be removed in a coming release. } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-addFilter}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-addFilter}{}}} \subsection{Method \code{addFilter()}}{ \verb{$addFilter()} has been deprecated in v0.4.0 and will be removed in a coming release. Please use \verb{$filter()} instead. \subsection{Usage}{ @@ -954,8 +956,8 @@ addAssets has been deprecated in v0.4.0 and will be removed in a coming release. } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-addGlobalProcessor}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-addGlobalProcessor}{}}} \subsection{Method \code{addGlobalProcessor()}}{ \verb{$addGlobalProcessor()} has been deprecated in v0.4.0 and will be removed in a coming release. Please use \verb{$registerHook}(s) instead. \subsection{Usage}{ @@ -971,28 +973,28 @@ addAssets has been deprecated in v0.4.0 and will be removed in a coming release. } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-openAPIFile}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-openAPIFile}{}}} \subsection{Method \code{openAPIFile()}}{ -Deprecated. Retrieve openAPI file +Deprecated. Retrieve OpenAPI file \subsection{Usage}{ \if{html}{\out{
}}\preformatted{Plumber$openAPIFile()}\if{html}{\out{
}} } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-swaggerFile}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-swaggerFile}{}}} \subsection{Method \code{swaggerFile()}}{ -Deprecated. Retrieve openAPI file +Deprecated. Retrieve OpenAPI file \subsection{Usage}{ \if{html}{\out{
}}\preformatted{Plumber$swaggerFile()}\if{html}{\out{
}} } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-clone}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-Plumber-clone}{}}} \subsection{Method \code{clone()}}{ The objects of this class are cloneable with this method. \subsection{Usage}{ diff --git a/man/PlumberEndpoint.Rd b/man/PlumberEndpoint.Rd index 0bbaeda4f..10d3ead9b 100644 --- a/man/PlumberEndpoint.Rd +++ b/man/PlumberEndpoint.Rd @@ -9,7 +9,7 @@ Plumber Endpoint Plumber Endpoint } \details{ -Defines a terminal handler in a PLumber router. +Defines a terminal handler in a Plumber router. Parameters values are obtained from parsing blocks of lines in a plumber file. They can also be provided manually for historical reasons. @@ -41,30 +41,30 @@ each separate verb/path into its own endpoint, so we just do that.} \section{Methods}{ \subsection{Public methods}{ \itemize{ -\item \href{#method-getTypedParams}{\code{PlumberEndpoint$getTypedParams()}} -\item \href{#method-canServe}{\code{PlumberEndpoint$canServe()}} -\item \href{#method-matchesPath}{\code{PlumberEndpoint$matchesPath()}} -\item \href{#method-new}{\code{PlumberEndpoint$new()}} -\item \href{#method-getPathParams}{\code{PlumberEndpoint$getPathParams()}} -\item \href{#method-getFunc}{\code{PlumberEndpoint$getFunc()}} -\item \href{#method-getFuncParams}{\code{PlumberEndpoint$getFuncParams()}} -\item \href{#method-getEndpointParams}{\code{PlumberEndpoint$getEndpointParams()}} -\item \href{#method-setPath}{\code{PlumberEndpoint$setPath()}} -\item \href{#method-clone}{\code{PlumberEndpoint$clone()}} -} -} -\if{html}{ -\out{
Inherited methods} -\itemize{ -\item \out{}\href{../../plumber/html/Hookable.html#method-registerHooks}{\code{plumber::Hookable$registerHooks()}}\out{} -\item \out{}\href{../../plumber/html/PlumberStep.html#method-exec}{\code{plumber::PlumberStep$exec()}}\out{} -\item \out{}\href{../../plumber/html/PlumberStep.html#method-registerHook}{\code{plumber::PlumberStep$registerHook()}}\out{} -} -\out{
} -} +\item \href{#method-PlumberEndpoint-getTypedParams}{\code{PlumberEndpoint$getTypedParams()}} +\item \href{#method-PlumberEndpoint-canServe}{\code{PlumberEndpoint$canServe()}} +\item \href{#method-PlumberEndpoint-matchesPath}{\code{PlumberEndpoint$matchesPath()}} +\item \href{#method-PlumberEndpoint-new}{\code{PlumberEndpoint$new()}} +\item \href{#method-PlumberEndpoint-getPathParams}{\code{PlumberEndpoint$getPathParams()}} +\item \href{#method-PlumberEndpoint-getFunc}{\code{PlumberEndpoint$getFunc()}} +\item \href{#method-PlumberEndpoint-getFuncParams}{\code{PlumberEndpoint$getFuncParams()}} +\item \href{#method-PlumberEndpoint-getEndpointParams}{\code{PlumberEndpoint$getEndpointParams()}} +\item \href{#method-PlumberEndpoint-setPath}{\code{PlumberEndpoint$setPath()}} +\item \href{#method-PlumberEndpoint-clone}{\code{PlumberEndpoint$clone()}} +} +} +\if{html}{\out{ +
Inherited methods + +
+}} \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-getTypedParams}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PlumberEndpoint-getTypedParams}{}}} \subsection{Method \code{getTypedParams()}}{ retrieve endpoint typed parameters \subsection{Usage}{ @@ -73,8 +73,8 @@ retrieve endpoint typed parameters } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-canServe}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PlumberEndpoint-canServe}{}}} \subsection{Method \code{canServe()}}{ ability to serve request \subsection{Usage}{ @@ -93,8 +93,8 @@ a logical. \code{TRUE} when endpoint can serve request. } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-matchesPath}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PlumberEndpoint-matchesPath}{}}} \subsection{Method \code{matchesPath()}}{ determines if route matches requested path \subsection{Usage}{ @@ -113,8 +113,8 @@ a logical. \code{TRUE} when endpoint matches the requested path. } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-new}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PlumberEndpoint-new}{}}} \subsection{Method \code{new()}}{ Create a new \code{PlumberEndpoint} object \subsection{Usage}{ @@ -158,7 +158,9 @@ Create a new \code{PlumberEndpoint} object If the parser name \code{"all"} is found in any character value or list name, all remaining parsers will be added. When using a list, parser information already defined will maintain their existing argument values. All remaining parsers will use their default arguments. -Example:\preformatted{# provide a character string +Example: + +\if{html}{\out{
}}\preformatted{# provide a character string parsers = "json" # provide a named list with no arguments @@ -169,7 +171,7 @@ parsers = list(json = list(simplifyVector = FALSE), rds = list()) # default plumber parsers parsers = c("json", "form", "text", "octet", "multi") -}} +}\if{html}{\out{
}}} \item{\code{lines}}{Endpoint block} @@ -186,8 +188,8 @@ A new \code{PlumberEndpoint} object } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-getPathParams}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PlumberEndpoint-getPathParams}{}}} \subsection{Method \code{getPathParams()}}{ retrieve endpoint path parameters \subsection{Usage}{ @@ -203,8 +205,8 @@ retrieve endpoint path parameters } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-getFunc}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PlumberEndpoint-getFunc}{}}} \subsection{Method \code{getFunc()}}{ retrieve endpoint function \subsection{Usage}{ @@ -213,8 +215,8 @@ retrieve endpoint function } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-getFuncParams}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PlumberEndpoint-getFuncParams}{}}} \subsection{Method \code{getFuncParams()}}{ retrieve endpoint expression parameters \subsection{Usage}{ @@ -223,8 +225,8 @@ retrieve endpoint expression parameters } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-getEndpointParams}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PlumberEndpoint-getEndpointParams}{}}} \subsection{Method \code{getEndpointParams()}}{ retrieve endpoint defined parameters \subsection{Usage}{ @@ -233,8 +235,8 @@ retrieve endpoint defined parameters } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-setPath}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PlumberEndpoint-setPath}{}}} \subsection{Method \code{setPath()}}{ Updates \verb{$path} with a sanitized \code{path} and updates the internal path meta-data \subsection{Usage}{ @@ -250,8 +252,8 @@ Updates \verb{$path} with a sanitized \code{path} and updates the internal path } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-clone}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PlumberEndpoint-clone}{}}} \subsection{Method \code{clone()}}{ The objects of this class are cloneable with this method. \subsection{Usage}{ diff --git a/man/PlumberStatic.Rd b/man/PlumberStatic.Rd index 1b2bd2611..be5ae3b08 100644 --- a/man/PlumberStatic.Rd +++ b/man/PlumberStatic.Rd @@ -17,49 +17,49 @@ Creates a router that is backed by a directory of files on disk. \section{Methods}{ \subsection{Public methods}{ \itemize{ -\item \href{#method-new}{\code{PlumberStatic$new()}} -\item \href{#method-print}{\code{PlumberStatic$print()}} -\item \href{#method-clone}{\code{PlumberStatic$clone()}} -} -} -\if{html}{ -\out{
Inherited methods} -\itemize{ -\item \out{}\href{../../plumber/html/Hookable.html#method-registerHooks}{\code{plumber::Hookable$registerHooks()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-addAssets}{\code{plumber::Plumber$addAssets()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-addEndpoint}{\code{plumber::Plumber$addEndpoint()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-addFilter}{\code{plumber::Plumber$addFilter()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-addGlobalProcessor}{\code{plumber::Plumber$addGlobalProcessor()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-call}{\code{plumber::Plumber$call()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-filter}{\code{plumber::Plumber$filter()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-getApiSpec}{\code{plumber::Plumber$getApiSpec()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-getDebug}{\code{plumber::Plumber$getDebug()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-handle}{\code{plumber::Plumber$handle()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-mount}{\code{plumber::Plumber$mount()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-onHeaders}{\code{plumber::Plumber$onHeaders()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-onWSOpen}{\code{plumber::Plumber$onWSOpen()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-openAPIFile}{\code{plumber::Plumber$openAPIFile()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-registerHook}{\code{plumber::Plumber$registerHook()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-removeHandle}{\code{plumber::Plumber$removeHandle()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-route}{\code{plumber::Plumber$route()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-run}{\code{plumber::Plumber$run()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-serve}{\code{plumber::Plumber$serve()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-set404Handler}{\code{plumber::Plumber$set404Handler()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-setApiSpec}{\code{plumber::Plumber$setApiSpec()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-setDebug}{\code{plumber::Plumber$setDebug()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-setDocs}{\code{plumber::Plumber$setDocs()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-setDocsCallback}{\code{plumber::Plumber$setDocsCallback()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-setErrorHandler}{\code{plumber::Plumber$setErrorHandler()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-setParsers}{\code{plumber::Plumber$setParsers()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-setSerializer}{\code{plumber::Plumber$setSerializer()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-swaggerFile}{\code{plumber::Plumber$swaggerFile()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-unmount}{\code{plumber::Plumber$unmount()}}\out{} -} -\out{
} -} +\item \href{#method-PlumberStatic-new}{\code{PlumberStatic$new()}} +\item \href{#method-PlumberStatic-print}{\code{PlumberStatic$print()}} +\item \href{#method-PlumberStatic-clone}{\code{PlumberStatic$clone()}} +} +} +\if{html}{\out{ +
Inherited methods + +
+}} \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-new}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PlumberStatic-new}{}}} \subsection{Method \code{new()}}{ Create a new \code{PlumberStatic} router \subsection{Usage}{ @@ -80,8 +80,8 @@ A new \code{PlumberStatic} router } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-print}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PlumberStatic-print}{}}} \subsection{Method \code{print()}}{ Print representation of \code{PlumberStatic()} router. \subsection{Usage}{ @@ -105,8 +105,8 @@ A terminal friendly representation of a \code{PlumberStatic()} router. } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-clone}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PlumberStatic-clone}{}}} \subsection{Method \code{clone()}}{ The objects of this class are cloneable with this method. \subsection{Usage}{ diff --git a/man/PlumberStep.Rd b/man/PlumberStep.Rd index 813fe0a02..d5eb8dcaf 100644 --- a/man/PlumberStep.Rd +++ b/man/PlumberStep.Rd @@ -24,22 +24,22 @@ of a request by a plumber router. \section{Methods}{ \subsection{Public methods}{ \itemize{ -\item \href{#method-new}{\code{PlumberStep$new()}} -\item \href{#method-exec}{\code{PlumberStep$exec()}} -\item \href{#method-registerHook}{\code{PlumberStep$registerHook()}} -\item \href{#method-clone}{\code{PlumberStep$clone()}} -} -} -\if{html}{ -\out{
Inherited methods} -\itemize{ -\item \out{}\href{../../plumber/html/Hookable.html#method-registerHooks}{\code{plumber::Hookable$registerHooks()}}\out{} -} -\out{
} -} +\item \href{#method-PlumberStep-new}{\code{PlumberStep$new()}} +\item \href{#method-PlumberStep-exec}{\code{PlumberStep$exec()}} +\item \href{#method-PlumberStep-registerHook}{\code{PlumberStep$registerHook()}} +\item \href{#method-PlumberStep-clone}{\code{PlumberStep$clone()}} +} +} +\if{html}{\out{ +
Inherited methods + +
+}} \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-new}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PlumberStep-new}{}}} \subsection{Method \code{new()}}{ Create a new \code{\link[=PlumberStep]{PlumberStep()}} object \subsection{Usage}{ @@ -66,8 +66,8 @@ A new \code{PlumberStep} object } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-exec}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PlumberStep-exec}{}}} \subsection{Method \code{exec()}}{ step execution function \subsection{Usage}{ @@ -83,8 +83,8 @@ step execution function } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-registerHook}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PlumberStep-registerHook}{}}} \subsection{Method \code{registerHook()}}{ step hook registration method \subsection{Usage}{ @@ -105,8 +105,8 @@ step hook registration method } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-clone}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-PlumberStep-clone}{}}} \subsection{Method \code{clone()}}{ The objects of this class are cloneable with this method. \subsection{Usage}{ diff --git a/man/deprecated_r6.Rd b/man/deprecated_r6.Rd index c0e4ec305..b6552e7a1 100644 --- a/man/deprecated_r6.Rd +++ b/man/deprecated_r6.Rd @@ -27,21 +27,21 @@ Deprecated R6 functions \section{Methods}{ \subsection{Public methods}{ \itemize{ -\item \href{#method-new}{\code{hookable$new()}} -\item \href{#method-clone}{\code{hookable$clone()}} +\item \href{#method-hookable-new}{\code{hookable$new()}} +\item \href{#method-hookable-clone}{\code{hookable$clone()}} } } -\if{html}{ -\out{
Inherited methods} -\itemize{ -\item \out{}\href{../../plumber/html/Hookable.html#method-registerHook}{\code{plumber::Hookable$registerHook()}}\out{} -\item \out{}\href{../../plumber/html/Hookable.html#method-registerHooks}{\code{plumber::Hookable$registerHooks()}}\out{} -} -\out{
} -} +\if{html}{\out{ +
Inherited methods + +
+}} \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-new}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-hookable-new}{}}} \subsection{Method \code{new()}}{ Initialize a new \code{hookable}. Throws deprecated warning prompting user to use \code{\link{Hookable}} \subsection{Usage}{ @@ -50,8 +50,8 @@ Initialize a new \code{hookable}. Throws deprecated warning prompting user to us } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-clone}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-hookable-clone}{}}} \subsection{Method \code{clone()}}{ The objects of this class are cloneable with this method. \subsection{Usage}{ @@ -73,49 +73,49 @@ The objects of this class are cloneable with this method. \section{Methods}{ \subsection{Public methods}{ \itemize{ -\item \href{#method-new}{\code{plumber$new()}} -\item \href{#method-clone}{\code{plumber$clone()}} -} -} -\if{html}{ -\out{
Inherited methods} -\itemize{ -\item \out{}\href{../../plumber/html/Hookable.html#method-registerHooks}{\code{plumber::Hookable$registerHooks()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-addAssets}{\code{plumber::Plumber$addAssets()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-addEndpoint}{\code{plumber::Plumber$addEndpoint()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-addFilter}{\code{plumber::Plumber$addFilter()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-addGlobalProcessor}{\code{plumber::Plumber$addGlobalProcessor()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-call}{\code{plumber::Plumber$call()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-filter}{\code{plumber::Plumber$filter()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-getApiSpec}{\code{plumber::Plumber$getApiSpec()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-getDebug}{\code{plumber::Plumber$getDebug()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-handle}{\code{plumber::Plumber$handle()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-mount}{\code{plumber::Plumber$mount()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-onHeaders}{\code{plumber::Plumber$onHeaders()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-onWSOpen}{\code{plumber::Plumber$onWSOpen()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-openAPIFile}{\code{plumber::Plumber$openAPIFile()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-print}{\code{plumber::Plumber$print()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-registerHook}{\code{plumber::Plumber$registerHook()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-removeHandle}{\code{plumber::Plumber$removeHandle()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-route}{\code{plumber::Plumber$route()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-run}{\code{plumber::Plumber$run()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-serve}{\code{plumber::Plumber$serve()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-set404Handler}{\code{plumber::Plumber$set404Handler()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-setApiSpec}{\code{plumber::Plumber$setApiSpec()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-setDebug}{\code{plumber::Plumber$setDebug()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-setDocs}{\code{plumber::Plumber$setDocs()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-setDocsCallback}{\code{plumber::Plumber$setDocsCallback()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-setErrorHandler}{\code{plumber::Plumber$setErrorHandler()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-setParsers}{\code{plumber::Plumber$setParsers()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-setSerializer}{\code{plumber::Plumber$setSerializer()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-swaggerFile}{\code{plumber::Plumber$swaggerFile()}}\out{} -\item \out{}\href{../../plumber/html/Plumber.html#method-unmount}{\code{plumber::Plumber$unmount()}}\out{} -} -\out{
} -} +\item \href{#method-plumber-new}{\code{plumber$new()}} +\item \href{#method-plumber-clone}{\code{plumber$clone()}} +} +} +\if{html}{\out{ +
Inherited methods + +
+}} \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-new}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-plumber-new}{}}} \subsection{Method \code{new()}}{ Initialize a new \code{plumber}. Throws deprecated warning prompting user to use \code{\link{Plumber}} \subsection{Usage}{ @@ -131,8 +131,8 @@ Initialize a new \code{plumber}. Throws deprecated warning prompting user to use } } \if{html}{\out{
}} -\if{html}{\out{}} -\if{latex}{\out{\hypertarget{method-clone}{}}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-plumber-clone}{}}} \subsection{Method \code{clone()}}{ The objects of this class are cloneable with this method. \subsection{Usage}{ diff --git a/man/options_plumber.Rd b/man/options_plumber.Rd index 756b071f2..ef2b717fb 100644 --- a/man/options_plumber.Rd +++ b/man/options_plumber.Rd @@ -48,7 +48,8 @@ by RStudio to open the docs when then API is ran from the editor. Defaults to op that has a matching route with a trailing slash. For example, if set to \code{TRUE} and the GET route \verb{/test/} existed, then a GET request of \verb{/test?a=1} would redirect to \verb{/test/?a=1}. Defaults to \code{FALSE}. This option will default to \code{TRUE} in a future release.} -\item{\code{plumber.methodNotAllowed}}{Logical value which allows the router to notify that an +\item{\code{plumber.methodNotAllowed}}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[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 \code{TRUE} and the GET route \verb{/test} existed, then a POST request of \verb{/test} would receive a 405 status and the allowed methods. Defaults to \code{TRUE}.} diff --git a/man/parsers.Rd b/man/parsers.Rd index 2b956580a..64c7c81fa 100644 --- a/man/parsers.Rd +++ b/man/parsers.Rd @@ -3,6 +3,7 @@ \name{parser_form} \alias{parser_form} \alias{parser_json} +\alias{parser_geojson} \alias{parser_text} \alias{parser_yaml} \alias{parser_csv} @@ -10,6 +11,7 @@ \alias{parser_read_file} \alias{parser_rds} \alias{parser_feather} +\alias{parser_parquet} \alias{parser_octet} \alias{parser_multi} \alias{parser_none} @@ -19,6 +21,8 @@ parser_form() parser_json(...) +parser_geojson(...) + parser_text(parse_fn = identity) parser_yaml(...) @@ -33,6 +37,8 @@ parser_rds(...) parser_feather(...) +parser_parquet(...) + parser_octet() parser_multi() @@ -68,6 +74,8 @@ See \code{\link[=registered_parsers]{registered_parsers()}} for a list of regist \item \code{parser_json}: JSON parser. See \code{\link[jsonlite:read_json]{jsonlite::parse_json()}} for more details. (Defaults to using \code{simplifyVectors = TRUE}) +\item \code{parser_geojson}: GeoJSON parser. See \code{\link[geojsonsf:geojson_sf]{geojsonsf::geojson_sf()}} for more details. + \item \code{parser_text}: Helper parser to parse plain text \item \code{parser_yaml}: YAML parser. See \code{\link[yaml:yaml.load]{yaml::yaml.load()}} for more details. @@ -81,7 +89,9 @@ This parser should be used when reading from a file is required. \item \code{parser_rds}: RDS parser. See \code{\link[=readRDS]{readRDS()}} for more details. -\item \code{parser_feather}: feather parser. See \code{\link[feather:read_feather]{feather::read_feather()}} for more details. +\item \code{parser_feather}: feather parser. See \code{\link[arrow:read_feather]{arrow::read_feather()}} for more details. + +\item \code{parser_parquet}: parquet parser. See \code{\link[arrow:read_parquet]{arrow::read_parquet()}} for more details. \item \code{parser_octet}: Octet stream parser. Returns the raw content. @@ -93,7 +103,7 @@ This parser should be used when reading from a file is required. \examples{ \dontrun{ # Overwrite `text/json` parsing behavior to not allow JSON vectors to be simplified -#* @parser json simplifyVector = FALSE +#* @parser json list(simplifyVector = FALSE) # Activate `rds` parser in a multipart request #* @parser multi #* @parser rds diff --git a/man/pr_set_parsers.Rd b/man/pr_set_parsers.Rd index 125090637..2c1a65681 100644 --- a/man/pr_set_parsers.Rd +++ b/man/pr_set_parsers.Rd @@ -20,7 +20,9 @@ pr_set_parsers(pr, parsers) If the parser name \code{"all"} is found in any character value or list name, all remaining parsers will be added. When using a list, parser information already defined will maintain their existing argument values. All remaining parsers will use their default arguments. -Example:\preformatted{# provide a character string +Example: + +\if{html}{\out{
}}\preformatted{# provide a character string parsers = "json" # provide a named list with no arguments @@ -31,7 +33,7 @@ parsers = list(json = list(simplifyVector = FALSE), rds = list()) # default plumber parsers parsers = c("json", "form", "text", "octet", "multi") -}} +}\if{html}{\out{
}}} } \value{ The Plumber router with the new default \link{PlumberEndpoint} parsers diff --git a/man/register_parser.Rd b/man/register_parser.Rd index 0d92137d7..882369a1f 100644 --- a/man/register_parser.Rd +++ b/man/register_parser.Rd @@ -38,7 +38,9 @@ Functions signature should include \code{value}, \code{...} and possibly \code{content_type}, \code{filename}. Other parameters may be provided if you want to use the headers from \code{\link[webutils:parse_multipart]{webutils::parse_multipart()}}. -Parser function structure is something like below.\if{html}{\out{
}}\preformatted{function(parser_arguments_here) \{ +Parser function structure is something like below. + +\if{html}{\out{
}}\preformatted{function(parser_arguments_here) \{ # return a function to parse a raw value function(value, ...) \{ # do something with raw value diff --git a/man/serializers.Rd b/man/serializers.Rd index 1e4321464..ae0747a5e 100644 --- a/man/serializers.Rd +++ b/man/serializers.Rd @@ -8,8 +8,10 @@ \alias{serializer_html} \alias{serializer_json} \alias{serializer_unboxed_json} +\alias{serializer_geojson} \alias{serializer_rds} \alias{serializer_feather} +\alias{serializer_parquet} \alias{serializer_yaml} \alias{serializer_text} \alias{serializer_format} @@ -40,9 +42,13 @@ serializer_json(..., type = "application/json") serializer_unboxed_json(auto_unbox = TRUE, ..., type = "application/json") +serializer_geojson(..., type = "application/geo+json") + serializer_rds(version = "2", ascii = FALSE, ..., type = "application/rds") -serializer_feather(type = "application/feather") +serializer_feather(type = "application/vnd.apache.arrow.file") + +serializer_parquet(type = "application/vnd.apache.parquet") serializer_yaml(..., type = "text/x-yaml; charset=UTF-8") @@ -85,8 +91,8 @@ serializer_pdf(..., type = "application/pdf") \item{...}{extra arguments supplied to respective internal serialization function.} -\item{auto_unbox}{automatically \code{\link[jsonlite]{unbox}} all atomic vectors of length 1. It is usually safer to avoid this and instead use the \code{\link[jsonlite]{unbox}} function to unbox individual elements. -An exception is that objects of class \code{AsIs} (i.e. wrapped in \code{I()}) are not automatically unboxed. This is a way to mark single values as length-1 arrays.} +\item{auto_unbox}{automatically \code{\link[jsonlite:unbox]{unbox()}} all atomic vectors of length 1. It is usually safer to avoid this and instead use the \code{\link[jsonlite:unbox]{unbox()}} function to unbox individual elements. +An exception is that objects of class \code{AsIs} (i.e. wrapped in \code{\link[=I]{I()}}) are not automatically unboxed. This is a way to mark single values as length-1 arrays.} \item{version}{the workspace format version to use. \code{NULL} specifies the current default version (3). The only other supported @@ -128,9 +134,13 @@ more details on Plumber serializers and how to customize their behavior. \item \code{serializer_unboxed_json}: JSON serializer with \code{auto_unbox} defaulting to \code{TRUE}. See also: \code{\link[jsonlite:fromJSON]{jsonlite::toJSON()}} +\item \code{serializer_geojson}: GeoJSON serializer. See also \code{\link[geojsonsf:sf_geojson]{geojsonsf::sf_geojson()}} and [\code{\link[geojsonsf:sfc_geojson]{geojsonsf::sfc_geojson()}}]. + \item \code{serializer_rds}: RDS serializer. See also: \code{\link[base:serialize]{base::serialize()}} -\item \code{serializer_feather}: feather serializer. See also: \code{\link[feather:read_feather]{feather::write_feather()}} +\item \code{serializer_feather}: feather serializer. See also: \code{\link[arrow:write_feather]{arrow::write_feather()}} + +\item \code{serializer_parquet}: parquet serializer. See also: \code{\link[arrow:write_parquet]{arrow::write_parquet()}} \item \code{serializer_yaml}: YAML serializer. See also: \code{\link[yaml:as.yaml]{yaml::as.yaml()}} diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index 2f012b11c..8d5e594b3 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -4,15 +4,15 @@ url: https://www.rplumber.io # mode: auto template: + package: tidytemplate + bootstrap: 5 assets: pkgdown/assets - params: - docsearch: - api_key: 'e8f0e2420e2e9aac7973efb2cc877b06' - index_name: 'rplumber' + bslib: + primary: "#1f6b5d" + navbar-background: "#eefff6" + trailing_slash_redirect: true authors: - Barret Schloerke: - href: http://schloerke.com/ Jeff Allen: href: https://trestletech.com/ @@ -114,3 +114,9 @@ reference: - 'PlumberStatic' - 'PlumberStep' - 'Hookable' + + +news: + releases: + - text: "Version 1.1.0" + href: https://blog.rstudio.com/2021/03/29/plumber-v1-1-0/ diff --git a/revdep/README.md b/revdep/README.md index c3e7f2366..03c42f711 100644 --- a/revdep/README.md +++ b/revdep/README.md @@ -2,35 +2,39 @@ |field |value | |:--------|:----------------------------| -|version |R version 4.0.0 (2020-04-24) | -|os |macOS Catalina 10.15.6 | +|version |R version 4.0.2 (2020-06-22) | +|os |macOS 10.16 | |system |x86_64, darwin17.0 | |ui |X11 | |language |(EN) | |collate |en_US.UTF-8 | |ctype |en_US.UTF-8 | |tz |America/New_York | -|date |2020-09-14 | +|date |2021-03-23 | # Dependencies -|package |old |new |Δ | -|:-------|:-----|:----------|:--| -|plumber |0.4.6 |0.9.9.9000 |* | +|package |old |new |Δ | +|:-------|:-----|:-----|:--| +|plumber |1.0.0 |1.1.0 |* | # Revdeps -## All (9) +## All (13) -|package |version |error |warning |note | -|:----------------------------------------------|:-------|:-----|:-------|:----| -|arenar |0.1.8 | | | | -|[AzureContainers](problems.md#azurecontainers) |1.3.0 | | |1 | -|bayesAB |1.1.2 | | | | -|googleCloudRunner |0.3.0 | | | | -|gqlr |0.0.2 | | | | -|[openmetrics](problems.md#openmetrics) |0.2.0 | | |1 | -|[rjsonapi](problems.md#rjsonapi) |0.1.0 | | |1 | -|rsconnect |0.8.16 | | | | -|swagger |3.33.0 | | | | +|package |version |error |warning |note | +|:----------------------------------------------|:--------|:-----|:-------|:----| +|arenar |0.2.0 | | | | +|[AzureContainers](problems.md#azurecontainers) |1.3.1 | | |1 | +|bayesAB |1.1.2 | | | | +|googleCloudRunner |0.4.1 | | | | +|gqlr |0.0.2 | | | | +|[log](problems.md#log) |1.1.0 | | |1 | +|[microservices](problems.md#microservices) |0.1.0 | | |1 | +|[openmetrics](problems.md#openmetrics) |0.3.0 | | |1 | +|[rapidoc](problems.md#rapidoc) |8.4.3 | | |1 | +|redoc |2.0.0.49 | | | | +|[rjsonapi](problems.md#rjsonapi) |0.1.0 | | |1 | +|rsconnect |0.8.16 | | | | +|swagger |3.33.1 | | | | diff --git a/revdep/cran.md b/revdep/cran.md index 7ac41de33..c08b35369 100644 --- a/revdep/cran.md +++ b/revdep/cran.md @@ -1,6 +1,6 @@ ## revdepcheck results -We checked 9 reverse dependencies, comparing R CMD check results across CRAN and dev versions of this package. +We checked 13 reverse dependencies, comparing R CMD check results across CRAN and dev versions of this package. * We saw 0 new problems * We failed to check 0 packages diff --git a/revdep/problems.md b/revdep/problems.md index 3f1dd2f47..c5011f233 100644 --- a/revdep/problems.md +++ b/revdep/problems.md @@ -2,11 +2,11 @@
-* Version: 1.3.0 +* Version: 1.3.1 * GitHub: https://github.com/Azure/AzureContainers * Source code: https://github.com/cran/AzureContainers -* Date/Publication: 2020-05-06 08:10:02 UTC -* Number of recursive dependencies: 57 +* Date/Publication: 2020-10-14 23:40:17 UTC +* Number of recursive dependencies: 74 Run `revdep_details(, "AzureContainers")` for more info @@ -20,15 +20,60 @@ Run `revdep_details(, "AzureContainers")` for more info All declared Imports should be used. ``` +# log + +
+ +* Version: 1.1.0 +* GitHub: NA +* Source code: https://github.com/cran/log +* Date/Publication: 2021-01-14 09:10:02 UTC +* Number of recursive dependencies: 67 + +Run `revdep_details(, "log")` for more info + +
+ +## In both + +* checking dependencies in R code ... NOTE + ``` + Namespace in Imports field not imported from: ‘R6’ + All declared Imports should be used. + ``` + +# microservices + +
+ +* Version: 0.1.0 +* GitHub: https://github.com/tidylab/microservices +* Source code: https://github.com/cran/microservices +* Date/Publication: 2021-03-03 09:20:20 UTC +* Number of recursive dependencies: 68 + +Run `revdep_details(, "microservices")` for more info + +
+ +## In both + +* checking dependencies in R code ... NOTE + ``` + Namespaces in Imports field not imported from: + ‘config’ ‘desc’ ‘dplyr’ ‘glue’ ‘withr’ + All declared Imports should be used. + ``` + # openmetrics
-* Version: 0.2.0 +* Version: 0.3.0 * GitHub: https://github.com/atheriel/openmetrics * Source code: https://github.com/cran/openmetrics -* Date/Publication: 2020-07-14 08:00:03 UTC -* Number of recursive dependencies: 45 +* Date/Publication: 2020-11-09 21:20:02 UTC +* Number of recursive dependencies: 63 Run `revdep_details(, "openmetrics")` for more info @@ -42,6 +87,28 @@ Run `revdep_details(, "openmetrics")` for more info All declared Imports should be used. ``` +# rapidoc + +
+ +* Version: 8.4.3 +* GitHub: https://github.com/meztez/rapidoc +* Source code: https://github.com/cran/rapidoc +* Date/Publication: 2021-02-05 10:30:05 UTC +* Number of recursive dependencies: 19 + +Run `revdep_details(, "rapidoc")` for more info + +
+ +## In both + +* checking dependencies in R code ... NOTE + ``` + Namespace in Imports field not imported from: ‘jsonlite’ + All declared Imports should be used. + ``` + # rjsonapi
@@ -50,7 +117,7 @@ Run `revdep_details(, "openmetrics")` for more info * GitHub: https://github.com/ropensci/rjsonapi * Source code: https://github.com/cran/rjsonapi * Date/Publication: 2017-01-09 01:47:26 -* Number of recursive dependencies: 46 +* Number of recursive dependencies: 58 Run `revdep_details(, "rjsonapi")` for more info diff --git a/revdep/revdep_cran.md b/revdep/revdep_cran.md index 197327e39..613992c71 100644 --- a/revdep/revdep_cran.md +++ b/revdep/revdep_cran.md @@ -1,6 +1,6 @@ ## revdepcheck results -We checked 8 reverse dependencies, comparing R CMD check results across CRAN and dev versions of this package. +We checked 10 reverse dependencies, comparing R CMD check results across CRAN and dev versions of this package. * We saw 0 new problems * We failed to check 0 packages diff --git a/scripts/release.md b/scripts/release.md index c2c5a594b..7a5e45cb1 100644 --- a/scripts/release.md +++ b/scripts/release.md @@ -21,5 +21,5 @@ 1. Submit to CRAN. 1. `devtools::release()` (actually release) 1. Do any revisions CRAN requests on the release branch -1. Once accepted to CRAN, merge the release branch to master and tag the release. -1. Bump the version # in DESCRIPTION to the next odd number on master for development of next release. +1. Once accepted to CRAN, merge the release branch to main and tag the release. +1. Bump the version # in DESCRIPTION to the next odd number on main for development of next release. diff --git a/tests/spelling.R b/tests/spelling.R new file mode 100644 index 000000000..6713838fc --- /dev/null +++ b/tests/spelling.R @@ -0,0 +1,3 @@ +if(requireNamespace('spelling', quietly = TRUE)) + spelling::spell_check_test(vignettes = TRUE, error = FALSE, + skip_on_cran = TRUE) diff --git a/tests/testthat/files/endpoints.R b/tests/testthat/files/endpoints.R index e96dbe0ef..f287feda6 100644 --- a/tests/testthat/files/endpoints.R +++ b/tests/testthat/files/endpoints.R @@ -9,7 +9,7 @@ function(req, res, forward){ #* @get /test #* @post /test #* @more stuff -#' @param req Roxygen params +#' @param req roxygen2 params #* hey foo <- function(a, b, ..., req, res, forward){ 5 diff --git a/tests/testthat/files/router.R b/tests/testthat/files/router.R index ad912d874..a52a543f9 100644 --- a/tests/testthat/files/router.R +++ b/tests/testthat/files/router.R @@ -42,13 +42,20 @@ function(){ stop("ERROR") } -#* @get /response +#* @get /response123 function(res){ res$body <- "overridden" res$status <- 123 res } +#* @get /response200 +function(res){ + res$body <- "overridden" + res$status <- 200 + res +} + #* @get /path1 #* @get /path2 function(){ diff --git a/tests/testthat/test-endpoint-aroundexec.R b/tests/testthat/test-endpoint-aroundexec.R index a4652657e..b85c9e385 100644 --- a/tests/testthat/test-endpoint-aroundexec.R +++ b/tests/testthat/test-endpoint-aroundexec.R @@ -128,7 +128,7 @@ test_that("serializers can register all pre,post,aroundexec stages", { test_that("not producing an image produces an error", { - skip_on_os("win") + skip_on_os("windows") root <- with_tmp_serializers({ plumb(test_path("files/endpoint-serializer.R")) %>% pr_set_debug(TRUE) diff --git a/tests/testthat/test-parse-body.R b/tests/testthat/test-parse-body.R index f2054e8e8..ec2b37703 100644 --- a/tests/testthat/test-parse-body.R +++ b/tests/testthat/test-parse-body.R @@ -90,7 +90,7 @@ test_that("Test tsv parser", { }) test_that("Test feather parser", { - skip_if_not_installed("feather") + skip_if_not_installed("arrow") tmp <- tempfile() on.exit({ @@ -98,10 +98,10 @@ test_that("Test feather parser", { }, add = TRUE) r_object <- iris - feather::write_feather(r_object, tmp) + arrow::write_feather(r_object, tmp) val <- readBin(tmp, "raw", 10000) - parsed <- parse_body(val, "application/feather", make_parser("feather")) + parsed <- parse_body(val, "application/vnd.apache.arrow.file", make_parser("feather")) # convert from feather tibble to data.frame parsed <- as.data.frame(parsed, stringsAsFactors = FALSE) attr(parsed, "spec") <- NULL @@ -109,6 +109,60 @@ test_that("Test feather parser", { expect_equal(parsed, r_object) }) +test_that("Test parquet parser", { + skip_if_not_installed("arrow") + + tmp <- tempfile() + on.exit({ + file.remove(tmp) + }, add = TRUE) + + r_object <- iris + arrow::write_parquet(r_object, tmp) + val <- readBin(tmp, "raw", 10000) + + parsed <- parse_body(val, "application/vnd.apache.parquet", make_parser("parquet")) + # convert from parquet tibble to data.frame + parsed <- as.data.frame(parsed, stringsAsFactors = FALSE) + attr(parsed, "spec") <- NULL + + expect_equal(parsed, r_object) +}) + +test_that("Test geojson parser", { + skip_if_not_installed("geojsonsf") + skip_if_not_installed("sf") + + # Test sf object w/ fields + geojson <- '{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"a":3},"geometry":{"type":"Point","coordinates":[1,2]}},{"type":"Feature","properties":{"a":4},"geometry":{"type":"Point","coordinates":[3,4]}}]}' + parsed <- parse_body(geojson, "application/geo+json", make_parser("geojson")) + expect_equal(parsed, geojsonsf::geojson_sf(geojson)) + + # Test sfc + geojson <- '[ + { "type":"Point","coordinates":[0,0]}, + {"type":"LineString","coordinates":[[0,0],[1,1]]} + ]' + parsed <- parse_body(geojson, "application/geo+json", make_parser("geojson")) + expect_equal(parsed, geojsonsf::geojson_sf(geojson)) + + # Test simple sf object + geojson <- '{ "type" : "Point", "coordinates" : [0, 0] }' + parsed <- parse_body(geojson, "application/geo+json", make_parser("geojson")) + expect_equal(parsed, geojsonsf::geojson_sf(geojson)) + + # Test geojson file + tmp <- tempfile() + on.exit({ + file.remove(tmp) + }, add = TRUE) + + writeLines(geojson, tmp) + val <- readBin(tmp, "raw", 1000) + parsed <- parse_body(val, "application/geo+json", make_parser("geojson")) + expect_equal(parsed, geojsonsf::geojson_sf(geojson)) + +}) test_that("Test multipart output is reduced for argument matching", { bin_file <- test_path("files/multipart-file-names.bin") diff --git a/tests/testthat/test-plumber.R b/tests/testthat/test-plumber.R index b141a531f..f87a71ae8 100644 --- a/tests/testthat/test-plumber.R +++ b/tests/testthat/test-plumber.R @@ -63,15 +63,15 @@ test_that("plumb accepts a file", { test_that("plumb gives a good error when passing in a dir instead of a file", { + # brittle test. Fails on r-devel-windows-x86_64-gcc10-UCRT + skip_on_cran() + if (isWindows()) { - # https://stat.ethz.ch/R-manual/R-devel/library/base/html/files.html - # "However, directory names must not include a trailing backslash or slash on Windows" - # Appveyor does not work with "files/", but does trigger the proper error with "files\\" - expect_error(plumb(test_path("files\\")), "File does not exist:") - } else { - expect_error(plumb(test_path("files/")), "Expecting a file but found a directory: 'files/'") + # File paths are hard to work with and are inconsistent + skip_on_os("windows") } + expect_error(plumb(test_path("files/")), "Expecting a file but found a directory: 'files/'") }) test_that("plumb accepts a directory with a `plumber.R` file", { diff --git a/tests/testthat/test-response.R b/tests/testthat/test-response.R index c459f02e2..b3cec4611 100644 --- a/tests/testthat/test-response.R +++ b/tests/testthat/test-response.R @@ -44,3 +44,10 @@ test_that("response properly sets cookies with multiple options", { head <- res$toResponse()$headers expect_equal(head[["Set-Cookie"]], "abc=two%20words; HttpOnly; Secure; SameSite=None") }) + +test_that("Body on HTTP responses which forbid it", { + res <- PlumberResponse$new() + res$body <- "Hello" + res$status <- 204 + expect_null(res$toResponse()$body) +}) diff --git a/tests/testthat/test-serializer-device.R b/tests/testthat/test-serializer-device.R index e747f50d5..f58aa2e63 100644 --- a/tests/testthat/test-serializer-device.R +++ b/tests/testthat/test-serializer-device.R @@ -29,7 +29,7 @@ expect_device_output <- function(name, content_type, capability_type = name) { if (!is.null(capability_type)) { if (!capabilities(capability_type)) { - testthat::skip("Graphics device type not supported: ", name) + testthat::skip(paste0("Graphics device type not supported: ", name)) } } diff --git a/tests/testthat/test-serializer-feather.R b/tests/testthat/test-serializer-feather.R index c191997da..cfa4bc591 100644 --- a/tests/testthat/test-serializer-feather.R +++ b/tests/testthat/test-serializer-feather.R @@ -1,15 +1,15 @@ context("feather serializer") test_that("feather serializes properly", { - skip_if_not_installed("feather") + skip_if_not_installed("arrow") d <- data.frame(a=1, b=2, c="hi") val <- serializer_feather()(d, data.frame(), PlumberResponse$new(), stop) expect_equal(val$status, 200L) - expect_equal(val$headers$`Content-Type`, "application/feather") + expect_equal(val$headers$`Content-Type`, "application/vnd.apache.arrow.file") # can test by doing a full round trip if we believe the parser works via `test-parse-body.R` - parsed <- parse_body(val$body, "application/feather", make_parser("feather")) + parsed <- parse_body(val$body, "application/vnd.apache.arrow.file", make_parser("feather")) # convert from feather tibble to data.frame parsed <- as.data.frame(parsed, stringsAsFactors = FALSE) attr(parsed, "spec") <- NULL @@ -18,7 +18,7 @@ test_that("feather serializes properly", { }) test_that("Errors call error handler", { - skip_if_not_installed("feather") + skip_if_not_installed("arrow") errors <- 0 errHandler <- function(req, res, err){ @@ -31,7 +31,7 @@ test_that("Errors call error handler", { }) test_that("Errors are rendered correctly with debug TRUE", { - skip_if_not_installed("feather") + skip_if_not_installed("arrow") pr <- pr() %>% pr_get("/", function() stop("myerror"), serializer = serializer_feather()) %>% pr_set_debug(TRUE) capture.output(res <- pr$serve(make_req(pr = pr), PlumberResponse$new("csv"))) diff --git a/tests/testthat/test-serializer-geojson.R b/tests/testthat/test-serializer-geojson.R new file mode 100644 index 000000000..6ac6eaad6 --- /dev/null +++ b/tests/testthat/test-serializer-geojson.R @@ -0,0 +1,35 @@ +test_that("GeoJSON serializes properly", { + skip_if_not_installed("geojsonsf") + skip_if_not_installed("sf") + + # Objects taken from ?st_sf() examples. + sfc <- sf::st_sfc(sf::st_point(1:2), sf::st_point(3:4)) + sf <- sf::st_sf(a = 3:4, sfc) + + # Test sfc + val <- serializer_geojson()(sfc, data.frame(), PlumberResponse$new(), stop) + expect_equal(val$status, 200L) + expect_equal(val$headers$`Content-Type`, "application/geo+json") + expect_equal(val$body, geojsonsf::sfc_geojson(sfc)) + + # Test sf + val <- serializer_geojson()(sf, data.frame(), PlumberResponse$new(), stop) + expect_equal(val$status, 200L) + expect_equal(val$headers$`Content-Type`, "application/geo+json") + expect_equal(val$body, geojsonsf::sf_geojson(sf)) + +}) + +test_that("Errors call error handler", { + skip_if_not_installed("geojsonsf") + skip_if_not_installed("sf") + + errors <- 0 + errHandler <- function(req, res, err){ + errors <<- errors + 1 + } + + expect_equal(errors, 0) + serializer_geojson()(parse(text="h$534i} {!"), data.frame(), PlumberResponse$new(), errorHandler = errHandler) + expect_equal(errors, 1) +}) diff --git a/tests/testthat/test-serializer-htmlwidgets.R b/tests/testthat/test-serializer-htmlwidgets.R index b0f36a992..5e5105270 100644 --- a/tests/testthat/test-serializer-htmlwidgets.R +++ b/tests/testthat/test-serializer-htmlwidgets.R @@ -22,7 +22,7 @@ test_that("htmlwidgets serialize properly", { expect_equal(val$status, 200L) expect_equal(val$headers$`Content-Type`, "text/html; charset=UTF-8") # Check that content is encoded - expect_match(val$body, "url(data:image/png;base64", fixed = TRUE) + expect_match(val$body, "url\\(['\"]?data:image\\/png;base64") }) test_that("Errors call error handler", { diff --git a/tests/testthat/test-serializer.R b/tests/testthat/test-serializer.R index 3dc7c35e5..081d82ac0 100644 --- a/tests/testthat/test-serializer.R +++ b/tests/testthat/test-serializer.R @@ -4,9 +4,12 @@ test_that("Responses returned directly aren't serialized", { res <- PlumberResponse$new("") r <- pr(test_path("files/router.R")) - val <- r$serve(make_req("GET", "/response"), res) - expect_equal(val$body, "overridden") + val <- r$serve(make_req("GET", "/response123"), res) + expect_equal(val$body, NULL) expect_equal(val$status, 123) + val <- r$serve(make_req("GET", "/response200"), res) + expect_equal(val$body, "overridden") + expect_equal(val$status, 200) }) test_that("JSON is the default serializer", { diff --git a/tests/testthat/test-shared-secret.R b/tests/testthat/test-shared-secret.R index a2b77fea3..6f882f3c2 100644 --- a/tests/testthat/test-shared-secret.R +++ b/tests/testthat/test-shared-secret.R @@ -5,12 +5,23 @@ test_that("requests with shared secrets pass, w/o fail", { pr <- pr() pr$handle("GET", "/", function(){ 123 }) + req <- make_req("GET", "/", pr = pr) # No shared secret - req <- make_req("GET", "/") res <- PlumberResponse$new() - capture.output(pr$route(req, res)) + output <- pr$route(req, res) expect_equal(res$status, 400) + expect_equal(output, list(error = "400 - Bad request")) + + # When debugging, we get additional details in the error. + pr$setDebug(TRUE) + res <- PlumberResponse$new() + output <- pr$route(req, res) + expect_equal(res$status, 400) + expect_equal(output, list( + error = "400 - Bad request", + message = "Shared secret mismatch")) + pr$setDebug(FALSE) # Set shared secret assign("HTTP_PLUMBER_SHARED_SECRET", "abcdefg", envir=req) diff --git a/tests/testthat/test-static.R b/tests/testthat/test-static.R index 3ad92054b..c481f9cb5 100644 --- a/tests/testthat/test-static.R +++ b/tests/testthat/test-static.R @@ -17,6 +17,22 @@ test_that("static txt file is served", { expect_equal(trimws(rawToChar(res$body)), "I am a text file.") }) +test_that("static txt file with encoded URI is served", { + + # Some file systems cannot handle these characters. + testthat::skip_on_cran() + + res <- PlumberResponse$new() + f <- test_path("files/static/测试.txt") + file.create(f) + on.exit(unlink(f), add = TRUE) + writeChar("here be dragons", f) + pr$route(make_req("GET", "/测试.txt"), res) + unlink(test_path("files/static/测试.txt")) + expect_equal(res$headers$`Content-Type`, "text/plain") + expect_equal(trimws(rawToChar(res$body)), "here be dragons") +}) + test_that("static html file is served", { res <- PlumberResponse$new() pr$route(make_req("GET", "/index.html"), res) diff --git a/tests/testthat/test-zzz-openapi.R b/tests/testthat/test-zzz-openapi.R index 6c9fcc177..0b102fb0a 100644 --- a/tests/testthat/test-zzz-openapi.R +++ b/tests/testthat/test-zzz-openapi.R @@ -336,7 +336,7 @@ test_that("no params plumber router still produces spec when there is a func par test_that("Response content type set with serializer", { a <- pr() - pr_get(a, "/json", function() {"OK"}, serializer = serializer_json) + pr_get(a, "/json", function() {"OK"}, serializer = serializer_json()) pr_get(a, "/csv", function() {"OK"}, serializer = serializer_csv()) spec <- a$getApiSpec() expect_equal(spec$paths$`/json`$get$responses$`200`$content, list("application/json" = list(schema = list(type = "object")))) diff --git a/vignettes/_ex-github.Rmd b/vignettes/_ex-github.Rmd index 55b30f667..96de7c7c3 100644 --- a/vignettes/_ex-github.Rmd +++ b/vignettes/_ex-github.Rmd @@ -33,7 +33,7 @@ At this point, any commits that are pushed to that repository will trigger a POS In this example, we'll demonstrate how to setup an plumber endpoint that is capable of listening for Webhook notifications from GitHub. The example will simply subscribe to `push` notifications on the plumber repository (which are triggered any time a commit is pushed to that repo) and, in response, will install the most up-to-date version of plumber. -We'll add one additional endpoint that enables us to see what version of plumber is installed on the system at that moment. You should find that the `sha1` value of the response matches the latest commit hash in the master branch of plumber. +We'll add one additional endpoint that enables us to see what version of plumber is installed on the system at that moment. You should find that the `sha1` value of the response matches the latest commit hash in the main branch of plumber.
diff --git a/vignettes/annotations.Rmd b/vignettes/annotations.Rmd index 57604e493..46210db82 100644 --- a/vignettes/annotations.Rmd +++ b/vignettes/annotations.Rmd @@ -13,7 +13,7 @@ source("_helpers.R") ## Annotations {#annotations} -Annotations are specially-structured comments used in your plumber file to create an API. A full annotation line starts with `#*` or `#'`, then the annotation keyword `@...`, any number of space characters followed by the content. It is recommended to use `#*` to differentiate them from `Roxygen2` annotations. +Annotations are specially-structured comments used in your plumber file to create an API. A full annotation line starts with `#*` or `#'`, then the annotation keyword `@...`, any number of space characters followed by the content. It is recommended to use `#*` to differentiate them from `roxygen2` annotations. ## Global annotations {#global-annotations} @@ -74,7 +74,7 @@ Annotation | Argument | Description/References `@parser` | `Alias` `[Args list]` | Some parsers accept arguments. See [parsers reference](https://www.rplumber.io/reference/parsers.html). Can be repeated to allow multiple parsers on the same endpoint. Aliases : `r paste0("", registered_parsers(), "", collapse = ", ")` from [`registered_parsers()`](https://www.rplumber.io/reference/register_parser.html). `@param` | `Name`[`:Type` `Description`] | Enclose `Type` between square brackets `[]` to indicate it is an array. Can be repeated to define different parameters. `@response` | `Name` `Description` | Simple [Response object](http://spec.openapis.org/oas/v3.0.3#response-object). Can be repeated to define different responses. -`@tags` | `Tag` | Can be repeated to add multiple tags. Quote with " or ' to use non word character (like spaces) in `Tag`. [Tag field](http://spec.openapis.org/oas/v3.0.3#operation-object) +`@tag` | `Tag` | Can be repeated to add multiple tags. Quote with " or ' to use non word character (like spaces) in `Tag`. [Tag field](http://spec.openapis.org/oas/v3.0.3#operation-object) `@preempt` | `Filter` | Specify that this endpoint has to execute before `Filter`. [Filters](./programmatic-usage.html#defining-filters) None | `Comments` | Lines without annotation will be mapped to [Summary field](http://spec.openapis.org/oas/v3.0.3#fixed-fields-6). @@ -85,9 +85,9 @@ Types are used to define API inputs. You can use most of them in dynamic routes. Query parameters currently need to be explicitly converted as they are pushed as is (character) to block expression. Only dynamic route parameters are converted to specified `@param` type before being pushed to block expression. -Plumber parameter type to OpenAPI type refenrence. For programmatic use, pick the one with an asterisk. +Plumber parameter type to OpenAPI type reference. For programmatic use, pick the one with an asterisk. -Type | OpenApi | Availability +Type | OpenAPI | Availability ---------------- | ----------- | --------- `bool`, `boolean`*, `logical` | `boolean` | `query`, `path` `dbl`, `double`, `float`, `number`*, `numeric` | `number` `format:double` | `query`, `path` @@ -217,7 +217,7 @@ pr() %>% Annotation | Arguments | Description/References -----------| --------- | ---------------------- -`@plumber` | None | Modify plumber router from plumber file. The plumber router provided to the function **must** be returned. +`@plumber` | None | Modify plumber router from plumber file. The plumber router provided to the function **must** be returned. In most cases, anonymous functions are used following the `#* @plumber` annotation. However, named functions can also be used. When a named function is used, it must be referenced without parentheses. ##### Annotations example @@ -228,6 +228,16 @@ function(pr) { pr_set_debug(TRUE) %>% pr_set_docs("swagger") } + +# Named function +debug_swagger <- function(pr) { + pr %>% + pr_set_debug(TRUE) %>% + pr_set_docs("swagger") +} + +#* @plumber +debug_swagger ``` ##### Equivalent programmatic usage diff --git a/vignettes/execution-model.Rmd b/vignettes/execution-model.Rmd index 7dc0a2871..6f87b9d11 100644 --- a/vignettes/execution-model.Rmd +++ b/vignettes/execution-model.Rmd @@ -82,7 +82,7 @@ If you'd like to use cookies to store information with guarantees that the user The final option to consider when coordinating state for an API is leveraging an external data store. This could be a relational database (like MySQL or Amazon RedShift), a non-relational database (like MongoDB), or an transactional data store like Redis. -One important consideration for any of these options is to ensure that they are "transactional," meaning that two Plumber processes trying to write at the same time won't overwrite one another. If you're interested in pursuing this option you should see [db.rstudio.com](http://db.rstudio.com/) or looks at [some](http://shiny.rstudio.com/articles/overview.html) of the [resources](http://shiny.rstudio.com/articles/sql-injections.html) [put together](http://shiny.rstudio.com/articles/pool-basics.html) for Shiny as pertains to dealing with databases in a web-accessible R platform. +One important consideration for any of these options is to ensure that they are "transactional," meaning that two Plumber processes trying to write at the same time won't overwrite one another. If you're interested in pursuing this option you should see [db.rstudio.com](http://db.rstudio.com/) or look at [some](http://shiny.rstudio.com/articles/overview.html) of the [resources](http://shiny.rstudio.com/articles/sql-injections.html) [put together](http://shiny.rstudio.com/articles/pool-basics.html) for Shiny as pertains to dealing with databases in a web-accessible R platform. ## Exit Handlers diff --git a/vignettes/hosting.Rmd b/vignettes/hosting.Rmd index e2da05271..a8bc167d1 100644 --- a/vignettes/hosting.Rmd +++ b/vignettes/hosting.Rmd @@ -267,7 +267,7 @@ We can use the [dockercloud/haproxy](https://github.com/docker/dockercloud-hapro The trick that allows this image to listen in to our scaling of `app1` is by passing in the docker socket as a shared volume. Note that this particular arrangement will differ based on your host OS. The above configuration is intended for Linux, but MacOS X users would require a [slightly different config](https://github.com/docker/dockercloud-haproxy#example-of-docker-composeyml-running-in-linux). -We could export port `80` of our new load balancer to port `80` of our host machine if we solely wanted to load-balance a single application. Alternatively, we can actually use both nginx (to handle the routing of various applications) and HAProxy (to handle the load balancing of a particular application). To do that, we'd merely add a new `location` block to our `nginx.conf` file that knows how to send traffic to HAproxy, or modify the existing `location` block to send traffic to the load balancer instead of going directly to the application. +We could export port `80` of our new load balancer to port `80` of our host machine if we solely wanted to load-balance a single application. Alternatively, we can actually use both nginx (to handle the routing of various applications) and HAProxy (to handle the load balancing of a particular application). To do that, we'd merely add a new `location` block to our `nginx.conf` file that knows how to send traffic to HAProxy, or modify the existing `location` block to send traffic to the load balancer instead of going directly to the application. So the `location /app1/` block becomes: diff --git a/vignettes/rendering-output.Rmd b/vignettes/rendering-output.Rmd index bda83f923..2ef0f9d37 100644 --- a/vignettes/rendering-output.Rmd +++ b/vignettes/rendering-output.Rmd @@ -59,7 +59,8 @@ Annotation | Content Type | Description/References `@serializer rds` | `application/rds` | Object processed with `base::serialize()` `@serializer csv` | `text/csv` | Object processed with `readr::format_csv()` `@serializer tsv` | `text/tab-separated-values` | Object processed with `readr::format_tsv()` -`@serializer feather` | `application/feather` | Object processed with `feather::write_feather()` +`@serializer feather` | `application/vnd.apache.arrow.file` | Object processed with `arrow::write_feather()` +`@serializer parquet` | `application/parquet` | Object processed with `arrow::write_parquet()` `@serializer yaml` | `text/x-yaml` | Object processed with `yaml::as_yaml()` `@serializer htmlwidget` | `text/html; charset=utf-8` | `htmlwidgets::saveWidget()` `@serializer text` | `text/plain` | Text output processed by `as.character()` diff --git a/vignettes/security.Rmd b/vignettes/security.Rmd index 0d8dc7af0..258724a42 100644 --- a/vignettes/security.Rmd +++ b/vignettes/security.Rmd @@ -138,7 +138,7 @@ In the [section on setting cookies](./rendering-output.html#setting-cookies), we First and foremost, recognize that that the client has the ability to modify or fabricate the cookies that they send to your API. So storing preferences that the user themselves provided in a cookie is not a concern. Storing something with security implications -- like the identity of the user making requests -- however, would be; a malicious user would just need to modify the user ID saved in their cookie in order to trick your API into thinking that they were someone they're not. -There are two common work-arounds to this concern. You can store all session information on the server identified by long, cryptographically random IDs and only rely on the cookie to store the ID. Or you can use signed/encrypted cookies, as detailed in the [section on setting encrypted cookies](./rendering-output.html#encrypted-cookies). +There are two common workarounds to this concern. You can store all session information on the server identified by long, cryptographically random IDs and only rely on the cookie to store the ID. Or you can use signed/encrypted cookies, as detailed in the [section on setting encrypted cookies](./rendering-output.html#encrypted-cookies). You should also be aware of how cookies will be handled and managed by clients. You can manage these properties by providing different parameters to the `setCookie()` call.