From a12e221b7a2ab7483a9e95069ec3e5bf686d989a Mon Sep 17 00:00:00 2001 From: Bruno Tremblay Date: Mon, 22 Jun 2020 00:59:34 -0400 Subject: [PATCH 1/7] add yaml support --- DESCRIPTION | 4 +++- NAMESPACE | 2 ++ R/post-parsers.R | 19 ++++++++++++++++ R/serializer-yaml.R | 21 ++++++++++++++++++ man/parsers.Rd | 3 +++ man/plumber.Rd | 2 +- man/serializers.Rd | 5 ++++- tests/testthat/test-postbody.R | 5 +++++ tests/testthat/test-serializer-yaml.R | 32 +++++++++++++++++++++++++++ 9 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 R/serializer-yaml.R create mode 100644 tests/testthat/test-serializer-yaml.R diff --git a/DESCRIPTION b/DESCRIPTION index 40f0b42d7..88ef08aef 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -40,7 +40,8 @@ Suggests: htmlwidgets, visNetwork, analogsea (>= 0.7.0), - later + later, + yaml Remotes: rstudio/swagger Collate: @@ -72,6 +73,7 @@ Collate: 'serializer-htmlwidget.R' 'serializer-rds.R' 'serializer-xml.R' + 'serializer-yaml.R' 'serializer.R' 'session-cookie.R' 'swagger.R' diff --git a/NAMESPACE b/NAMESPACE index f88bbfbcd..19ecbb19d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -22,6 +22,7 @@ export(parser_octet) export(parser_query) export(parser_rds) export(parser_text) +export(parser_yaml) export(plumb) export(plumber) export(randomCookieKey) @@ -31,6 +32,7 @@ export(serializer_htmlwidget) export(serializer_json) export(serializer_rds) export(serializer_unboxed_json) +export(serializer_yaml) export(sessionCookie) import(R6) import(crayon) diff --git a/R/post-parsers.R b/R/post-parsers.R index 526e9b491..e529caf13 100644 --- a/R/post-parsers.R +++ b/R/post-parsers.R @@ -164,6 +164,24 @@ parser_octet <- function(...) { +#' YAML +#' @rdname parsers +#' @export +parser_yaml <- function(...) { + if (!requireNamespace("yaml", quietly = TRUE)) { + stop("yaml must be installed for the yaml parser to work") + } + function(value, content_type = NULL, ...) { + charset <- getCharacterSet(content_type) + value <- rawToChar(value) + Encoding(value) <- charset + yaml::yaml.load(value) + } +} + + + + addParsers_onLoad <- function() { addParser("json", parser_json, "application/json") addParser("query", parser_query, "application/x-www-form-urlencoded") @@ -171,4 +189,5 @@ addParsers_onLoad <- function() { addParser("rds", parser_rds, "application/rds") addParser("multi", parser_multi, "multipart/form-data") addParser("octet", parser_octet, "application/octet") + addParser("yaml", parser_yaml, "application/x-yaml") } diff --git a/R/serializer-yaml.R b/R/serializer-yaml.R new file mode 100644 index 000000000..a7e5c9ec8 --- /dev/null +++ b/R/serializer-yaml.R @@ -0,0 +1,21 @@ +#' @rdname serializers +#' @export +serializer_yaml <- function(...) { + if (!requireNamespace("yaml", quietly = TRUE)) { + stop("yaml must be installed for the yaml serializer to work") + } + function(val, req, res, errorHandler) { + tryCatch({ + yaml <- yaml::as.yaml(val, ...) + res$setHeader("Content-Type", "application/x-yaml") + res$body <- yaml + + return(res$toResponse()) + }, error = function(e){ + errorHandler(req, res, e) + }) + } +} + +#' @include globals.R +.globals$serializers[["yaml"]] <- serializer_yaml diff --git a/man/parsers.Rd b/man/parsers.Rd index 903b4e349..8f1732db9 100644 --- a/man/parsers.Rd +++ b/man/parsers.Rd @@ -8,6 +8,7 @@ \alias{parser_rds} \alias{parser_multi} \alias{parser_octet} +\alias{parser_yaml} \title{Plumber Parsers} \usage{ parser_json(...) @@ -21,6 +22,8 @@ parser_rds(...) parser_multi(...) parser_octet(...) + +parser_yaml(...) } \arguments{ \item{...}{Raw values and headers are passed there.} diff --git a/man/plumber.Rd b/man/plumber.Rd index ded441ed1..bb6a4e98e 100644 --- a/man/plumber.Rd +++ b/man/plumber.Rd @@ -5,7 +5,7 @@ \alias{plumber} \title{Plumber Router} \usage{ -plumb(file, dir = ".") +plumb(file = NULL, dir = ".") } \arguments{ \item{file}{path to file to plumb} diff --git a/man/serializers.Rd b/man/serializers.Rd index 9ef89c884..287880324 100644 --- a/man/serializers.Rd +++ b/man/serializers.Rd @@ -1,7 +1,7 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/serializer-json.R, R/serializer-content-type.R, % R/serializer-html.R, R/serializer-htmlwidget.R, R/serializer-rds.R, -% R/serializer.R +% R/serializer-yaml.R, R/serializer.R \name{serializer_json} \alias{serializer_json} \alias{serializer_unboxed_json} @@ -9,6 +9,7 @@ \alias{serializer_html} \alias{serializer_htmlwidget} \alias{serializer_rds} +\alias{serializer_yaml} \alias{serializers} \title{Plumber Serializers} \usage{ @@ -23,6 +24,8 @@ serializer_html() serializer_htmlwidget(...) serializer_rds(version = "2", ascii = FALSE, ...) + +serializer_yaml(...) } \arguments{ \item{...}{extra arguments supplied to respective internal serialization function.} diff --git a/tests/testthat/test-postbody.R b/tests/testthat/test-postbody.R index 5d7ae8a7b..af97a12fb 100644 --- a/tests/testthat/test-postbody.R +++ b/tests/testthat/test-postbody.R @@ -49,6 +49,11 @@ test_that("Test text parser", { expect_equal(parseBody("Ceci est un texte.", "text/html"), "Ceci est un texte.") }) +test_that("Test yaml parser", { + r_object <- list(a=1,b=list(c=2,d=list(e=3,f=4:6))) + expect_equal(parseBody(charToRaw(yaml::as.yaml(r_object)), "application/x-yaml"), r_object) +}) + test_that("Test multipart parser", { bin_file <- test_path("files/multipart-form.bin") diff --git a/tests/testthat/test-serializer-yaml.R b/tests/testthat/test-serializer-yaml.R new file mode 100644 index 000000000..c88e98f99 --- /dev/null +++ b/tests/testthat/test-serializer-yaml.R @@ -0,0 +1,32 @@ +context("YAML serializer") + +test_that("YAML serializes properly", { + l <- list(a=1, b=2, c="hi") + val <- serializer_yaml()(l, list(), PlumberResponse$new(), stop) + expect_equal(val$status, 200L) + expect_equal(val$headers$`Content-Type`, "application/x-yaml") + expect_equal(val$body, yaml::as.yaml(l)) + + l <- list(a=1, b=2, c="hi", na=NA) + val <- serializer_yaml()(l, list(), PlumberResponse$new(), stop) + expect_equal(val$status, 200L) + expect_equal(val$headers$`Content-Type`, "application/x-yaml") + expect_equal(val$body, yaml::as.yaml(l)) + + l <- list(a=1, b=2, c="hi", na=NA) + val <- serializer_yaml(indent = 4)(l, list(), PlumberResponse$new(), stop) + expect_equal(val$status, 200L) + expect_equal(val$headers$`Content-Type`, "application/x-yaml") + expect_equal(val$body, yaml::as.yaml(l, indent = 4)) +}) + +test_that("Errors call error handler", { + errors <- 0 + errHandler <- function(req, res, err){ + errors <<- errors + 1 + } + + expect_equal(errors, 0) + serializer_yaml()(parse(text="hi"), list(), PlumberResponse$new("yaml"), err = errHandler) + expect_equal(errors, 1) +}) From 47135dfa250996c3058b719a78d39c132e50ac63 Mon Sep 17 00:00:00 2001 From: Bruno Tremblay Date: Mon, 22 Jun 2020 01:01:24 -0400 Subject: [PATCH 2/7] news --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 02d607cfd..98bb13024 100644 --- a/NEWS.md +++ b/NEWS.md @@ -42,6 +42,8 @@ plumber 0.5.0 ### New features +* Add yaml support, serializer and parser. (@meztez) + * Added Swagger support for array parameters using syntax `name:[type]` and new type `list` (synonym df, data.frame). (@meztez, #532) * Added support for promises in endpoints, filters, and hooks. (#248) From a8cdc2072fe3367bc8660ae1be974b2286212d31 Mon Sep 17 00:00:00 2001 From: Bruno Tremblay Date: Mon, 22 Jun 2020 14:08:29 -0400 Subject: [PATCH 3/7] Update NEWS.md Co-authored-by: Barret Schloerke --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 98bb13024..9bd843905 100644 --- a/NEWS.md +++ b/NEWS.md @@ -42,7 +42,7 @@ plumber 0.5.0 ### New features -* Add yaml support, serializer and parser. (@meztez) +* Add yaml support, serializer and parser. (@meztez, #556) * Added Swagger support for array parameters using syntax `name:[type]` and new type `list` (synonym df, data.frame). (@meztez, #532) From 929a88369e1f5baf8ac2f9dc5b914699015d7ad4 Mon Sep 17 00:00:00 2001 From: Bruno Tremblay Date: Mon, 22 Jun 2020 14:08:53 -0400 Subject: [PATCH 4/7] Update tests/testthat/test-serializer-yaml.R Co-authored-by: Barret Schloerke --- tests/testthat/test-serializer-yaml.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/testthat/test-serializer-yaml.R b/tests/testthat/test-serializer-yaml.R index c88e98f99..6b6112491 100644 --- a/tests/testthat/test-serializer-yaml.R +++ b/tests/testthat/test-serializer-yaml.R @@ -1,6 +1,8 @@ context("YAML serializer") test_that("YAML serializes properly", { + skip_if_not_installed("yaml") + l <- list(a=1, b=2, c="hi") val <- serializer_yaml()(l, list(), PlumberResponse$new(), stop) expect_equal(val$status, 200L) From 1d6bcd5124975533aca93d18669316d2ecdf901b Mon Sep 17 00:00:00 2001 From: Bruno Tremblay Date: Mon, 22 Jun 2020 14:09:17 -0400 Subject: [PATCH 5/7] Update tests/testthat/test-serializer-yaml.R Co-authored-by: Barret Schloerke --- tests/testthat/test-serializer-yaml.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/testthat/test-serializer-yaml.R b/tests/testthat/test-serializer-yaml.R index 6b6112491..385bffca7 100644 --- a/tests/testthat/test-serializer-yaml.R +++ b/tests/testthat/test-serializer-yaml.R @@ -23,6 +23,8 @@ test_that("YAML serializes properly", { }) test_that("Errors call error handler", { + skip_if_not_installed("yaml") + errors <- 0 errHandler <- function(req, res, err){ errors <<- errors + 1 From 531c752a932496fe30422fbdedb0ab32d6423b83 Mon Sep 17 00:00:00 2001 From: Bruno Tremblay Date: Mon, 22 Jun 2020 14:09:31 -0400 Subject: [PATCH 6/7] Update tests/testthat/test-postbody.R Co-authored-by: Barret Schloerke --- tests/testthat/test-postbody.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/testthat/test-postbody.R b/tests/testthat/test-postbody.R index af97a12fb..1cce315b9 100644 --- a/tests/testthat/test-postbody.R +++ b/tests/testthat/test-postbody.R @@ -50,6 +50,8 @@ test_that("Test text parser", { }) test_that("Test yaml parser", { + skip_if_not_installed("yaml") + r_object <- list(a=1,b=list(c=2,d=list(e=3,f=4:6))) expect_equal(parseBody(charToRaw(yaml::as.yaml(r_object)), "application/x-yaml"), r_object) }) From ff27647b507346163fc96b37db9efde2970a9a03 Mon Sep 17 00:00:00 2001 From: Bruno Tremblay Date: Mon, 22 Jun 2020 16:38:23 -0400 Subject: [PATCH 7/7] remove dots from parser --- R/post-parsers.R | 20 +++++++++----------- man/addParser.Rd | 4 ++-- man/parsers.Rd | 17 +++++++---------- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/R/post-parsers.R b/R/post-parsers.R index e529caf13..03b4f3a87 100644 --- a/R/post-parsers.R +++ b/R/post-parsers.R @@ -29,7 +29,7 @@ NULL #' to build parser are `value`, `content_type` and `filename` (only available #' in `multipart-form` body). #' ```r -#' parser <- function(...) { +#' parser <- function() { #' function(value, content_type = "ct", filename, ...) { #' # do something with raw value #' } @@ -40,7 +40,7 @@ NULL #' plumber endpoint function args. #' #' @examples -#' parser_json <- function(...) { +#' parser_json <- function() { #' function(value, content_type = "application/json", ...) { #' charset <- getCharacterSet(content_type) #' value <- rawToChar(value) @@ -68,9 +68,8 @@ addParser <- function(name, parser, pattern = NULL) { #' JSON #' @rdname parsers -#' @param ... Raw values and headers are passed there. #' @export -parser_json <- function(...) { +parser_json <- function() { function(value, content_type = NULL, ...) { charset <- getCharacterSet(content_type) value <- rawToChar(value) @@ -85,7 +84,7 @@ parser_json <- function(...) { #' QUERY STRING #' @rdname parsers #' @export -parser_query <- function(...) { +parser_query <- function() { function(value, content_type = NULL, ...) { charset <- getCharacterSet(content_type) value <- rawToChar(value) @@ -100,7 +99,7 @@ parser_query <- function(...) { #' TEXT #' @rdname parsers #' @export -parser_text <- function(...) { +parser_text <- function() { function(value, content_type = NULL, ...) { charset <- getCharacterSet(content_type) value <- rawToChar(value) @@ -115,7 +114,7 @@ parser_text <- function(...) { #" RDS #' @rdname parsers #' @export -parser_rds <- function(...) { +parser_rds <- function() { function(value, filename, ...) { tmp <- tempfile("plumb", fileext = paste0("_", basename(filename))) on.exit(file.remove(tmp), add = TRUE) @@ -131,7 +130,7 @@ parser_rds <- function(...) { #' @rdname parsers #' @export #' @importFrom webutils parse_multipart -parser_multi <- function(...) { +parser_multi <- function() { function(value, content_type, ...) { if (!stri_detect_fixed(content_type, "boundary=", case_insensitive = TRUE)) stop("No boundary found in multipart content-type header: ", content_type) @@ -152,9 +151,8 @@ parser_multi <- function(...) { #' OCTET #' @rdname parsers -#' @param ... Raw values and headers are passed there. #' @export -parser_octet <- function(...) { +parser_octet <- function() { function(value, filename = NULL, ...) { attr(value, "filename") <- filename return(value) @@ -167,7 +165,7 @@ parser_octet <- function(...) { #' YAML #' @rdname parsers #' @export -parser_yaml <- function(...) { +parser_yaml <- function() { if (!requireNamespace("yaml", quietly = TRUE)) { stop("yaml must be installed for the yaml parser to work") } diff --git a/man/addParser.Rd b/man/addParser.Rd index de3941aa9..b24966c18 100644 --- a/man/addParser.Rd +++ b/man/addParser.Rd @@ -28,7 +28,7 @@ case sensitive. Parser function structure is something like below. Available parameters to build parser are \code{value}, \code{content_type} and \code{filename} (only available -in \code{multipart-form} body).\if{html}{\out{
}}\preformatted{parser <- function(...) \{ +in \code{multipart-form} body).\if{html}{\out{
}}\preformatted{parser <- function() \{ function(value, content_type = "ct", filename, ...) \{ # do something with raw value \} @@ -39,7 +39,7 @@ It should return a named list if you want values to map to plumber endpoint function args. } \examples{ -parser_json <- function(...) { +parser_json <- function() { function(value, content_type = "application/json", ...) { charset <- getCharacterSet(content_type) value <- rawToChar(value) diff --git a/man/parsers.Rd b/man/parsers.Rd index 8f1732db9..f00adbb94 100644 --- a/man/parsers.Rd +++ b/man/parsers.Rd @@ -11,22 +11,19 @@ \alias{parser_yaml} \title{Plumber Parsers} \usage{ -parser_json(...) +parser_json() -parser_query(...) +parser_query() -parser_text(...) +parser_text() -parser_rds(...) +parser_rds() -parser_multi(...) +parser_multi() -parser_octet(...) +parser_octet() -parser_yaml(...) -} -\arguments{ -\item{...}{Raw values and headers are passed there.} +parser_yaml() } \description{ Parsers are used in Plumber to transform the raw body content received