This version is in beta. Some features may change before release.

Playground plugin

Interactive API playground for umbral-rest endpoints - Postman-style request builder, schema introspection, per-endpoint stats.

umbral-playground mounts an interactive API explorer at /api/playground/. It reads the OpenAPI spec produced by umbral-openapi and gives you a navigable endpoint tree, a request builder with schema introspection, a response viewer, per-endpoint history, and a per-endpoint stats panel - all in the browser, no extra service to run.

Quick start

Code
rust
use umbral_playground::PlaygroundPlugin;
use umbral_rest::RestPlugin;
use umbral_openapi::OpenApiPlugin;
 
let app = App::builder()
.plugin(RestPlugin::default())
.plugin(OpenApiPlugin::new())
.plugin(PlaygroundPlugin::new("my_app"))
.build();

PlaygroundPlugin::new(app_name) takes an application name. The playground namespaces its browser-side storage (Dexie/IndexedDB, localStorage) per app so two umbral apps served to the same browser don't collide. PlaygroundPlugin::default() warns and falls back to "default". Move the mount with .at("/some/path").

Open http://localhost:8000/api/playground/ in a browser.

Warning

The playground is a request console that fires live REST calls with the visitor's ambient cookies. To avoid exposing that against a production API, the plugin does not mount under Environment::Prod by default - routes() returns an empty router and logs a warning. Opt in explicitly with PlaygroundPlugin::new("my_app").allow_in_prod() if you intend to run it in production (e.g. behind your own auth proxy).

Build requirements

The plugin ships a React + Vite UI. To produce the bundle, you need Node.js 20+ available so npm can install the frontend's dev dependencies once:

Code
bash
cd plugins/umbral-playground/frontend
npm install

A normal cargo build of the plugin runs npm run build (Vite) automatically through build.rs, writing the output into the crate's dist/ directory. The plugin's shell HTML is compiled into the binary, but the JS/CSS bundle itself is served off disk through umbral's unified static pipeline - the plugin contributes its dist/ directory via Plugin::static_dirs() under the playground namespace, so dist/assets/index.js is reachable at <static_url>playground/assets/index.js (/static/playground/assets/… with the default static_url). In Environment::Dev the pipeline serves the file live off disk on every request, so dropping a freshly-built bundle into dist/ is picked up on the next request with no Rust recompile; in prod the collected copy under <static_root>/playground/… is served.

Without Node available (the bundle couldn't be built), the plugin still compiles and serves a placeholder page explaining the recovery steps - the placeholder HTML is baked into the binary so the route always renders something.

What you get

Three-pane layout

  • Sidebar - endpoint tree grouped by tag with a searchable filter. Tag names are humanised (permissions_contenttypePermissions Contenttype).
  • Request builder - URL bar, send button, and tabs: Params · Body · Headers · Auth · Schema.
  • Response viewer - status bar and tabs: Body · Headers · History · Stats · cURL.

Schema-driven request building

The Schema tab in the request builder surfaces the request body shape and every response schema. Each field is rendered with type / required / nullable / readOnly / maxLength / default / enum (choices) / FK target / multichoice - pulled straight from the OpenAPI vendor extensions emitted by umbral-openapi.

Two autofill flows for request bodies:

  • Automatic on endpoint click - when you select an endpoint that declares a request body, the Body tab pre-fills with a required-only JSON skeleton (zero-tracked per operation so harmless re-renders don't clobber your edits).
  • Manual via the Schema tab - [Required only] / [All fields] buttons regenerate the skeleton if you want to start over or expand to every documented field.

readOnly fields are excluded from the skeleton by default - they're server-generated (created_at, computed columns) and would 4xx if sent.

Required-param locking

The Params table reads pathItem.parameters and operation.parameters from the spec, so {id} on a detail URL appears in the table. Required params are auto-enabled, the checkbox is locked, and the trash button is locked - sending the request without them would 4xx, so the UI removes the temptation.

Filter discovery

When the OpenAPI spec advertises filter parameters (umbral-openapi emits them for every filterable resource; filtering is on by default unless you call ResourceConfig::disable_filters()), the Params table shows them with proper types and lookups. When it doesn't, the playground falls back to chip suggestions inferred from the response item schema: [+ eq] / [+ contains] / [+ gte] / [+ in] etc. per field, scoped by type.

Per-endpoint stats

The Stats tab computes performance insights from the request history for the current endpoint:

  • Stat cards - total requests, avg duration, p95 / p99 (with fastest/slowest hints), success rate.
  • Duration-over-time chart (Recharts area chart with rolling-window moving average).
  • Status distribution - 2xx/3xx/4xx/5xx/ERR class bars plus exact code counts.
  • Avg response size, latest sample timestamp.

Per-endpoint history with clickable detail dialog

Every send is recorded per operationId. Click any history row to open a detail dialog showing the full request (method, URL, query params, headers + auth, body) and response (status, timing, headers, body). Both bodies render in Monaco editors with copy-to-clipboard.

Persistent UI state

Stored in IndexedDB via Dexie:

  • History - every request/response pair per endpoint. Survives reloads, 50-record cap per operation.
  • Editor state - sidebar search, expanded tag groups, active tabs (Request + Response). The UI lands back exactly where you left it after a reload.
  • Selected endpoint - reload returns you to the same operation.

Settings (base URL, default headers, variables) live in localStorage since they're small and need to be readable synchronously at store init.

Other features

  • Dark/light mode that follows the playground's theme toggle.
  • cURL export of any request via the cURL tab.
  • Bearer-token Auth tab for quick token testing.
  • Workspace settings sheet - base URL, variables ({{name}} substitution), default headers.

v1 limitations

  • Same-origin requests only (no CORS proxy).
  • Auth is a single bearer-token field. Spec-driven securitySchemes discovery is a v2 follow-up.
  • History total cap is per-operation only; no global ceiling yet.
  • Schema tab doesn't drill into nested $refs - names like → Profile are clickable in v2.

See the design spec for the full design history and the post-v1 backlog at bugs/playground-openapi-gaps.md.