errand
Async smolweb transport in one scheme-routed call.
errand fetches a URL over a small-web protocol and hands back the raw bytes, a
normalized status, and a MIME hint. One call, routed by scheme:
let page = fetch.await?;
if page.status == Success
It does not speak HTTP, on purpose. HTTP is already well served by reqwest, and
a browser-extension host gets HTTP from the browser. errand covers the gap they
leave: the protocols of the small web, with a small dependency cone.
Made with AI
Protocols
errand::fetch routes by URL scheme and returns a normalized Status, the
protocol meta line, and the raw body. It does not follow redirects; the caller
decides. Seven read schemes are routed through fetch:
| Scheme | Port | Transport | Notes |
|---|---|---|---|
gemini:// |
1965 | TLS (TOFU) | self-signed capsules, per-host pinning |
gopher:// |
70 | plaintext TCP | no status code |
finger:// |
79 | plaintext TCP | no status code |
spartan:// |
300 | plaintext TCP | numeric status |
nex:// |
1900 | plaintext TCP | no status code |
guppy:// |
6775 | UDP (stop-and-wait) | numeric status, ACK-per-packet |
titan:// |
1965 | TLS (TOFU) | gemini's upload sibling; see below |
Write companions
Two schemes write rather than fetch, so they are direct calls, not part of
fetch:
titan_upload— Titan (titan://, port 1965) is gemini's upload sibling. Call it with the body bytes, MIME type, and an optional token. A baretitan://URL passed tofetchsends a zero-byte upload and returns the server's gemini-format response (typically a redirect to the read location).misfin_send— Misfin (misfin://, port 1958) is gemini-style peer-to-peer mail. Delivery opens a TLS connection presenting a caller-supplied client certificate (ClientIdentity) and writes amisfin://<mailbox>@<host> <message>request line.errandowns only the client send side; it does not generate, store, or rotate certificates, and it does not serve a mailbox.
API
fetch(url: &str)/fetch_url(url: &Url)— fetch over the URL's scheme.fetch_timeout(url, Duration)/fetch_url_timeout(url, Duration)— the same with a per-request timeout; returnsError::Timeoutif it does not complete.titan_upload(...)— Titan write.misfin_send(...)withClientIdentityandMISFIN_PORT— Misfin mail send.set_trust_store,TofuStore,InMemoryTofu,PermissiveTofu— TOFU trust policy (see below).Scheme— the routable scheme enum, withScheme::parse(&str)anddefault_port().Response { url, status, raw_status, meta, body }with.mime().Status—Success,Input,Redirect,Failure,CertRequired.Error—UnsupportedScheme,BadUrl,Connect,Io,Protocol,Timeout,CertificateChanged { host, pinned, seen }.Urlre-exported from theurlcrate.
Response::raw_status preserves the protocol's own two-digit code for the
schemes that have one (gemini, spartan, guppy, titan); it is None for gopher,
finger, and nex, which carry no status.
Trust (TOFU)
Gemini capsules are conventionally self-signed, so there is no CA to anchor
trust. errand pins the SHA-256 of a host's leaf certificate on first contact
and requires every later visit to present the same one. A changed certificate (a
man-in-the-middle, a key rotation, or a moved host) surfaces as
Error::CertificateChanged and the request is not sent; the embedder decides
whether to re-pin.
The pin store is the TofuStore trait, so the embedder chooses durability.
InMemoryTofu holds pins for the process lifetime. A host with a profile can
supply its own durable store. The store is installed once via set_trust_store;
until then errand uses PermissiveTofu (accept-any), so the module changes
nothing for callers that do not opt in.
Install
[]
= "0.1"
Or as a git dependency:
[]
= { = "https://github.com/mark-ik/errand" }
Build and test
Tests are inline #[cfg(test)] modules (request construction, scheme routing,
status mapping). There is no tests/ integration directory and no examples/.
Dependencies and platform
url2.5tokio1 (featuresnet,io-util,time; no full runtime)rustls0.23 andtokio-rustls0.26 on theringprovider, withtls12ring0.17 for leaf-certificate SHA-256 (TOFU pinning)
aws-lc-rs is deliberately left off so the crate builds without a C toolchain.
Edition 2021, MSRV (rust-version) 1.74.
Status
Version 0.1.0. Single crate, no workspace. Consumed one-way as a git dependency
by the broader Mere stack: the fetch actor routes http(s) to netfetcher and
the smolweb schemes here to errand, and the Misfin mail crate uses errand as
its client send transport. Dependency direction is one-way (consumers pull
errand, never the reverse).
License
MPL-2.0. See LICENSE.