Native doctor
A file-driven HTTP client: describe requests as JSON/YAML, compose them with sandboxed Rhai scripts, scaffold from OpenAPI specs, and browse everything in an embedded web UI.

The problem
Ad-hoc curl one-liners work until a flow needs multiple calls, shared variables, and repetition. I wanted HTTP exploration and light automation that is file-driven: describe a request once, compose it with others, and run it from the CLI or embed it in your own Rust code.
A session
The shape of the tool is easier to show than explain:
-- what nativedoctor looks like in practice --
~ $ nativedoctor run examples/hello.yaml▋
# one HTTP call per file: method, URL, query, headers, body
~ $ nativedoctor run ./login.yaml ./fetch.rhai --retain-runtime▋
# a shared runtime map across files: set vars in login, read them in fetch
~ $ nativedoctor run ./fetch.rhai▋
# Rhai scripts can import other scripts and request files
~ $ nativedoctor generate -i openapi.json -o ./generated --format yaml▋
# scaffold request files from an OpenAPI 3.0.x spec
~ $ nativedoctor web ./api ./scripts▋
# browse and run everything in a local browser UI
Scripts run sandboxed: no filesystem, no network inside Rhai. HTTP happens only through imported request files and api::invoke(#{ ... }), where per-call overrides win over the runtime map. ${VAR} templates pull from process env, --env files, Rhai set, and optional persistence; dynamic ${!name} helpers (uuidv4, nanoid, now) produce a fresh value per expansion.
The pieces
A workspace split so the core outlives the CLI:
| Crate | Role |
|---|---|
nd-core |
request loading, template expansion, execution, the Rhai engine (usable as a library) |
nd-generate |
OpenAPI 3.0.x -> request file scaffolding |
nd-web |
Axum server + JSON API + Vue 3 SPA, embedded with rust-embed |
nd-cli |
the nativedoctor binary |
Embedding nd-core in your own Rust code is a first-class use case: RequestFile::from_file, expand, execute, and run_rhai_script are the documented entry points.
Design decisions
- Scripts are declarative about side effects. Locked-down Rhai means a script can’t silently touch the network or the filesystem; every side effect is an explicit
invoke()on a request file. - The SPA ships inside the binary.
build.rsrunspnpm installandpnpm buildso the web UI is embedded; release CI pins pnpm and Node 20 and builds a four-target matrix (linux, windows, darwin aarch64/x86_64). No runtime assets to deploy. - Deterministic expansion order. Process env ->
--envfiles -> Rhaiset-> overrides. Same input, same request, every time.
Sharp edges, stated upfront
OpenAPI 3.1 and some $ref patterns are rejected; generation is solid for 3.0.x and honest about the gap. And the web UI is a local development tool: anyone who can reach the bind address can trigger outbound HTTP and run configured Rhai. It binds loopback by default and the README documents the exposure.
Up for later..
The core crate API is stable enough to build on. The documented gaps point the way: closing the OpenAPI 3.1 gap and growing the dynamic helper library. For me, it already replaced curl-and-pray workflows for smoke tests and API exploration.