api() |>
api_doc_add(list(
info = list(
title = "Sample Pet Store App",
description = "This is a sample server for a pet store.",
termsOfService = "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"
),
tags = list(
list(name = "pet", description = "Pets operations"),
list(name = "toy", description = "Toys operations"),
list(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
.
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 NULL
expression. Instead of @apiTitle
and @apiDescription
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 |
---|---|---|
@apiTitle |
Title |
Info Object |
@apiDescription |
Description |
Info Object |
@apiTOS |
TOS link |
Info Object |
@apiContact |
Contact object |
Contact Object |
@apiLicense |
License object |
License Object |
@apiVersion |
Version |
Info Object |
@apiTag |
Tag Description
|
Can be repeated to add multiple tags. Quote with ” or ’ to use non word character (like spaces) in Tag . Tag Object
|
Annotations example
#* Sample Pet Store App
#*
#* This is a sample server for a pet store.
#*
#* @apiTOS http://example.com/terms/
#* @apiContact list(name = "API Support", url = "http://www.example.com/support", email = "support@example.com")
#* @apiLicense list(name = "Apache 2.0", url = "https://www.apache.org/licenses/LICENSE-2.0.html")
#* @apiVersion 1.0.1
#* @apiTag pet Pets operations
#* @apiTag toy Toys operations
#* @apiTag "toy space" Toys operations
NULL
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 |
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
|
[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)
).
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 = list(
parameters = list(
list(
`in` = "query",
name = "name",
schema = list(type = "string"),
required = TRUE
),
list(
`in` = "query",
name = "age",
schema = list(type = "integer"),
required = TRUE
)
)
)
) |>
api_get(
path = "/dyn/<name:string>/<age:integer>/route",
handler = text_handler,
serializers = get_serializers("text"),
doc = list(
responses = list(
"200" = list(
description = "A sentence",
content = list(
"text/plain" = list(schema = list(
type = "string"
))
)
)
)
)
) |>
api_post(
path = "/upload_file",
handler = file_handler,
serializers = get_serializers("rds"),
parsers = get_parsers("multi"),
doc = list(
description = "Upload an rds file and return the object",
requestBody = list(
content = list(
"multipart/form-data" = list(schema = list(
type = "object",
properties = list(
file = list(
type = "string",
format = "binary"
)
)
))
)
)
)
) |>
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")
Plumber annotation
Annotation | Arguments | Description/References |
---|---|---|
@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
#* @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")