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
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.
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:
cd plugins/umbral-playground/frontendnpm installA 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_contenttype→Permissions 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-onlyJSON 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
securitySchemesdiscovery 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→ Profileare clickable in v2.
See the design spec for the full design history and the post-v1 backlog at bugs/playground-openapi-gaps.md.