api() |>
api_doc_add(
openapi(
info = openapi_info(
title = "Sample Pet Store App",
description = "This is a sample server for a pet store.",
terms_of_service = "http://example.com/terms/",
contact = openapi_contact(name = "API Support", url = "http://www.example.com/support", email = "support@example.com"),
license = openapi_license(name = "Apache 2.0", url = "https://www.apache.org/licenses/LICENSE-2.0.html"),
version = "1.0.1"
),
tags = list(
openapi_tag(name = "pet", description = "Pets operations"),
openapi_tag(name = "toy", description = "Toys operations"),
openapi_tag(name = "toy space", description = "Toys operations")
)
))
Annotations
Annotations are specially-structured comments used in your plumber file to create an API. A full annotation line starts with #*
, then the annotation keyword @...
, any number of space characters followed by the content. If you wish to use the annotation to document your API file but don’t want any OpenAPI documentation to be generated you can use @noDoc
tag which works much like roxygens @noRd
.
Specifying route name
By default, the name of the file (without extension) is used as the name for the route any handlers etc are attached to. However, you can change this by having a first block in your file with a @routeName
tag specifying the name to use for all route specific blocks in the file. This also makes it possible to split out the specification of a single route among multiple files if it begins to grow unwieldy
Annotation | Argument | Description/References |
---|---|---|
@routeName |
name |
Sets the name of the route to use for all blocks in the file |
Annotations example
#* @routeName main_route
NULL
Equivalent programmatic usage
There is no direct equivalent in programmatic usage as this pertains to the parsing of the plumber file. Any relevant function has a route
argument where you can specify which route to add the given functionality to.
Global annotations
Global annotations are not related to any handler and should be placed in their own block. The block should be terminated by a "_API"
expression. Instead of @title
and @description
you can also use the convention that the first line gives the title and any proceeding lines until the first tag gives the description.
Annotation | Argument | Description/References |
---|---|---|
@title |
Title |
Info Object |
@description |
Description |
Info Object |
@tos |
TOS link |
Info Object |
@contact |
Contact object |
Contact Object |
@license |
License object |
License Object |
@version |
Version |
Info Object |
@tag |
Tag Description
|
Can be repeated to add multiple tags. Quote with ” or ’ to use non word character (like spaces) in Tag . Tag Object
|
@noDoc |
None | Don’t generate OpenAPI documentation from this block |
Annotations example
#* Sample Pet Store App
#*
#* This is a sample server for a pet store.
#*
#* @tos http://example.com/terms/
#* @contact list(name = "API Support", url = "http://www.example.com/support", email = "support@example.com")
#* @license list(name = "Apache 2.0", url = "https://www.apache.org/licenses/LICENSE-2.0.html")
#* @version 1.0.1
#* @tag pet Pets operations
#* @tag toy Toys operations
#* @tag "toy space" Toys operations
"_API"
Equivalent programmatic usage
Handler annotations
Handler annotation describe all aspects of a request handler and always proceeds a function which is considered the handler function. The following tags can be used in a handler block. The first line, unless it has a tag is considered the title of the handler and any proceeding lines until the first tag is considered a long-form description.
Endpoint
Annotation |
Argument |
Description/References |
---|---|---|
|
|
|
|
None |
Should the handler be attached to the header router |
|
|
Some serializers accept arguments. See serializers article and serializers reference. Aliases : |
|
None |
Turn on strict content negotiation. By default, the first serializer is chosen if the client requests a response type that isnt supported. By using strict content negotiation a 406 response is sent if the requested response type is not available |
|
|
Some parsers accept arguments. See parsers reference. Can be repeated to allow multiple parsers on the same endpoint. Aliases : |
|
|
Adding an asterix indicates that the parameter is required. Can be repeated to define different parameters. If a single |
|
|
Simple Response object. Can be repeated to define different responses. |
|
[ |
Mark the response as something that should be downloaded, optionally setting a default filename for the file |
|
|
Can be repeated to add multiple tags. Quote with or to use non word character (like spaces) in |
|
None |
Marks the handler as a WebSocket message handler. No other tags will have an effect if this tag is present (except |
|
[ |
Marks the handler as running asynchronously. The default uses mirai, but a different registered engine can be specified as well |
|
None |
Chains the handler to the preceeding message or request handler if that handler is asynchronous |
|
None |
Dont generate OpenAPI documentation from this block |
More details on Type
Types are used to define API inputs and outputs. For path parameters they can be given both in @param
and inside the handler path. If they are given both places they must be in agreement. For query and body parameters they are given in their respective @query
and @body
tags.
Some types can have a nested structure which is also supported, but the type spec can quickly become difficult to read the further you recurse so use with care
Type | OpenAPI |
---|---|
boolean |
boolean |
number |
number |
integer |
integer |
string |
string |
date |
string format:date
|
date-time |
string format:date-time
|
byte |
string format:byte
|
binary |
string format:binary
|
enum |
string enum:|...|
|
pattern |
string pattern:|...|
|
[Type] |
array items:type:Type
|
{prop_name: Type, prop_name2: Type2} |
object properties:prop_name:type:Type properties:prop_name2:type:Type2
|
Types can have a default value, which is given in parentheses after the type specification, e.g. integer(10)
. For objects and arrays you should use JSON notation to describe the default value (e.g. [integer]([2, 6, 1])
).
For the integer
and number
types it is also possible to specify the lower and/or upper bounds of the value. This is done by putting these between |
like so: integer|3, 7|
. Omitting one of these will remove that bound requirement (e.g. integer|,7|
only requires the input to be below or equal to 7). If combining this with a default value the range comes first (integer|3,7|(5)
).
The enum
type is a factor type and you must provide the valid values of the type in between |
, separated by comma and an optional whitespace. The pattern
type is a string type that must match the regular expression given between |
.
The date
and date-time
types require the use of a specific format for the date or time described in RFC 3339, section 5.6. The date must be given in full-date notation, e.g. 2017-07-21 while the date-time must be given in the date-time notation, e.g. 2017-07-21T17:32:28Z
parameters can be specified as optional or required in their type notation. Path parameters are always required so any setting is ignored for this. A parameter can be marked as required by adding a *
suffix to the type description, e.g. arg1:integer*
to indicate that arg1
is required and an integer. A parameter cannot both be required and have a default value (for reasons of logic).
Apart from being used in the documentation of your API, the type information you provide for parameters will also be used to cast the incoming values to the correct type and add defaults if missing. Further, missing required parameters will result in an error. The response is not type checked so it is up to you to conform with the specification you provide.
Annotations example
#* @get /query/parameters
#* @serializer text
#* @query name:string*
#* @query age:integer*
function(query) {
sprintf("%s is %i years old", query$name, max(query$age))
}
#* @get /dyn/<name:string>/<age:integer>/route
#* @serializer text
#* @parser none
#* @response 200:string A sentence
function(name, age) {
sprintf("%s is %i years old", name, age)
}
#* Upload a file and return the object as an rds
#*
#* @post /upload_file
#* @serializer rds
#* @parser multi
#* @body file:binary A file
#* @download
function(body) {
body$file
}
#* @message
function(message, client_id, server) {
if (is.character(message)) {
server$log("message", paste0(client_id, " says ", message))
}
NULL
}
Equivalent programmatic usage
text_handler <- function(name, age) {
sprintf("%s is %i years old", name, max(age))
}
qp_handler <- function(query) {
text_handler(query$name, query$age)
}
file_handler <- function(body) {
body$f
}
msg_handler <- function(message, client_id, server) {
if (is.character(message)) {
server$log("message", paste0(client_id, " says ", message))
}
NULL
}
api() |>
api_get(
path = "/query/parameters",
handler = qp_handler,
serializers = get_serializers("text"),
parsers = get_parsers(),
doc = openapi_operation(
parameters = list(
openapi_parameter(
name = "name",
location = "query",
required = TRUE,
schema = openapi_schema(character())
),
openapi_parameter(
name = "age",
location = "query",
required = TRUE,
schema = openapi_schema(integer())
)
)
)
) |>
api_get(
path = "/dyn/<name:string>/<age:integer>/route",
handler = text_handler,
serializers = get_serializers("text"),
doc = openapi_operation(
responses = list(
"200" = openapi_response(
description = "A sentence",
content = openapi_content(
"text/plain" = openapi_schema(character())
)
)
)
)
) |>
api_post(
path = "/upload_file",
handler = file_handler,
serializers = get_serializers("rds"),
parsers = get_parsers("multi"),
doc = openapi_operation(
description = "Upload an rds file and return the object",
request_body = openapi_request_body(
content = openapi_content(
"multipart/form-data" = openapi_schema(list(file = raw()))
)
)
)
) |>
api_message(msg_handler)
Asset annotation
There are two ways to serve static content in plumber2 and they differ in subtle ways. The @assets
tag instruct plumber to create a regular handler that matches the mount path (defaults to /
) and will serve files from Path
. The @statics
tag works the same, but rather than create a handler it instructs httpuv (which is the low-level package powering plumber2) to serve the files before the request even reaches the R process. This makes it much faster but also limited in flexibility since the request never reaches your code and you are unable to modify it. In general, you should use @statics
unless you need to provide any additional handling of the request, such as authentication or logging.
Annotation | Arguments | Description/References |
---|---|---|
@assets |
Path [Mount path ] |
Static files |
@statics |
Path [Mount path ] |
|
@except |
Path |
Can be used together with @statics to exclude subpaths of the @statics Path from being served. |
Annotations example
#* @assets ./assets/files
NULL
#* @assets ./assets/files /assets
NULL
#* @statics ./assets/static_files
#* @except /secret_files
NULL
Equivalent programmatic usage (note that argument order is reversed)
api() %>%
api_assets("/", "./assets/files")
api() %>%
api_assets("/assets", "./assets/files")
api() %>%
api_statics("/", "./assets/static_files", except = "/secret_files")
plumber2 annotation
Annotation | Arguments | Description/References |
---|---|---|
@plumber |
None | Modify plumber router from plumber file. If the function returns a plumber2 api object then this object will be used going forward, otherwise the return value is ignored. 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
#* @plumber
function(api) {
api %>%
api_doc_setting("swagger")
}
# Named function
use_swagger <- function(api) {
api %>%
api_doc_setting("swagger")
}
#* @plumber
use_swagger
Equivalent programmatic usage
api() |>
api_doc_setting("swagger")
Forwards and redirects
plumber2 allows you to orchestrate requests that are ultimately handled elsewhere. There are two approaches to this. Either return a redirect response and let the client follow that to the new location, or handle it directly by forwarding the request to another origin and passing along the response to the client (this is called a reverse proxy). Both of these are considered somewhat advanced use.
Annotation | Arguments | Description/References |
---|---|---|
@redirect |
[!]Method From To
|
Will add a redirect response for the method given as Method from the path given in From to the path given in To . Path parameters are supported and will be matched between Trom and To . If the method is preceded by a ! then the redirect is considered permanent (308) — otherwise it is considered temporary (307) |
@forward |
Path URL
|
Will forward requests from Path to URL , acting as a reverse proxy. The proxy will also forward any WebSocket messaging that is established with Path . All forwarding is performed asynchronously so the api will not block if the URL takes a long time to respond. |
Annotation example
#* @redirect !any old/<path> new/<path>
#* @redirect get main/<path> temp/main/<path>
NULL
#* @forward proxy/server http://127.0.0.1:56789
NULL
Equivalent programmatic usage
api() |>
api_redirect("any", "old/<path>", "new/<path>", permanent = TRUE) |>
api_redirect("get", "main/*", "temp/main/*") |>
api_forward("proxy/server", "http://127.0.0.1:56789")
Shiny
plumber2 can serve one or more shiny apps at specified paths. It works by launching the shiny app defined below the block in a separate process and then forward requests to the defined path to the process. The shiny app is automatically launched and stopped along with the main plumber2 api.
Annotation | Arguments | Description/References |
---|---|---|
@shiny |
Path |
Launc the app defined below the annotation block and forward requests to Path to it |
Annotation example
#* @shiny app/
shinyAppDir("path/to/shiny/app")
Equivalent programmatic usage
Reports
plumber2 can automatically serve Rmarkdown and Quarto reports. The report will be rendered upon request but cached for the future. Different output formats can be selected through the Content-Type
request header as long as a matching format is specified in the reports yaml header. Query parameters are automatically passed in as parameters to the report to support parameterized reports natively. Reports can be annotated just like standard request handlers to have OpenAPI documentation generated for it. Rendering is performed asynchronously so the api is not blocked by someone requesting a report that takes a while to render
Annotation | Arguments | Description/References |
---|---|---|
@report |
Path |
Serve a report given by the path specified below the annotation block from Path . The report location is relative to the path of the annotation file |
Annotation example
#* Access the quarterly report
#*
#* @query quarter:enum|spring, summer, autumn, winter| The quarter to generate the report for
#*
#* @report quarterly/
"my_amazing_report.qmd"
Equivalent programmatic usage
api() |>
api_add_route(routr::report_route("quarterly/", "my_amazing_report.qmd"))
Note that the programmatic usage doesn’t add any documentation by itself