11registry $ parsers <- list ()
22
3- # ' Register a parser to a name for use with the `@ parser` tag
3+ # ' Register or fetch a parser
44# '
55# ' plumber2 comes with many parsers that should cover almost all standard
66# ' use cases. Still you might want to provide some of your own, which this
77# ' function facilitates.
88# '
9+ # ' If you want to register your own parser, then the function you register must
10+ # ' be a factory function, ie. a function returning a function. The returned
11+ # ' function must accept two arguments, the first being a raw vector
12+ # ' corresponding to the request body, the second being the parsed directives
13+ # ' from the request `Content-Type` header. All arguments to the factory function
14+ # ' should be optional.
15+ # '
916# ' @param name The name to register the parser function to. If already
1017# ' present the current parser will be overwritten by the one provided by you
1118# ' @param fun A function that, when called, returns a binary function that can
@@ -15,19 +22,114 @@ registry$parsers <- list()
1522# ' @param mime_types One or more mime types that this parser can handle. The
1623# ' mime types are allowed to contain wildcards, e.g. `"text/*"`
1724# '
18- # ' @return This function is called for its side effects
25+ # ' @return For `get_parsers` a named list of parser functions named by their
26+ # ' mime types. The order given in `parsers` is preserved.
1927# '
28+ # ' @seealso [parsers]
2029# ' @seealso [register_serializer()]
2130# ' @export
2231# '
2332register_parser <- function (name , fun , mime_types ) {
2433 check_function(fun )
2534 check_character(mime_types )
35+ check_string(name )
36+ if (grepl(" /" , name , fixed = TRUE )) {
37+ cli :: cli_abort(
38+ " {.arg name} must not contain the forward slash character ({.field /})"
39+ )
40+ }
41+ if (name %in% c(" ..." , " none" )) {
42+ cli :: cli_abort(
43+ " {.arg name} must not be {.val {c('...', 'none')}}"
44+ )
45+ }
2646 registry $ parsers [[name ]] <- list (fun = fun , types = mime_types )
2747 invisible (NULL )
2848}
2949
30- get_parsers <- function (types = NULL , env = caller_env()) {
50+ # ' @rdname register_parser
51+ # ' @param parsers Parsers to collect. This can either be a character vector of
52+ # ' names of registered parsers or a list. If it is a list then the following
53+ # ' expectations apply:
54+ # ' * Any unnamed elements containing a character vector will be considered as
55+ # ' names of registered parsers constructed with default values. The special
56+ # ' value `"..."` will fetch all the parsers that are otherwise not specified
57+ # ' in the call
58+ # ' * Any element containing a function are considered as a provided parser and
59+ # ' the element must be named by the mime type the parser understands
60+ # ' (wildcards allowed)
61+ # ' * Any remaining named elements will be considered names of registered parsers
62+ # ' that should be constructed with the arguments given in the element
63+ # '
64+ # ' @export
65+ get_parsers <- function (parsers = NULL ) {
66+ if (is.null(parsers )) {
67+ parsers <- names(registry $ parsers )
68+ }
69+ elem_names <- names(parsers ) %|| % rep_along(parsers , " " )
70+ named_parsers <- unlist(lapply(seq_along(parsers ), function (i ) {
71+ if (elem_names [i ] == " " ) {
72+ if (is_character(parsers [[i ]])) {
73+ parsers [[i ]]
74+ } else {
75+ NULL
76+ }
77+ } else {
78+ elem_names [i ]
79+ }
80+ }))
81+ if (sum(named_parsers == " ..." ) > 1 ) {
82+ cli :: cli_abort(" {.val ...} can only be used once in {.arg parsers}" )
83+ }
84+ named_parsers <- named_parsers [! grepl(" /|^\\ .\\ .\\ .$" , named_parsers )]
85+ dots_parsers <- setdiff(names(registry $ parsers ), named_parsers )
86+ parsers <- lapply(seq_along(parsers ), function (i ) {
87+ if (is_function(parsers [[i ]])) {
88+ if (length(fn_fmls(parsers [[i ]])) != 2 ) {
89+ cli :: cli_abort(
90+ " Provided parsers must be binary functions"
91+ )
92+ }
93+ if (! grepl(" /" , elem_names [i ], fixed = TRUE )) {
94+ cli :: cli_abort(
95+ " Parsers provided as functions must be named by their mime type"
96+ )
97+ }
98+ return (list2(!! elem_names [i ] : = parsers [[i ]]))
99+ }
100+ if (elem_names [i ] == " " && is_character(parsers [[i ]])) {
101+ if (any(grepl(" /" , parsers [[i ]], fixed = TRUE ))) {
102+ cli :: cli_abort(" mime types must be provided with a function" )
103+ }
104+ return (get_parsers_internal(
105+ parsers [[i ]],
106+ env = env ,
107+ dots_parsers = dots_parsers
108+ ))
109+ }
110+ if (elem_names [i ] != " " ) {
111+ if (is.null(registry $ parsers [[elem_names [i ]]])) {
112+ cli :: cli_abort(
113+ " No parser registered with {.val {elem_names[i]}} as name"
114+ )
115+ }
116+ if (! is.list(parsers [[i ]])) parsers [[i ]] <- list (parsers [[i ]])
117+ funs <- rep_named(
118+ registry $ parsers [[elem_names [i ]]]$ types ,
119+ list (registry $ parsers [[elem_names [i ]]]$ fun )
120+ )
121+ return (lapply(funs , function (f ) inject(f(!!! parsers [[i ]]))))
122+ }
123+ cli :: cli_abort(" Don't know how to parse element {i} in {.arg parsers}" )
124+ })
125+ unlist(parsers , recursive = FALSE )
126+ }
127+
128+ get_parsers_internal <- function (
129+ types = NULL ,
130+ env = caller_env(),
131+ dots_parsers = NULL
132+ ) {
31133 if (isTRUE(tolower(types ) == " none" )) {
32134 return (NULL )
33135 }
@@ -38,19 +140,19 @@ get_parsers <- function(types = NULL, env = caller_env()) {
38140 if (length(dots ) != 0 ) {
39141 types <- c(
40142 types [seq_len(dots - 1 )],
41- setdiff(names(registry $ parsers ), types ),
143+ dots_parsers % || % setdiff(names(registry $ parsers ), types ),
42144 types [dots + seq_len(length(types ) - dots )]
43145 )
44146 }
45147 parsers <- lapply(types , function (type ) {
46148 type <- stringi :: stri_split_fixed(type , " " , n = 2 )[[1 ]]
47149 if (stringi :: stri_count_fixed(type [[1 ]], " /" ) == 1 ) {
48150 parser_fun <- if (length(type ) == 2 )
49- eval_bare(parse_expr(type [2 ]), env = env ) else identity
151+ eval_bare(parse_expr(type [2 ]), env = env ) else function ( x , ... ) x
50152 check_function(parser_fun )
51153 parser <- list (
52154 fun = parser_fun ,
53- type = type [1 ]
155+ types = type [1 ]
54156 )
55157 } else {
56158 parser <- registry $ parsers [[type [[1 ]]]]
@@ -82,51 +184,132 @@ get_parsers <- function(types = NULL, env = caller_env()) {
82184}
83185
84186# Default parsers --------------------------------------------------------------
187+
188+ # ' Parser functions provided by plumber2
189+ # '
190+ # ' These functions cover a large area of potential request body formats. They
191+ # ' are all registered to their standard mime types but users may want to use
192+ # ' them to register them to alternative types if they know it makes sense.
193+ # '
194+ # ' # Provided parsers
195+ # ' * `parse_csv()` uses [readr::read_csv()] for parsing. It is registered as
196+ # ' `"csv"` for the mime types `application/csv`, `application/x-csv`,
197+ # ' `text/csv`, and `text/x-csv`
198+ # ' * `parse_octet()` passes the raw data through unchanged. It is registered as
199+ # ' `"octet"` for the mime type `application/octet-stream`
200+ # ' * `parse_rds()` uses [unserialize()] for parsing. It is registered as
201+ # ' `"rds"` for the mime type `application/rds`
202+ # ' * `parse_feather()` uses [arrow::read_feather()] for parsing. It is
203+ # ' registered as `"feather"` for the mime types
204+ # ' `application/vnd.apache.arrow.file` and `application/feather`
205+ # ' * `parse_parquet()` uses [arrow::read_parquet()] for parsing. It is
206+ # ' registered as `"parquet"` for the mime type `application/vnd.apache.parquet`
207+ # ' * `parse_text()` uses [rawToChar()] for parsing. It is registered as
208+ # ' `"text"` for the mime types `text/plain` and `text/*`
209+ # ' * `parse_tsv()` uses [readr::read_tsv()] for parsing. It is registered as
210+ # ' `"tsv"` for the mime types `application/tab-separated-values` and
211+ # ' `text/tab-separated-values`
212+ # ' * `parse_yaml()` uses [yaml::yaml.load()] for parsing. It is registered as
213+ # ' `"yaml"` for the mime types `text/vnd.yaml`, `application/yaml`,
214+ # ' `application/x-yaml`, `text/yaml`, and `text/x-yaml`
215+ # ' * `parse_geojson()` uses [geojsonsf::geojson_sf()] for parsing. It is
216+ # ' registered as `"geojson"` for the mime types `application/geo+json` and
217+ # ' `application/vdn.geo+json`
218+ # '
219+ # ' ## Additional registered parsers
220+ # ' * [reqres::parse_json()] is registered as "`json`" for the mime types
221+ # ' `application/json` and `text/json`
222+ # ' * [reqres::parse_multiform()] is registered as "`multi`" for the mime
223+ # ' type `multipart/*`
224+ # ' * [reqres::parse_queryform()] is registered as "`form`" for the mime type
225+ # ' `application/x-www-form-urlencoded`
226+ # '
227+ # ' @param ... Further argument passed on to the internal parsing function. See
228+ # ' Details for information on which function handles the parsing internally in
229+ # ' each parser
230+ # '
231+ # ' @return A function accepting a raw vector along with a `directives` argument
232+ # ' that provides further directives from the `Content-Type` to be passed along
233+ # '
234+ # ' @seealso [register_parser()]
235+ # ' @rdname parsers
236+ # ' @name parsers
237+ # '
238+ NULL
239+
240+ # ' @rdname parsers
241+ # ' @export
242+ # '
85243parse_csv <- function (... ) {
86244 check_installed(" readr" )
87245 function (raw , directives ) {
88246 readr :: read_csv(raw , ... )
89247 }
90248}
249+ # ' @rdname parsers
250+ # ' @export
251+ # '
91252parse_octet <- function () {
92253 function (raw , directives ) {
93254 raw
94255 }
95256}
257+ # ' @rdname parsers
258+ # ' @export
259+ # '
96260parse_rds <- function (... ) {
97261 function (raw , directives ) {
98262 unserialize(raw , ... )
99263 }
100264}
265+ # ' @rdname parsers
266+ # ' @export
267+ # '
101268parse_feather <- function (... ) {
102269 check_installed(" arrow" )
103270 function (raw , directives ) {
104271 arrow :: read_feather(raw , ... )
105272 }
106273}
274+ # ' @rdname parsers
275+ # ' @export
276+ # '
107277parse_parquet <- function (... ) {
108278 check_installed(" arrow" )
109279 function (raw , directives ) {
110280 arrow :: read_parquet(raw , ... )
111281 }
112282}
283+ # ' @rdname parsers
284+ # ' @inheritParams base::rawToChar
285+ # ' @export
286+ # '
113287parse_text <- function (multiple = FALSE ) {
114288 function (raw , directives ) {
115289 rawToChar(raw , multiple = multiple )
116290 }
117291}
292+ # ' @rdname parsers
293+ # ' @export
294+ # '
118295parse_tsv <- function (... ) {
119296 check_installed(" readr" )
120297 function (raw , directives ) {
121298 readr :: read_tsv(raw , ... )
122299 }
123300}
301+ # ' @rdname parsers
302+ # ' @export
303+ # '
124304parse_yaml <- function (... ) {
125305 check_installed(" yaml" )
126306 function (raw , directives ) {
127307 yaml :: yaml.load(rawToChar(raw ), eval.expr = FALSE , ... )
128308 }
129309}
310+ # ' @rdname parsers
311+ # ' @export
312+ # '
130313parse_geojson <- function (... ) {
131314 check_installed(" geojsonsf" )
132315 function (raw , directives ) {
0 commit comments