--- title: "Variant 3 — Plumber Drop-In (`drogonR::pr_run`)" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Variant 3 — Plumber Drop-In} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set(eval = FALSE, comment = "#>") ``` If you already have a plumber service, the shim lets you run it under drogonR by changing one line. The shim parses the plumber router into drogonR routes and dispatches them through `dr_serve()`. Existing handlers, paths, and parameter types keep working. For the overall picture see `vignette("drogonR", package = "drogonR")`. --- ## The one-line swap Existing plumber code: ```r library(plumber) pr <- pr() |> pr_get ("/users/", function(id) list(id = id, ok = TRUE)) |> pr_post("/users", function(req) { body <- jsonlite::fromJSON(req$postBody) list(created = body$name) }) plumber::pr_run(pr, port = 8080L, docs = FALSE) # <-- before ``` Becomes: ```r drogonR::pr_run(pr, port = 8080L, docs = FALSE) # <-- after ``` That's the whole change. `docs`, `swagger`, `swaggerCallback`, `quiet` are silently accepted and ignored (the shim has no swagger surface, so the flags are inapplicable but valid). Other arguments — `threads`, `workers`, `max_queue` — forward to `dr_serve()`. --- ## What the shim supports * `@get`, `@post`, `@put`, `@delete` annotations and the `pr_get/post/put/delete()` helpers. * Path placeholders `` and ``. Recognised types are `int` / `integer`, `dbl` / `double` / `numeric`, `bool` / `logical`; anything else passes through as character. Coercion runs only on path parameters — query and body values keep plumber's untyped shape (string for query, parsed-JSON for body). * Handler argument resolution by name: `path > query > JSON body`, with `req` injected if the handler declares a `req` parameter. * Plumber 1.x default serialisation: `jsonlite::toJSON(auto_unbox = FALSE)` for every return value. Bare strings become JSON arrays (`["hello"]`), exactly as plumber sends them — byte-level parity with `plumber::pr_run()`. If you've already built a JSON string with `jsonlite::toJSON()`, it's emitted verbatim. * Returning a `dr_response()` / `dr_json()` / `dr_text()` from a handler opts out of the default serializer and is forwarded as-is — useful for incrementally migrating hot endpoints to drogonR's response shape without leaving the shim. --- ## What the shim rejects Each of these triggers an explicit error at `pr_run()` time, before any route is registered, so failure is loud: * **`@filter` / `pr_filter()`** — user-defined filters. Rewrite as middleware via `dr_use()` (see `vignette("mode-native")`). * **`pr_hook()` / `@hook`** — preroute / postroute / postserialize hooks. Same migration path as filters. * **`pr_mount()` / sub-routers** — composing one router from several. Flatten into a single `pr()` (or move to native `dr_app()`). * **Custom parsers / serialisers** — every response goes through the default plumber JSON serializer. Build the response yourself with `dr_response(headers = list("Content-Type" = "..."))` if you need another format. * **`PlumberResponse` / `PlumberFile` return values** — return a list / data.frame for JSON, a string for text, or a `dr_response()` for full control. * **The `res` parameter in handlers** — plumber-style mutation of a passed-in `res` object isn't supported. The shim warns once per affected route at `pr_run()` time. Set status / headers via the return value (`dr_response(...)`). * **Async handlers, websockets, OpenAPI / swagger assets** — out of scope for the shim. --- ## A minimal end-to-end example ```r library(plumber) library(drogonR) pr <- pr() |> pr_get ("/health", function() list(ok = TRUE)) |> pr_get ("/users/", function(id) { list(id = id, type = typeof(id)) # id arrives as integer }) |> pr_post("/echo", function(req) { list(received = jsonlite::fromJSON(req$postBody)) }) drogonR::pr_run(pr, port = 8080L, docs = FALSE) ``` Three responses your client will see: ``` GET /health -> {"ok":[true]} GET /users/42 -> {"id":[42],"type":["integer"]} POST /echo {"a":1} -> {"received":{"a":[1]}} ``` The bracketed scalars are plumber's default serialiser (`auto_unbox = FALSE`) — preserved on purpose so existing clients don't break. To get unboxed JSON, return `dr_json(x, auto_unbox = TRUE)` from the handler; that bypasses the default serializer. --- ## When to migrate to native The shim is fine for steady-state plumber apps. Reach for the native API (`vignette("mode-native")`) if you want: * per-request middleware (auth, logging, CORS, rate limiting), * explicit control over status / headers / content-type without per-handler `dr_response()` calls, * response helpers (`dr_text`, `dr_html`, `dr_redirect`, `dr_file`), * the full request shape (`req$params`, `dr_query()`, `dr_body()`), * path placeholders with `:id` / `` / `{id}` syntax (the shim rewrites plumber's `` form internally). If your hot endpoint is C/C++-bound (model inference, embeddings), register that endpoint with `dr_get_cpp()` and leave the rest under the shim — see `vignette("mode-cpp-shared", package = "drogonR")`.